Repository: nanoapi-io/napi Branch: main Commit: 1827e3b087e5 Files: 308 Total size: 1.5 MB Directory structure: gitextract_l1fkgvec/ ├── .github/ │ ├── CODE_OF_CONDUCT.md │ ├── CONTRIBUTING.md │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── PULL_REQUEST_TEMPLATE.md │ ├── SECURITY.md │ └── workflows/ │ ├── lint_test_compile.yml │ └── release.yml ├── .gitignore ├── .vscode/ │ └── settings.json ├── CLA/ │ ├── CORPORATE_CONTRIBUTOR_LICENSE_AGREEMENT.md │ └── INDIVIDUAL_CONTRIBUTOR_LICENSE_AGREEMENT.md ├── LICENSE.md ├── LICENSE_EE.md ├── README.md ├── SUPPORT.md ├── USERS.md ├── WEEKLY_UPDATE_LOG.md ├── deno.json ├── examples/ │ ├── README.md │ ├── c/ │ │ └── network/ │ │ ├── .napirc │ │ ├── makefile │ │ └── src/ │ │ ├── cartographie/ │ │ │ ├── cartographie.c │ │ │ ├── cartographie.h │ │ │ ├── tnmap.c │ │ │ └── tnmap.h │ │ ├── main.c │ │ └── tcp_cs/ │ │ ├── clientTCP.c │ │ ├── clientTCP.h │ │ ├── serveurTCP.c │ │ └── serveurTCP.h │ ├── csharp/ │ │ └── EndpointExample/ │ │ ├── .napirc │ │ ├── EndpointExample.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ ├── appsettings.Development.json │ │ ├── appsettings.json │ │ └── src/ │ │ ├── MyEndpoint.cs │ │ ├── MyRequest.cs │ │ └── MyResponse.cs │ ├── java/ │ │ └── websocket/ │ │ ├── README.md │ │ ├── pom.xml │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── samples/ │ │ │ │ └── websocket/ │ │ │ │ ├── client/ │ │ │ │ │ ├── GreetingService.java │ │ │ │ │ ├── SimpleClientWebSocketHandler.java │ │ │ │ │ └── SimpleGreetingService.java │ │ │ │ ├── config/ │ │ │ │ │ └── SampleWebSocketsApplication.java │ │ │ │ ├── echo/ │ │ │ │ │ ├── DefaultEchoService.java │ │ │ │ │ ├── EchoService.java │ │ │ │ │ └── EchoWebSocketHandler.java │ │ │ │ └── snake/ │ │ │ │ ├── Direction.java │ │ │ │ ├── Location.java │ │ │ │ ├── Snake.java │ │ │ │ ├── SnakeTimer.java │ │ │ │ ├── SnakeUtils.java │ │ │ │ └── SnakeWebSocketHandler.java │ │ │ └── resources/ │ │ │ └── static/ │ │ │ ├── echo.html │ │ │ ├── index.html │ │ │ └── snake.html │ │ └── test/ │ │ └── java/ │ │ └── samples/ │ │ └── websocket/ │ │ ├── echo/ │ │ │ ├── CustomContainerWebSocketsApplicationTests.java │ │ │ └── SampleWebSocketsApplicationTests.java │ │ └── snake/ │ │ └── SnakeTimerTests.java │ └── python/ │ └── flask/ │ ├── .napi/ │ │ └── manifests/ │ │ └── 1775598176791-d5c2217.json │ ├── .napirc │ ├── api/ │ │ ├── data/ │ │ │ ├── elves.py │ │ │ └── hobbits.py │ │ ├── services/ │ │ │ ├── elves.py │ │ │ └── hobbits.py │ │ ├── views/ │ │ │ ├── elves.py │ │ │ └── hobbits.py │ │ └── wizards/ │ │ ├── data.py │ │ ├── services.py │ │ └── views.py │ ├── app.py │ └── pyproject.toml ├── install_scripts/ │ ├── install.ps1 │ └── install.sh ├── media/ │ └── README.md ├── scripts/ │ └── get_version.ts ├── src/ │ ├── cli/ │ │ ├── handlers/ │ │ │ ├── extract/ │ │ │ │ └── index.ts │ │ │ ├── generate/ │ │ │ │ └── index.ts │ │ │ ├── init/ │ │ │ │ └── index.ts │ │ │ ├── set/ │ │ │ │ ├── apiKey.ts │ │ │ │ └── index.ts │ │ │ └── view/ │ │ │ └── index.ts │ │ ├── index.ts │ │ └── middlewares/ │ │ ├── checkVersion.ts │ │ ├── globalConfig.ts │ │ └── napiConfig.ts │ ├── helpers/ │ │ ├── fileSystem/ │ │ │ └── index.ts │ │ ├── sourceCode/ │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ └── treeSitter/ │ │ └── parsers.ts │ ├── index.test.ts │ ├── index.ts │ ├── languagePlugins/ │ │ ├── c/ │ │ │ ├── README.md │ │ │ ├── dependencyFormatting/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── extractor/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── headerResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── includeResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── invocationResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── metrics/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── symbolRegistry/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── testFiles/ │ │ │ │ ├── cFiles/ │ │ │ │ │ ├── .clangd │ │ │ │ │ ├── .napirc │ │ │ │ │ ├── all.h │ │ │ │ │ ├── burgers.c │ │ │ │ │ ├── burgers.h │ │ │ │ │ ├── crashcases.h │ │ │ │ │ ├── errors.h │ │ │ │ │ ├── main.c │ │ │ │ │ ├── oldman.h │ │ │ │ │ ├── personnel.c │ │ │ │ │ └── personnel.h │ │ │ │ └── index.ts │ │ │ └── warnings/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ ├── queries.ts │ │ │ └── types.ts │ │ ├── csharp/ │ │ │ ├── README.md │ │ │ ├── dependencyFormatting/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── extensionResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── extractor/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── invocationResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── metricsAnalyzer/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── namespaceMapper/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── namespaceResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── projectMapper/ │ │ │ │ ├── index.test.ts │ │ │ │ └── index.ts │ │ │ ├── testFiles/ │ │ │ │ ├── csharpFiles/ │ │ │ │ │ ├── 2Namespaces1File.cs │ │ │ │ │ ├── Models.cs │ │ │ │ │ ├── Namespaced.cs │ │ │ │ │ ├── Nested.cs │ │ │ │ │ ├── OtherFileSameNamespace.cs │ │ │ │ │ ├── Program.cs │ │ │ │ │ ├── SemiNamespaced.cs │ │ │ │ │ ├── Subfolder/ │ │ │ │ │ │ ├── GlobalUsings.cs │ │ │ │ │ │ └── TestFiles.Subfolder.csproj │ │ │ │ │ ├── TestFiles.csproj │ │ │ │ │ └── Usage.cs │ │ │ │ └── index.ts │ │ │ └── usingResolver/ │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── java/ │ │ │ ├── dependencyFormatting/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── extractor/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── importResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── invocationResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── metrics/ │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ ├── packageMapper/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ ├── packageResolver/ │ │ │ │ ├── index.test.ts │ │ │ │ ├── index.ts │ │ │ │ ├── queries.ts │ │ │ │ └── types.ts │ │ │ └── testFiles/ │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ └── napi-tests/ │ │ │ ├── .project │ │ │ └── src/ │ │ │ └── main/ │ │ │ └── java/ │ │ │ └── io/ │ │ │ └── nanoapi/ │ │ │ └── testfiles/ │ │ │ ├── App.java │ │ │ ├── food/ │ │ │ │ ├── Burger.java │ │ │ │ ├── Condiment.java │ │ │ │ ├── DoubleBurger.java │ │ │ │ ├── Food.java │ │ │ │ ├── Steak.java │ │ │ │ └── goron/ │ │ │ │ └── Pebble.java │ │ │ └── medication/ │ │ │ └── Wormkiller.java │ │ └── python/ │ │ ├── dependencyResolver/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── exportExtractor/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── importExtractor/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── itemResolver/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── metricAnalyzer/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── moduleResolver/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── symbolExtractor/ │ │ │ ├── index.test.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── usageResolver/ │ │ ├── index.test.ts │ │ ├── index.ts │ │ └── types.ts │ ├── manifest/ │ │ ├── auditManifest/ │ │ │ ├── index.ts │ │ │ └── types.ts │ │ └── dependencyManifest/ │ │ ├── c/ │ │ │ └── index.ts │ │ ├── csharp/ │ │ │ ├── index.test.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── java/ │ │ │ └── index.ts │ │ ├── labeling/ │ │ │ ├── graph.ts │ │ │ ├── grouping.ts │ │ │ ├── index.ts │ │ │ ├── model.ts │ │ │ └── types.ts │ │ ├── python/ │ │ │ └── index.ts │ │ └── types.ts │ ├── scripts/ │ │ └── generate_python_stdlib_list/ │ │ ├── output.json │ │ └── script.sh │ └── symbolExtractor/ │ ├── c/ │ │ └── index.ts │ ├── csharp/ │ │ └── index.ts │ ├── index.ts │ ├── java/ │ │ └── index.ts │ ├── python/ │ │ └── index.ts │ └── types.ts └── viewer/ ├── .gitignore ├── README.md ├── index.html ├── src/ │ ├── App.tsx │ ├── api.ts │ ├── components/ │ │ ├── DependencyVisualizer/ │ │ │ ├── DependencyVisualizer.tsx │ │ │ ├── components/ │ │ │ │ ├── BreadcrumbNav.tsx │ │ │ │ ├── DisplayNameWithTooltip.tsx │ │ │ │ ├── FileExplorerSidebar.tsx │ │ │ │ ├── SymbolExtractionDialog.tsx │ │ │ │ ├── contextMenu/ │ │ │ │ │ ├── FileContextMenu.tsx │ │ │ │ │ └── SymbolContextMenu.tsx │ │ │ │ ├── controls/ │ │ │ │ │ ├── ControlExtensions/ │ │ │ │ │ │ ├── FiltersExtension.tsx │ │ │ │ │ │ ├── GraphDepthExtension.tsx │ │ │ │ │ │ └── MetricsExtension.tsx │ │ │ │ │ └── Controls.tsx │ │ │ │ └── detailsPanes/ │ │ │ │ ├── AlertBadge.tsx │ │ │ │ ├── FileDetailsPane.tsx │ │ │ │ ├── Metrics.tsx │ │ │ │ └── SymbolDetailsPane.tsx │ │ │ └── visualizers/ │ │ │ ├── FileVisualizer.tsx │ │ │ ├── ProjectVisualizer.tsx │ │ │ └── SymbolVisualizer.tsx │ │ └── shadcn/ │ │ ├── Alert.tsx │ │ ├── Breadcrumb.tsx │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── Dialog.tsx │ │ ├── Dropdownmenu.tsx │ │ ├── Input.tsx │ │ ├── Label.tsx │ │ ├── Scrollarea.tsx │ │ ├── Separator.tsx │ │ ├── Sheet.tsx │ │ ├── Sidebar.tsx │ │ ├── Skeleton.tsx │ │ ├── Slider.tsx │ │ ├── Tooltip.tsx │ │ └── hooks/ │ │ └── use-mobile.tsx │ ├── contexts/ │ │ └── ThemeProvider.tsx │ ├── cytoscape/ │ │ ├── elements/ │ │ │ ├── file.ts │ │ │ ├── project.ts │ │ │ ├── symbol.ts │ │ │ └── types.ts │ │ ├── fileDependencyVisualizer/ │ │ │ └── index.ts │ │ ├── label/ │ │ │ └── index.ts │ │ ├── layout/ │ │ │ └── index.ts │ │ ├── metrics/ │ │ │ └── index.ts │ │ ├── projectDependencyVisualizer/ │ │ │ └── index.ts │ │ ├── styles/ │ │ │ └── index.ts │ │ └── symbolDependencyVisualizer/ │ │ └── index.ts │ ├── index.css │ ├── lib/ │ │ └── utils.ts │ ├── main.tsx │ ├── pages/ │ │ ├── ManifestList.tsx │ │ └── ManifestView.tsx │ └── types/ │ ├── auditManifest.ts │ └── dependencyManifest.ts └── vite.config.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/CODE_OF_CONDUCT.md ================================================ ## Code of Conduct ### Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ### Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting - Other conduct which falls outside of [NanoAPI's Open-Source Manifesto](https://github.com/Nano-API/oss-manifesto/blob/main/README.md) ### Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ### Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ### Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [info@nanoapi.io](mailto:info@nanoapi.io). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ### Attribution This Code of Conduct is adapted from the [ncc CoC](https://github.com/vercel/ncc/blob/main/CODE_OF_CONDUCT.md) which is itself adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ ================================================ FILE: .github/CONTRIBUTING.md ================================================ # How to Contribute to NanoAPI ## Contributor License Agreement - By submitting code as an individual you agree to the [individual contributor license agreement](/CLA/INDIVIDUAL_CONTRIBUTOR_LICENSE_AGREEMENT.md). - By submitting code as an entity you agree to the [corporate contributor license agreement](/CLA/CORPORATE_CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Housekeeping First off, thank you for being here. You dropped this: 👑 Here are some guidelines to help you get started contributing to NanoAPI. 1. Follow our [Code of Conduct](/.github/CODE_OF_CONDUCT.md). 2. Check for open issues before creating a new one. 3. We require an open issue for all pull requests. 4. Help others by reviewing their pull requests. 5. All donations we receive go directly back to our contributors. We’re here to support you when you successfully submit a PR to us. Your efforts help the community grow, and we want to give back to those who help make that possible! ## How to File Issues Make use of the issue templates, and label your issues appropriately. If you’re unsure about which label to use, don’t worry! We will help you choose the right one. ## How to Submit a Pull Request 1. Don't panic. 2. Ensure an issue exists for the changes you want to make. 3. Fork the repository. 4. Create a new branch. 5. Make your changes. 6. Test your changes. 7. Push your changes to your fork. 1. Make sure to rebase before pushing. 8. Submit a pull request. 9. Follow the template and fill in all the sections. 10. Wait for feedback. 11. Make changes if necessary. 12. Celebrate your success after your PR gets merged. The Codex Astartes supports this action. ## Development Environment You will need the following tools to develop NanoAPI: - [Node.js](https://nodejs.org/en/) version 22 or higher. ### Environment Set Up We use the fork-and-pull model for contributions. Here’s how you can set up your development environment: 1. Fork the repository. 2. Clone your fork locally: ```bash $ git clone https://github.com//napi.git ``` 3. Enter the folder: ```bash $ cd napi ``` 4. Add the original repository as a remote: ```bash $ git remote add upstream https://github.com/nanoapi-io/napi.git ``` 5. Install the dependencies: ```bash $ npm install ``` > [!NOTE] > You may encounter issues on a second or third install of dependencies. If this > happens, install with `npm i --no-cache --force` to fix these issues. ### Running the Project When running locally, the shared libraries and the UI must be built before the CLI can be run. To build them: ```bash $ npm run build ``` Next, we want to run the CLI and the UI with hot reload. You will need two terminal windows for this. 1. In the first terminal, run the CLI. This command should be run in the `napi` directory with a `workdir` pointing to the project you want to work on. For example, if you want to work on Apache Airflow, run: ```bash $ npm run dev:cli -- audit view -- --workdir=/path/to/airflow ``` Running the `audit view` command from the CLI will spin up a web server on your localhost. You can access the UI by navigating to `http://localhost:3000`. > [!NOTE] > In case of port collisions, the UI will automatically switch to the next > available port. 2. In the second terminal, run the UI. This command should be run in the `napi` directory as well: ```bash $ npm run dev:app ``` This controls the hot reload functionality for the UI. You can now make changes to the UI and see them reflected in real-time. > [!IMPORTANT] > The react UI elements (sidebar, header, etc.) will automatically reload when > you make changes. However any Cytoscape elements will not. You will need to > refresh the page to see those changes. ### Project Setup You can use any project (in a supported language) to test the CLI. There are some steps that must be taken to set up the project: 1. Clone or CD to the repo you want to work on/test with. For this example we'll use Apache Airflow. ```bash git clone https://github.com/apache/airflow.git cd airflow ``` 2. From the `napi` repo initialize the project using the CLI, which will create a `.napirc` file in the project root. This file contains the configuration for the project and is required for the CLI to work.: ```bash cd /path/to/napi # or just use a different terminal npm start -- init -- --workdir=/path/to/airflow ``` > [!NOTE] > If you encounter any issues with the config file, you can > [check the reference for the file on our documentation](https://docs.nanoapi.io/default-guide/reference/napirc). ### Testing ```bash $ npm test ``` ### Linting ```bash $ npm run lint ``` ### Release Process We are currently formalizing the release process. For now, the NanoAPI team will handle making regular releases. To ensure releases run smoothly, put the content of your changes in our [CHANGELOG](/packages/cli/CHANGELOG.md) file. ### Documentation We are also building on the documentation process. For now, include any documentation changes in your PRs and we will add them into the main documentation. The critical documentation to maintain is for any changes that impact the following: - CLI commands - Configuration file - Local development setup - Release process - Testing - Linting ### Discussions vs Issues We use GitHub Discussions for general questions, ideas, and feedback. If you have a question, please use the Discussions tab. If you have a bug report or feature request, please use the Issues tab. --- That's it for this guide for now. So long, and thanks for all the fish! 🚀 ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report for a bug, regression, or unexpected behavior title: "[BUG] " labels: bug assignees: "" --- ## Description ## Expected Behavior ## Actual Behavior ## Possible Fix ## Steps to Reproduce 1. 2. 3. 4. ## Context ## Your Environment - Version used: - Environment name and version (e.g. Chrome 39, node.js 5.4): - Operating System and version (desktop or mobile): - Link to your project: ### Config File Paste your `.napirc` below ```json { "your": "config" } ``` ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: "[FEATURE] " labels: enhancement assignees: "" --- ## Detailed Description ## Context ## Possible Implementation ## Your Environment - Version used: - Environment name and version (e.g. Chrome 39, node.js 5.4): - Operating System and version (desktop or mobile): - Link to your project: ### Config File Paste your `.napirc` below ```json { "your": "config" } ``` ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ ## Type of change - [ ] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Documentation update ## Description Please include a summary of the changes and the related issue. Explain the motivation behind this change. ## Related Issue Issue Number: # ## Motivation and Context ## How Has This Been Tested? ## Screenshots (if appropriate): ## Checklist - [ ] I have read the [contributing guidelines](CONTRIBUTING.md) - [ ] My code follows the code style of this project. - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] I have performed a self-review of my own code - [ ] I have commented my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [ ] My changes generate no new warnings - [ ] Any dependent changes have been merged and published ================================================ FILE: .github/SECURITY.md ================================================ # Security Policy Please report any suspected security vulnerabilities privately to [security@nanoapi.io](mailto:security@nanoapi.io). Please do NOT create publicly viewable issues for suspected security vulnerabilities. We will acknowledge receipt of your vulnerability report as soon as possible and strive to send you regular updates about our progress. If you're curious about the status of your disclosure please feel free to email us again. If you want to encrypt your disclosure email please email us to ask for our PGP key. Please refrain from requesting compensation for reporting vulnerabilities. If you want we will publicly acknowledge your responsible disclosure. We also try to make the issue public after the vulnerability is announced. Usually bug reports are made public after 72 hours, if possible. You are not allowed to search for security vulnerabilities on any hosted service of NanoAPI without the consent of the party hosting it. NanoAPI is open source software and can be installed for testing and security issues on your own infrastructure. ================================================ FILE: .github/workflows/lint_test_compile.yml ================================================ name: Lint, tests and build on: push: branches-ignore: ["main"] jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Deno uses: denoland/setup-deno@v2 with: deno-version: "2.4.0" - name: Install dependencies run: | deno install --allow-scripts --reload - name: Deno lint run: deno lint - name: Deno fmt (check) run: deno fmt --check tests: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Deno uses: denoland/setup-deno@v2 with: deno-version: "2.4.0" - name: Install dependencies run: | deno install --allow-scripts --reload - name: Tests run: deno test -A compile: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Deno uses: denoland/setup-deno@v2 with: deno-version: "2.4.0" - name: Install dependencies run: | deno install --allow-scripts --reload - name: Compile run: deno task compile:all ================================================ FILE: .github/workflows/release.yml ================================================ name: Release Packages permissions: contents: write packages: write on: [workflow_dispatch] jobs: release: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Set up Deno uses: denoland/setup-deno@v2 with: deno-version: "2.4.0" - name: Get new version and bump deno.json id: get_version run: | VERSION=$(deno run -A scripts/get_version.ts) echo "release_version=$VERSION" >> $GITHUB_OUTPUT - name: Install dependencies run: | deno install --reload --allow-scripts - name: Compile run: | deno task compile:all - name: Create release uses: softprops/action-gh-release@v2 with: tag_name: v${{ steps.get_version.outputs.release_version }} files: | dist/* ================================================ FILE: .gitignore ================================================ node_modules dist .env napi_dist __pycache__ .ruff_cache .OPENAIKEY examples/csharp/EndpointExample/obj examples/csharp/EndpointExample/bin src/languagePlugins/csharp/testFiles/csharpFiles/**/obj napi.sln examples/csharp/EndpointExample/bin auditResponse.json .extracted/ napi-output/ coverage/ .vite/ .mvn/ target/ .settings/ .classpath ================================================ FILE: .vscode/settings.json ================================================ { "editor.formatOnSave": true, "editor.defaultFormatter": "denoland.vscode-deno" } ================================================ FILE: CLA/CORPORATE_CONTRIBUTOR_LICENSE_AGREEMENT.md ================================================ # Corporate contributor license agreement You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Nano API B.V. Except for the license granted herein to Nano API B.V. and recipients of software distributed by Nano API B.V., You reserve all right, title, and interest in and to Your Contributions. 1. Definitions. "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Nano API B.V. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to Nano API B.V. for inclusion in, or documentation of, any of the products owned or managed by Nano API B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Nano API B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Nano API B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." 2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Nano API B.V. and to recipients of software distributed by Nano API B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. 3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Nano API B.V. and to recipients of software distributed by Nano API B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. 1. You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of Your corporation here]." Such designations of exclusion for unauthorized employees are to be submitted via email to [legal@nanoapi.io](mailto:legal@nanoapi.io). 2. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). 3. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 4. Should You wish to submit work that is not Your original creation, You may submit it to Nano API B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]". 5. It is Your responsibility to notify Nano API B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf per Section 4. Such notification should be sent via email to [legal@nanoapi.io](mailto:legal@nanoapi.io). This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. ================================================ FILE: CLA/INDIVIDUAL_CONTRIBUTOR_LICENSE_AGREEMENT.md ================================================ # Individual contributor license agreement You accept and agree to the following terms and conditions for Your present and future Contributions submitted to Nano API B.V. Except for the license granted herein to Nano API B.V. and recipients of software distributed by Nano API B.V., You reserve all right, title, and interest in and to Your Contributions. 1. Definitions. "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with Nano API B.V. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to Nano API B.V. for inclusion in, or documentation of, any of the products owned or managed by Nano API B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to Nano API B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, Nano API B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution." 2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to Nano API B.V. and to recipients of software distributed by Nano API B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works. 3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to Nano API B.V. and to recipients of software distributed by Nano API B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed. 4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to Nano API B.V., or that your employer has executed a separate Corporate CLA with Nano API B.V. 5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions. 6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. 7. Should You wish to submit work that is not Your original creation, You may submit it to Nano API B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [insert_name_here]". 8. You agree to notify Nano API B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect. This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office. ================================================ FILE: LICENSE.md ================================================ # License Portions of this software are licensed as follows: - Content of branches other than the main branch (i.e. "master") are not licensed. - Source code files that contain ".ee." in their filename are NOT licensed under the Sustainable Use License. To use source code files that contain ".ee." in their filename you must hold a valid NanoAPI Enterprise License specifically allowing you access to such source code files and as defined in "LICENSE_EE.md". - All third party components incorporated into the NanoAPI Software are licensed under the original license provided by the owner of the applicable component. - Content outside of the above mentioned files or restrictions is available under the "Sustainable Use License" as defined below. ## Sustainable Use License Version 1.0 ### Acceptance By using the software, you agree to all of the terms and conditions below. ### Copyright License The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations below. ### Limitations You may use or modify the software only for your own internal business purposes or for non-commercial or personal use. You may distribute the software or provide it to others only if you do so free of charge for non-commercial purposes. You may not alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor’s trademarks is subject to applicable law. ### Patents The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company. ### Notices You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms. If you modify the software, you must include in any modified copies of the software a prominent notice stating that you have modified the software. ### No Other Rights These terms do not imply any licenses other than those expressly granted in these terms. ### Termination If you use the software in violation of these terms, such use is not licensed, and your license will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your license will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your license to terminate automatically and permanently. ### No Liability As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim. ### Definitions The “licensor” is the entity offering these terms. The “software” is the software the licensor makes available under these terms, including any portion of it. “You” refers to the individual or entity agreeing to these terms. “Your company” is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. Control means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect. “Your license” is the license granted to you for the software under these terms. “Use” means anything you do with the software requiring your license. “Trademark” means trademarks, service marks, and similar rights. ================================================ FILE: LICENSE_EE.md ================================================ # The NanoAPI Enterprise License (the “Enterprise License”) Copyright (c) 2024-present Nano API B.V. With regard to the NanoAPI Software: This software and associated documentation files (the "Software") may only be used in production, if you (and any entity that you represent) hold a valid NanoAPI Enterprise license corresponding to your usage. Subject to the foregoing sentence, you are free to modify this Software and publish patches to the Software. You agree that NanoAPI and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications and/or patches, and all such modifications and/or patches may only be used, copied, modified, displayed, distributed, or otherwise exploited with a valid NanoAPI Enterprise license for the corresponding usage. Notwithstanding the foregoing, you may copy and modify the Software for development and testing purposes, without requiring a subscription. You agree that NanoAPI and/or its licensors (as applicable) retain all right, title and interest in and to all such modifications. You are not granted any other rights beyond what is expressly stated herein. Subject to the foregoing, it is forbidden to copy, merge, publish, distribute, sublicense, and/or sell the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For all third party components incorporated into the NanoAPI Software, those components are licensed under the original license provided by the owner of the applicable component. ================================================ FILE: README.md ================================================ ![NanoAPI Banner](/media/github-banner.png) # napi - Better Software Architecture for the AI Age `napi` is a fully offline CLI that analyzes your codebase's architecture -- dependencies, complexity, and structure -- then lets you visualize and refactor it, all without sending your code anywhere. It generates dependency manifests from your source code, stores them locally, and serves an interactive graph visualizer directly from the CLI. ![NanoAPI UI Overview](/media/hero-app.png) ## Features - **🔍 Dependency Analysis**: Map every file, symbol, and dependency in your codebase automatically. - **🚨 Audit**: Detect files and symbols that exceed complexity, size, or coupling thresholds. - **📊 Interactive Visualizer**: Explore your architecture through Cytoscape.js graphs served locally in your browser. - **📝 Symbol Extraction**: Extract specific functions, classes, or symbols into standalone files for refactoring. - **🏷️ AI Labeling** (optional): Use OpenAI, Google, or Anthropic models to auto-label dependencies. - **⚙️ CI/CD Ready**: Integrates into any pipeline -- generate manifests on every push and track architecture over time. - **🔒 Fully Offline**: No accounts, no servers, no data leaves your machine. ## Supported Languages | Language | Status | | -------- | -------------- | | Python | ✅ Supported | | C# | ✅ Supported | | C | ✅ Supported | | Java | ✅ Supported | | C++ | 🚧 In Progress | | PHP | 🚧 In Progress | | JS/TS | 🚧 In Progress | ## Installation ### Unix (macOS, Linux) ```bash curl -fsSL https://raw.githubusercontent.com/nanoapi-io/napi/refs/heads/main/install_scripts/install.sh | bash ``` Or download a binary directly from [GitHub Releases](https://github.com/nanoapi-io/napi/releases/latest). ### Windows Use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) to run napi. Native Windows support is in progress. ## Quick Start ```bash # 1. Initialize your project (creates .napirc) napi init # 2. Generate a dependency manifest napi generate # 3. Open the visualizer in your browser napi view ``` That's it. Your manifest is saved locally in `.napi/manifests/` and the visualizer opens at `http://localhost:3000`. ## CLI Commands ### `napi init` Interactive setup that creates a `.napirc` configuration file in your project root. Prompts you for: - **Language** -- Python, C#, C, or Java - **Include/exclude patterns** -- which files to analyze - **Output directory** -- where extracted symbols are written - **AI labeling** (optional) -- provider and concurrency settings ```bash napi init ``` ### `napi generate` Analyzes your codebase and generates a dependency manifest. The manifest captures every file, symbol, dependency, and metric (lines, complexity, coupling). Manifests are saved as JSON files in `.napi/manifests/` with the naming pattern `{timestamp}-{commitSha}.json`. ```bash # Interactive (prompts for branch/commit if not in git) napi generate # Non-interactive (for CI) napi generate --branch main --commit-sha abc1234 --commit-sha-date 2026-01-01T00:00:00Z ``` Options: - `--branch` -- Git branch name (auto-detected if omitted) - `--commit-sha` -- Git commit hash (auto-detected if omitted) - `--commit-sha-date` -- Commit date in ISO 8601 format (auto-detected if omitted) - `--labelingApiKey` -- API key for AI labeling (overrides global config) ### `napi view` Starts a local web server and opens an interactive dependency visualizer in your browser. ```bash napi view napi view --port 8080 ``` The viewer provides: - **Manifest list** -- browse all locally stored manifests by branch, commit, and date - **Project graph** -- file-level dependency map with Cytoscape.js - **File graph** -- symbol-level view within a file (functions, classes, variables) - **Symbol graph** -- transitive dependency chain for a specific symbol - **File explorer sidebar** -- navigate your codebase structure - **Audit alerts** -- visual indicators for files/symbols exceeding thresholds ### `napi extract` Extracts specific symbols from your codebase into separate files using a local manifest. ```bash # Extract a function from a specific file napi extract --symbol "src/auth/login.py|authenticate" # Extract multiple symbols napi extract --symbol "src/models.py|User" --symbol "src/models.py|Session" # Use a specific manifest (defaults to latest) napi extract --symbol "src/main.py|run" --manifestId 1712500000000-a1b2c3d ``` Output is written to `{outDir}/extracted-{timestamp}/`. ### `napi set apiKey` Configure API keys for AI-powered dependency labeling. Keys are stored in the global config (not in your project). ```bash napi set apiKey ``` Prompts for: - **Provider** -- Google, OpenAI, or Anthropic - **API key** -- your provider API key ## Local Manifest Storage All manifests are stored in `.napi/manifests/` relative to your project root. Each manifest is a self-contained JSON file: ```json { "id": "1712500000000-a1b2c3d", "branch": "main", "commitSha": "a1b2c3d4e5f6...", "commitShaDate": "2026-04-07T10:00:00Z", "createdAt": "2026-04-07T10:01:00Z", "manifest": {} } ``` Add `.napi/` to your `.gitignore` or commit it to track architecture history in version control -- your choice. ## CI/CD Integration Generate manifests automatically on every push: ```yaml # .github/workflows/napi.yml name: Generate Manifest on: [push] jobs: manifest: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install napi run: curl -fsSL https://raw.githubusercontent.com/nanoapi-io/napi/refs/heads/main/install_scripts/install.sh | bash - name: Generate manifest run: napi generate --branch ${{ github.ref_name }} --commit-sha ${{ github.sha }} --commit-sha-date "$(git log -1 --format=%cI)" ``` ## Configuration Reference ### `.napirc` Project-level configuration created by `napi init`: ```json { "language": "python", "python": { "version": "3.10" }, "project": { "include": ["src/**/*.py"], "exclude": [".git/**", "**/__pycache__/**", "napi_out/**"] }, "outDir": "napi_out", "labeling": { "modelProvider": "openai", "maxConcurrency": 5 } } ``` ### Global Config Stored in your OS config directory (`~/.config/napi/config.json` on Linux, `~/Library/Application Support/napi/config.json` on macOS). Managed via `napi set apiKey`. ```json { "labeling": { "apiKeys": { "openai": "sk-...", "google": "AIza...", "anthropic": "sk-ant-..." } } } ``` ## Development Requires [Deno](https://deno.land/) v2.4+. ```bash # Install dependencies deno install --allow-scripts # Run CLI in dev mode deno task dev # Run viewer dev server (hot-reload) deno task dev:viewer # Build viewer for production deno task build:viewer # Compile binary (includes viewer) deno task compile # Run tests deno task test # Lint deno lint # Format deno fmt ``` ## Contributing We welcome contributions from the community. Please read our [contributing guide](https://github.com/nanoapi-io/napi/blob/main/.github/CONTRIBUTING.md) for details on how to get involved. ## License `napi` is licensed under the [Sustainable Use License](https://github.com/nanoapi-io/napi/blob/main/LICENSE.md). ## Further Reading - [Automating the Strangler Pattern with Microlithic Development](https://medium.com/@joel_40950/automating-the-strangler-pattern-with-microlithic-development-241e4e0dd79b) - [Rise of the "Microlith": Rethinking Microservices for Modern Developers](https://dev.to/nanojoel/open-sourcing-nanoapi-rethinking-microservices-for-modern-developers-14m2) ## Donations NanoAPI is a fair-source project. Because of this, we feel it would be unethical to keep any donations to ourselves. Instead, here is how we will handle donations: - Donations go into a pool - Money from the pool will be distributed to contributors - At the end of the year, any remaining money will be donated to a charity of the community's choice We will post regular updates on how much money is in the pool and how it is being distributed. ================================================ FILE: SUPPORT.md ================================================ # Support using napi Welcome to NanoAPI! We use Github for tracking bugs and feature requests. There are helpful volunteers who may be able to help you. If it happens that you know the solution to an existing bug, please first open the issue in order to keep track of it. Afterwards open the relevant pull/merge request that potentially fixes it. Please remember this is a community project and you are not entitled to free support. Be kind to anyone helping out. For commercial support reach out to [info@nanoapi.io](mailto:info@nanoapi.io). ## Documentation - [User Documentation](https://nanoapi.io/docs) ================================================ FILE: USERS.md ================================================ - [Ainur](https://ainurhq.cloud/) ================================================ FILE: WEEKLY_UPDATE_LOG.md ================================================ # Weekly Update Log This log tracks significant changes, improvements, and new features implemented in the NAPI project on a weekly basis. It serves as a quick reference for team members and users to stay informed about the project's progress and recent developments. ## April 27th to May 16th, 2024 ### Deno Migration & Performance Optimization - Migrate from Node to Deno for improved performance and ease of development - Replaced Node.js dependencies with native Deno modules, reducing bundle size and performance - Migrated from Express to Oak framework (JSR:@oak/oak) for improved Deno compatibility - Eliminated external dependencies (uuid, express, http-proxy-middleware, octokit) for built-in Deno modules - Switched to JSR registry for standard libraries (@std/path@^1.0.9) to leverage Deno's ecosystem ### Build System & Deployment - Change release from npm registry to Deno executables published on GitHub - Created convenience installation scripts - Streamlined GitHub Actions release workflow to reduce complexity and build times - Enhanced installation instructions in README to facilitate installation ### API & Development Experience - Refactored version checking to use native fetch API with timeout control (5s) to prevent blocking the use of the tool when being rate limited by GitHub API - Updated version checking to check against our GitHub release instead of npm registry - Implemented platform-specific browser launching with Deno.Command for better cross-OS compatibility - Enhanced port detection using Deno's native networking APIs ### Frontend - Improved API integration between frontend and backend components - Optimized frontend routing to work seamlessly with Oak middleware - Move from bare Radix UI component to more comprehensive Shadcn/UI component - Simplify some UX flow - Improve highlighting logic from the file explorer to the graph ## April 21st to 27th, 2024 ### Feature Improvements - Improved Python symbol extraction with better handling of partial imports - Enhanced visual representation of nodes for large codebases - Updated highlighting mechanism for better code navigation - Implemented extraction mode with API integration and symbol editing capabilities - Fixed Python error AST node cleanup for more reliable extraction - Added C# metrics feature for enhanced code analysis capabilities ### Build System and Package Management Improvements - Switched from using published `@nanoapi.io/shared` package to bundling it directly with the CLI - Added `tsup` for improved bundling configuration - Updated package versions and dependencies across the workspace - Made the root package private and updated workspace configurations ### CLI Enhancements - Enhanced version checking with detailed update instructions - Fixed path resolution for static file serving - Improved build process with better bundling configuration - Added proper shebang handling for the CLI executable ### Build System Updates - Removed separate build step for shared package - Updated build scripts to use tsup for better bundling - Fixed path resolution in development and production environments - Improved static file serving configuration ### Version Management - Updated CLI version to 1.0.3 - Set shared package version to 0.0.0 since it's now bundled - Added proper version checking middleware with detailed update instructions ================================================ FILE: deno.json ================================================ { "version": "1.0.15", "name": "@napi/cli", "exports": "./src/index.ts", "nodeModulesDir": "auto", "lock": false, "imports": { "@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.4", "@inquirer/prompts": "npm:@inquirer/prompts@^7.5.3", "@langchain/anthropic": "npm:@langchain/anthropic@^0.3.23", "@langchain/core": "npm:@langchain/core@^0.3.61", "@langchain/google-genai": "npm:@langchain/google-genai@^0.2.14", "@langchain/google-vertexai": "npm:@langchain/google-vertexai@^0.2.14", "@langchain/langgraph": "npm:@langchain/langgraph@^0.3.5", "@langchain/openai": "npm:@langchain/openai@^0.5.15", "@oak/oak": "jsr:@oak/oak@^17.1.4", "@radix-ui/react-dialog": "npm:@radix-ui/react-dialog@^1.1.14", "@radix-ui/react-dropdown-menu": "npm:@radix-ui/react-dropdown-menu@^2.1.15", "@radix-ui/react-label": "npm:@radix-ui/react-label@^2.1.7", "@radix-ui/react-scroll-area": "npm:@radix-ui/react-scroll-area@^1.2.9", "@radix-ui/react-separator": "npm:@radix-ui/react-separator@^1.1.7", "@radix-ui/react-slider": "npm:@radix-ui/react-slider@^1.3.5", "@radix-ui/react-slot": "npm:@radix-ui/react-slot@^1.2.3", "@radix-ui/react-tooltip": "npm:@radix-ui/react-tooltip@^1.2.7", "@std/expect": "jsr:@std/expect@^1.0.16", "@std/path": "jsr:@std/path@^1.0.9", "@std/testing": "jsr:@std/testing@^1.0.14", "@tailwindcss/vite": "npm:@tailwindcss/vite@^4.1.8", "@types/cytoscape-fcose": "npm:@types/cytoscape-fcose@^2.2.4", "@types/react": "npm:@types/react@^19.1.6", "@types/react-dom": "npm:@types/react-dom@^19.1.3", "@vitejs/plugin-react": "npm:@vitejs/plugin-react@^4.4.1", "class-variance-authority": "npm:class-variance-authority@^0.7.1", "clsx": "npm:clsx@^2.1.1", "cytoscape": "npm:cytoscape@^3.32.0", "cytoscape-fcose": "npm:cytoscape-fcose@^2.2.0", "glob": "npm:glob@^11.0.2", "lucide-react": "npm:lucide-react@^0.513.0", "react": "npm:react@^19.1.0", "react-dom": "npm:react-dom@^19.1.0", "react-router-dom": "npm:react-router-dom@^7.5.3", "tailwind-merge": "npm:tailwind-merge@^3.3.0", "tailwindcss": "npm:tailwindcss@^4.1.8", "tree-sitter": "npm:tree-sitter@^0.22.4", "tree-sitter-c": "npm:tree-sitter-c@0.23.6", "tree-sitter-c-sharp": "npm:tree-sitter-c-sharp@^0.23.1", "tree-sitter-python": "npm:tree-sitter-python@^0.23.6", "tree-sitter-java": "npm:tree-sitter-java@^0.23.5", "tw-animate-css": "npm:tw-animate-css@^1.3.4", "vite": "npm:vite@^6.3.5", "yargs": "https://deno.land/x/yargs@v18.0.0-deno/deno.ts", "yargs-types": "https://deno.land/x/yargs@v18.0.0-deno/deno-types.ts", "zod": "npm:zod@^3.24.4" }, "exclude": ["viewer/"], "tasks": { "dev": "deno run -A src/index.ts", "dev:viewer": "cd viewer && deno run -A npm:vite", "build:viewer": "cd viewer && deno run -A npm:vite build", "compile": "deno task build:viewer && deno compile -A --include=viewer/dist --output=dist/napi src/index.ts", "compile-linux": "deno compile -A --include=viewer/dist --output=dist/napi.linux --target=x86_64-unknown-linux-gnu src/index.ts", "compile-macos": "deno compile -A --include=viewer/dist --output=dist/napi.macos --target=x86_64-apple-darwin src/index.ts", "compile-windows": "deno compile -A --include=viewer/dist --output=dist/napi.exe --target=x86_64-pc-windows-msvc src/index.ts", "compile:all": "deno task build:viewer && deno task 'compile-*'", "test": "deno test -A" }, "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "react", "jsxImportSourceTypes": "@types/react" } } ================================================ FILE: examples/README.md ================================================ ## Quickstart Each example is already setup and initialize to use napi. ### To view the project on the UI ``` napi split configure ``` ### Or to split the APIs from the command line directly ``` napi split ``` ================================================ FILE: examples/c/network/.napirc ================================================ { "language": "c", "project": { "include": [ "**/*.c", "**/*.h" ], "exclude": [ ".git/**", "**/dist/**", "**/build/**", "**/bin/**", "**/obj/**", "**/napi_out/**" ] }, "outDir": "napi_out", "audit": { "file": { "maxCodeChar": 5000, "maxCodeLine": 300, "maxDependency": 20, "maxCyclomaticComplexity": 50 }, "symbol": { "maxCodeChar": 1000, "maxCodeLine": 80, "maxDependency": 10, "maxCyclomaticComplexity": 15 } } } ================================================ FILE: examples/c/network/makefile ================================================ # --- Macros --- # compilation exec = gcc -Wall -Wextra -pedantic -O3 -g3 -fopenmp -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls comp = gcc -c -Wall -Wextra -pedantic -O3 -g3 -fopenmp -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls toexe = -o toobj = -o # sources SRCSC= $(wildcard src/*/*.c) SRCSH= $(wildcard src/*/*.h) TSTSC= $(wildcard tst/*.c) # objects OBJS= $(wildcard obj/*.o) OBJBM= $(wildcard src/*/*.o) # --- Functions --- all: test main main: create_exe_main to_obj_src test: bin_mkdir create_exe_new to_obj clean: clean_o clean_bin bin_mkdir: mkdir -p bin; obj_mkdir: mkdir -p obj; tst_mov: mv $(foreach exe, $(TSTSC:tst/%.c=%) , $(exe) ) bin create_exe_new: create_obj $(foreach test_obj,$(TSTSC:%.c=%.o), $(exec) $(test_obj) $(SRCSC:%.c=%.o) $(toexe) $(test_obj:%.o=%);) create_exe_main: create_obj_main $(exec) obj/main.o $(SRCSC:%.c=%.o) $(toexe) app to_obj: obj_mkdir to_obj_src to_obj_tst to_obj_src: mv $(foreach obj,$(SRCSC:%.c=%.o), $(obj) ) obj to_obj_tst: mv $(foreach obj,$(TSTSC:%.c=%.o), $(obj) ) obj create_obj: create_obj_src create_obj_tst create_obj_src: $(foreach cfl, $(SRCSC), $(comp) $(cfl) $(toobj) $(cfl:%.c=%).o;) create_obj_tst: $(foreach cfl, $(TSTSC), $(comp) $(cfl) $(toobj) $(cfl:%.c=%).o;) create_obj_main: obj_mkdir create_obj_src $(comp) src/main.c $(toobj) obj/main.o ================================================ FILE: examples/c/network/src/cartographie/cartographie.c ================================================ #include "cartographie.h" void scanHorizontal(const char* network) { struct sockaddr_in addr; int sockfd, ttl = 64, timeout = 1000; char ip[INET_ADDRSTRLEN]; // Création du socket sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); if (sockfd < 0) { perror("socket"); exit(EXIT_FAILURE); } // Définition de l'option TTL if (setsockopt(sockfd, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl)) < 0) { perror("setsockopt"); exit(EXIT_FAILURE); } // Définition de l'option de timeout struct timeval tv; tv.tv_sec = timeout / 1000; tv.tv_usec = (timeout % 1000) * 1000; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) { perror("setsockopt"); exit(EXIT_FAILURE); } // Scan du réseau for (int i = 1; i <= 255; i++) { memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; // Adresse --> octets int octet1, octet2, octet3, octet4; sscanf(network, "%d.%d.%d.%d", &octet1, &octet2, &octet3, &octet4); // Dernier octet --> i octet4 = i; // Réassemblement de l'adresse char host[INET_ADDRSTRLEN]; sprintf(host, "%d.%d.%d.%d", octet1, octet2, octet3, octet4); // Conversion adresse en binaire inet_pton(AF_INET, host, &(addr.sin_addr)); // Conversion adresse en texte inet_ntop(AF_INET, &(addr.sin_addr), ip, INET_ADDRSTRLEN); // Envoi du paquet ICMP struct icmphdr icmp; memset(&icmp, 0, sizeof(icmp)); icmp.type = ICMP_ECHO; icmp.code = 0; icmp.checksum = htons(~(ICMP_ECHO << 8)); if (sendto(sockfd, &icmp, sizeof(icmp), 0, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("sendto"); continue; } // Attente de la réponse fd_set read_set; FD_ZERO(&read_set); FD_SET(sockfd, &read_set); struct timeval timeout; timeout.tv_sec = 1; timeout.tv_usec = 0; int select_result = select(sockfd + 1, &read_set, NULL, NULL, &timeout); if (select_result < 0) { perror("select"); continue; } else if (select_result == 0) { printf("Aucune réponse de %s\n", ip); continue; } // Réception de la réponse char buffer[IP_MAXPACKET]; struct sockaddr_in sender; socklen_t sender_len = sizeof(sender); ssize_t packet_len = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&sender, &sender_len); if (packet_len < 0) { perror("recvfrom"); continue; } // Affichage de l'adresse printf("L'hôte %s est en ligne\n", ip); } // Fermeture socket close(sockfd); } ================================================ FILE: examples/c/network/src/cartographie/cartographie.h ================================================ #ifndef CARTOGRAPHIE_H #include #include #include #include #include #include #include #include #include #include #include "tnmap.h" void scanHorizontal(const char* network); #define CARTOGRAPHIE_H #endif ================================================ FILE: examples/c/network/src/cartographie/tnmap.c ================================================ #include "tnmap.h" // Vérifie si un hôte est actif // Retourne un socket si l'hôte est actif, 1 sinon int tnmap(const char* ip_addr) { int sock; struct sockaddr_in server; int port; int result[MAX_PORTS] = {0}; // Création du socket sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == -1) { perror("Échec de la création du socket"); return -1; } server.sin_addr.s_addr = inet_addr(ip_addr); server.sin_family = AF_INET; // Scan des ports for (port = 1; port <= MAX_PORTS; port++) { server.sin_port = htons(port); // Connexion à l'hôte distant if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) { result[port - 1] = 0; // Port fermé } else { result[port - 1] = 1; // Port ouvert close(sock); sock = socket(AF_INET, SOCK_STREAM, 0); // Socket pour le prochain port if (sock == -1) { perror("Échec de la création du socket"); return -1; } } } #pragma omp parallel for // Affichage des résultats for (port = 1; port <= MAX_PORTS; port++) { if (result[port - 1] == 1) { printf("Le port %d de %s est ouvert\n", port, ip_addr); } } return 1; } void scanVertical(const char* ip_addr) { for (int i = 0; i < 255; i++) { tnmap(ip_addr); } } ================================================ FILE: examples/c/network/src/cartographie/tnmap.h ================================================ #ifndef TNMAP_H #define TNMAP_H #include #include #include #include #include #include #define MAX_PORTS 65535 #endif int tnmap(const char* ip_addr); void scanVertical(const char* ip_addr); ================================================ FILE: examples/c/network/src/main.c ================================================ #include "cartographie/cartographie.h" #include #include int main(int argc, char *argv[]) { if (argc < 3) { fprintf(stderr, "Utilisation : %s \n", argv[0]); fprintf(stderr, "Modes :\n"); fprintf(stderr, " -h: Scan horizontal\n"); fprintf(stderr, " -v: Scan vertical\n"); fprintf(stderr, "Option:\n"); fprintf(stderr, " : IP à scanner verticalemnet or horizontalement (adresse de réseau)\n"); return 1; } if (strcmp(argv[1], "-h") == 0) { scanHorizontal(argv[2]); } else if (strcmp(argv[1], "-v") == 0) { scanVertical(argv[2]); } else { fprintf(stderr, "Mode invalide\n"); return 1; } return 0; } ================================================ FILE: examples/c/network/src/tcp_cs/clientTCP.c ================================================ #include "clientTCP.h" int startClient(int argc, char *argv[]) { int serverSocket; /* * Ouvrir socket (socket STREAM) */ if ((serverSocket = socket(PF_INET, SOCK_STREAM, 0)) <0) { perror ("erreur socket"); exit (1); } struct sockaddr_in serv_addr; /* * Lier l'adresse locale à la socket */; memset(&serv_addr, 0, sizeof(serv_addr) ); serv_addr.sin_family = AF_INET ; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(CLIENT_PORT); if (bind(serverSocket,(struct sockaddr *)&serv_addr, sizeof(serv_addr) ) <0) { perror ("servecho: erreur bind\n"); exit (1); } /* * Remplir la structure serv_addr avec l'adresse du serveur */ bzero( (char *) &serv_addr, sizeof(serv_addr) ); serv_addr.sin_family = AF_INET; if (argc != 3) { fprintf(stderr, "usage : %s \n", argv[0]); exit(1); } serv_addr.sin_port = htons((uint16_t) atoi(argv[2])); serv_addr.sin_addr.s_addr = inet_addr(argv[1]); if (connect(serverSocket, (struct sockaddr *) &serv_addr, sizeof(serv_addr) ) < 0){ perror ("cliecho : erreur connect"); return 1; } char buffer[280]; int n; while (1) { printf("cliecho : message à envoyer : "); memset(buffer, 0, sizeof(buffer)); scanf("%s", buffer); n = write(serverSocket, buffer, strlen(buffer)); if (n < 0) { perror("cliecho : erreur write\n"); close(serverSocket); return 1; } memset(buffer, 0, sizeof(buffer)); n = read(serverSocket, buffer, 280); if (n < 0) { perror("cliecho : erreur read\n"); close(serverSocket); return 1; } printf("cliecho : message reçu : %s\n", buffer); } } ================================================ FILE: examples/c/network/src/tcp_cs/clientTCP.h ================================================ #ifndef CLIENTTCP_H #include #include #include #include #include #include #include #include #include #define CLIENT_PORT 7776 int startClient(int argc, char *argv[]); #define CLIENTTCP_H #endif ================================================ FILE: examples/c/network/src/tcp_cs/serveurTCP.c ================================================ #include "serveurTCP.h" int startServer(void) { int serverSocket; struct sockaddr_in serv_addr; /* * Ouvrir socket (socket STREAM) */ if ((serverSocket = socket(PF_INET, SOCK_STREAM, 0))<0) { perror("erreur socket"); return 1; } memset(&serv_addr, 0, sizeof(serv_addr) ); serv_addr.sin_family = AF_INET ; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(SERV_PORT); if (bind(serverSocket,(struct sockaddr *)&serv_addr, sizeof(serv_addr) ) <0) { perror ("servecho: erreur bind\n"); return 1; } if (listen(serverSocket, SOMAXCONN) <0) { perror ("servecho: erreur listen\n"); return 1; } int dialogSocket; int clilen; struct sockaddr_in cli_addr; clilen = sizeof(cli_addr); dialogSocket = accept(serverSocket, (struct sockaddr *)&cli_addr, (socklen_t *)&clilen); if (dialogSocket < 0) { perror("servecho : erreur accep\n"); close(serverSocket); return 1; } while (1) { char buffer[280]; int n; n = read(dialogSocket, buffer, 280); if (n < 0) { perror("servecho : erreur read\n"); close(dialogSocket); return 1; } printf("servecho : message reçu : %s\n", buffer); n = write(dialogSocket, buffer, strlen(buffer)); if (n < 0) { perror("servecho : erreur write\n"); close(dialogSocket); return 1; } } close(dialogSocket); return 0; } ================================================ FILE: examples/c/network/src/tcp_cs/serveurTCP.h ================================================ #ifndef SERVEURTCP_H #include #include #include #include #include #include #include #include #include #define SERV_PORT 7777 int startServer(void); #define SERVEURTCP_H #endif ================================================ FILE: examples/csharp/EndpointExample/.napirc ================================================ { "language": "c-sharp", "project": { "include": ["**/*"], "exclude": ["bin/**", "obj/**", "napi-output/**"] }, "outDir": "napi-output", "audit": { "file": { "maxCodeChar": 5000, "maxCodeLine": 300, "maxDependency": 20, "maxCyclomaticComplexity": 50 }, "symbol": { "maxCodeChar": 1000, "maxCodeLine": 80, "maxDependency": 10, "maxCyclomaticComplexity": 15 } } } ================================================ FILE: examples/csharp/EndpointExample/EndpointExample.csproj ================================================ net9.0 enable enable ================================================ FILE: examples/csharp/EndpointExample/Program.cs ================================================ using FastEndpoints; var bld = WebApplication.CreateBuilder(); bld.Services.AddFastEndpoints(); var app = bld.Build(); app.UseFastEndpoints(); app.Run(); ================================================ FILE: examples/csharp/EndpointExample/Properties/launchSettings.json ================================================ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "http://localhost:5288", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "https": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, "applicationUrl": "https://localhost:7299;http://localhost:5288", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: examples/csharp/EndpointExample/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } } } ================================================ FILE: examples/csharp/EndpointExample/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" } ================================================ FILE: examples/csharp/EndpointExample/src/MyEndpoint.cs ================================================ public class MyEndpoint : Endpoint { public override void Configure() { Post("/api/user/create"); AllowAnonymous(); } public override async Task HandleAsync(MyRequest req, CancellationToken ct) { await SendAsync(new() { FullName = req.FirstName + " " + req.LastName, IsOver18 = req.Age > 18 }); } } ================================================ FILE: examples/csharp/EndpointExample/src/MyRequest.cs ================================================ public class MyRequest { public string FirstName { get; set; } public string LastName { get; set; } public int Age { get; set; } } ================================================ FILE: examples/csharp/EndpointExample/src/MyResponse.cs ================================================ public class MyResponse { public string FullName { get; set; } public bool IsOver18 { get; set; } } ================================================ FILE: examples/java/websocket/README.md ================================================ # NAPI Java example This example uses Spring's websocket sample artifact from the Maven repository. To experiment with it, simply run : ```Bash napi init ``` in this folder and follow the instructions. ================================================ FILE: examples/java/websocket/pom.xml ================================================ 4.0.0 websocket org.springframework.boot spring-boot-starter-parent 1.0.2.RELEASE napi Spring Boot WebSocket Sample Spring Boot WebSocket Sample 1.0-SNAPSHOT http://projects.spring.io/spring-boot/ Pivotal Software, Inc. http://www.spring.io ${basedir}/../.. 1.7 org.springframework.boot spring-boot-starter-websocket org.springframework.boot spring-boot-starter-actuator org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/client/GreetingService.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.client; public interface GreetingService { String getGreeting(); } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/client/SimpleClientWebSocketHandler.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.client; import java.util.concurrent.CountDownLatch; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; public class SimpleClientWebSocketHandler extends TextWebSocketHandler { protected Log logger = LogFactory.getLog(SimpleClientWebSocketHandler.class); private final GreetingService greetingService; private final CountDownLatch latch; @Autowired public SimpleClientWebSocketHandler(GreetingService greetingService, CountDownLatch latch) { this.greetingService = greetingService; this.latch = latch; } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { TextMessage message = new TextMessage(this.greetingService.getGreeting()); session.sendMessage(message); } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { this.logger.info("Received: " + message + " (" + this.latch.getCount() + ")"); session.close(); this.latch.countDown(); } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/client/SimpleGreetingService.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.client; public class SimpleGreetingService implements GreetingService { @Override public String getGreeting() { return "Hello world!"; } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/config/SampleWebSocketsApplication.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.config; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.context.web.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; import org.springframework.web.socket.handler.PerConnectionWebSocketHandler; import samples.websocket.client.GreetingService; import samples.websocket.client.SimpleGreetingService; import samples.websocket.echo.DefaultEchoService; import samples.websocket.echo.EchoService; import samples.websocket.echo.EchoWebSocketHandler; import samples.websocket.snake.SnakeWebSocketHandler; @Configuration @EnableAutoConfiguration @EnableWebSocket public class SampleWebSocketsApplication extends SpringBootServletInitializer implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(echoWebSocketHandler(), "/echo").withSockJS(); registry.addHandler(snakeWebSocketHandler(), "/snake").withSockJS(); } @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { return application.sources(SampleWebSocketsApplication.class); } public static void main(String[] args) { SpringApplication.run(SampleWebSocketsApplication.class, args); } @Bean public EchoService echoService() { return new DefaultEchoService("Did you say \"%s\"?"); } @Bean public GreetingService greetingService() { return new SimpleGreetingService(); } @Bean public WebSocketHandler echoWebSocketHandler() { return new EchoWebSocketHandler(echoService()); } @Bean public WebSocketHandler snakeWebSocketHandler() { return new PerConnectionWebSocketHandler(SnakeWebSocketHandler.class); } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/echo/DefaultEchoService.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.echo; public class DefaultEchoService implements EchoService { private final String echoFormat; public DefaultEchoService(String echoFormat) { this.echoFormat = (echoFormat != null) ? echoFormat : "%s"; } @Override public String getMessage(String message) { return String.format(this.echoFormat, message); } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/echo/EchoService.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.echo; public interface EchoService { String getMessage(String message); } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/echo/EchoWebSocketHandler.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.echo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; /** * Echo messages by implementing a Spring {@link WebSocketHandler} abstraction. */ public class EchoWebSocketHandler extends TextWebSocketHandler { private static Logger logger = LoggerFactory.getLogger(EchoWebSocketHandler.class); private final EchoService echoService; @Autowired public EchoWebSocketHandler(EchoService echoService) { this.echoService = echoService; } @Override public void afterConnectionEstablished(WebSocketSession session) { logger.debug("Opened new session in instance " + this); } @Override public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String echoMessage = this.echoService.getMessage(message.getPayload()); logger.debug(echoMessage); session.sendMessage(new TextMessage(echoMessage)); } @Override public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception { session.close(CloseStatus.SERVER_ERROR); } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/snake/Direction.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; public enum Direction { NONE, NORTH, SOUTH, EAST, WEST } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/snake/Location.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; public class Location { public int x; public int y; public static final int GRID_SIZE = 10; public static final int PLAYFIELD_HEIGHT = 480; public static final int PLAYFIELD_WIDTH = 640; public Location(int x, int y) { this.x = x; this.y = y; } public Location getAdjacentLocation(Direction direction) { switch (direction) { case NORTH: return new Location(this.x, this.y - Location.GRID_SIZE); case SOUTH: return new Location(this.x, this.y + Location.GRID_SIZE); case EAST: return new Location(this.x + Location.GRID_SIZE, this.y); case WEST: return new Location(this.x - Location.GRID_SIZE, this.y); case NONE: // fall through default: return this; } } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Location location = (Location) o; if (this.x != location.x) { return false; } if (this.y != location.y) { return false; } return true; } @Override public int hashCode() { int result = this.x; result = 31 * result + this.y; return result; } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/snake/Snake.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; import java.util.ArrayDeque; import java.util.Collection; import java.util.Deque; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; public class Snake { private static final int DEFAULT_LENGTH = 5; private final int id; private final WebSocketSession session; private Direction direction; private int length = DEFAULT_LENGTH; private Location head; private final Deque tail = new ArrayDeque(); private final String hexColor; public Snake(int id, WebSocketSession session) { this.id = id; this.session = session; this.hexColor = SnakeUtils.getRandomHexColor(); resetState(); } private void resetState() { this.direction = Direction.NONE; this.head = SnakeUtils.getRandomLocation(); this.tail.clear(); this.length = DEFAULT_LENGTH; } private synchronized void kill() throws Exception { resetState(); sendMessage("{'type': 'dead'}"); } private synchronized void reward() throws Exception { this.length++; sendMessage("{'type': 'kill'}"); } protected void sendMessage(String msg) throws Exception { this.session.sendMessage(new TextMessage(msg)); } public synchronized void update(Collection snakes) throws Exception { Location nextLocation = this.head.getAdjacentLocation(this.direction); if (nextLocation.x >= SnakeUtils.PLAYFIELD_WIDTH) { nextLocation.x = 0; } if (nextLocation.y >= SnakeUtils.PLAYFIELD_HEIGHT) { nextLocation.y = 0; } if (nextLocation.x < 0) { nextLocation.x = SnakeUtils.PLAYFIELD_WIDTH; } if (nextLocation.y < 0) { nextLocation.y = SnakeUtils.PLAYFIELD_HEIGHT; } if (this.direction != Direction.NONE) { this.tail.addFirst(this.head); if (this.tail.size() > this.length) { this.tail.removeLast(); } this.head = nextLocation; } handleCollisions(snakes); } private void handleCollisions(Collection snakes) throws Exception { for (Snake snake : snakes) { boolean headCollision = this.id != snake.id && snake.getHead().equals(this.head); boolean tailCollision = snake.getTail().contains(this.head); if (headCollision || tailCollision) { kill(); if (this.id != snake.id) { snake.reward(); } } } } public synchronized Location getHead() { return this.head; } public synchronized Collection getTail() { return this.tail; } public synchronized void setDirection(Direction direction) { this.direction = direction; } public synchronized String getLocationsJson() { StringBuilder sb = new StringBuilder(); sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(this.head.x), Integer.valueOf(this.head.y))); for (Location location : this.tail) { sb.append(','); sb.append(String.format("{x: %d, y: %d}", Integer.valueOf(location.x), Integer.valueOf(location.y))); } return String.format("{'id':%d,'body':[%s]}", Integer.valueOf(this.id), sb.toString()); } public int getId() { return this.id; } public String getHexColor() { return this.hexColor; } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/snake/SnakeTimer.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import org.apache.juli.logging.Log; import org.apache.juli.logging.LogFactory; /** * Sets up the timer for the multi-player snake game WebSocket example. */ public class SnakeTimer { private static final Log log = LogFactory.getLog(SnakeTimer.class); private static Timer gameTimer = null; private static final long TICK_DELAY = 100; private static final ConcurrentHashMap snakes = new ConcurrentHashMap(); public static synchronized void addSnake(Snake snake) { if (snakes.size() == 0) { startTimer(); } snakes.put(Integer.valueOf(snake.getId()), snake); } public static Collection getSnakes() { return Collections.unmodifiableCollection(snakes.values()); } public static synchronized void removeSnake(Snake snake) { snakes.remove(Integer.valueOf(snake.getId())); if (snakes.size() == 0) { stopTimer(); } } public static void tick() throws Exception { StringBuilder sb = new StringBuilder(); for (Iterator iterator = SnakeTimer.getSnakes().iterator(); iterator .hasNext();) { Snake snake = iterator.next(); snake.update(SnakeTimer.getSnakes()); sb.append(snake.getLocationsJson()); if (iterator.hasNext()) { sb.append(','); } } broadcast(String.format("{'type': 'update', 'data' : [%s]}", sb.toString())); } public static void broadcast(String message) throws Exception { Collection snakes = new CopyOnWriteArrayList<>(SnakeTimer.getSnakes()); for (Snake snake : snakes) { try { snake.sendMessage(message); } catch (Throwable ex) { // if Snake#sendMessage fails the client is removed removeSnake(snake); } } } public static void startTimer() { gameTimer = new Timer(SnakeTimer.class.getSimpleName() + " Timer"); gameTimer.scheduleAtFixedRate(new TimerTask() { @Override public void run() { try { tick(); } catch (Throwable ex) { log.error("Caught to prevent timer from shutting down", ex); } } }, TICK_DELAY, TICK_DELAY); } public static void stopTimer() { if (gameTimer != null) { gameTimer.cancel(); } } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/snake/SnakeUtils.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; import java.awt.Color; import java.util.Random; public class SnakeUtils { public static final int PLAYFIELD_WIDTH = 640; public static final int PLAYFIELD_HEIGHT = 480; public static final int GRID_SIZE = 10; private static final Random random = new Random(); public static String getRandomHexColor() { float hue = random.nextFloat(); // sat between 0.1 and 0.3 float saturation = (random.nextInt(2000) + 1000) / 10000f; float luminance = 0.9f; Color color = Color.getHSBColor(hue, saturation, luminance); return '#' + Integer.toHexString((color.getRGB() & 0xffffff) | 0x1000000) .substring(1); } public static Location getRandomLocation() { int x = roundByGridSize(random.nextInt(PLAYFIELD_WIDTH)); int y = roundByGridSize(random.nextInt(PLAYFIELD_HEIGHT)); return new Location(x, y); } private static int roundByGridSize(int value) { value = value + (GRID_SIZE / 2); value = value / GRID_SIZE; value = value * GRID_SIZE; return value; } } ================================================ FILE: examples/java/websocket/src/main/java/samples/websocket/snake/SnakeWebSocketHandler.java ================================================ /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; import java.awt.Color; import java.util.Iterator; import java.util.Random; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; public class SnakeWebSocketHandler extends TextWebSocketHandler { public static final int PLAYFIELD_WIDTH = 640; public static final int PLAYFIELD_HEIGHT = 480; public static final int GRID_SIZE = 10; private static final AtomicInteger snakeIds = new AtomicInteger(0); private static final Random random = new Random(); private final int id; private Snake snake; public static String getRandomHexColor() { float hue = random.nextFloat(); // sat between 0.1 and 0.3 float saturation = (random.nextInt(2000) + 1000) / 10000f; float luminance = 0.9f; Color color = Color.getHSBColor(hue, saturation, luminance); return '#' + Integer.toHexString((color.getRGB() & 0xffffff) | 0x1000000) .substring(1); } public static Location getRandomLocation() { int x = roundByGridSize(random.nextInt(PLAYFIELD_WIDTH)); int y = roundByGridSize(random.nextInt(PLAYFIELD_HEIGHT)); return new Location(x, y); } private static int roundByGridSize(int value) { value = value + (GRID_SIZE / 2); value = value / GRID_SIZE; value = value * GRID_SIZE; return value; } public SnakeWebSocketHandler() { this.id = snakeIds.getAndIncrement(); } @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { this.snake = new Snake(this.id, session); SnakeTimer.addSnake(this.snake); StringBuilder sb = new StringBuilder(); for (Iterator iterator = SnakeTimer.getSnakes().iterator(); iterator .hasNext();) { Snake snake = iterator.next(); sb.append(String.format("{id: %d, color: '%s'}", Integer.valueOf(snake.getId()), snake.getHexColor())); if (iterator.hasNext()) { sb.append(','); } } SnakeTimer .broadcast(String.format("{'type': 'join','data':[%s]}", sb.toString())); } @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); if ("west".equals(payload)) { this.snake.setDirection(Direction.WEST); } else if ("north".equals(payload)) { this.snake.setDirection(Direction.NORTH); } else if ("east".equals(payload)) { this.snake.setDirection(Direction.EAST); } else if ("south".equals(payload)) { this.snake.setDirection(Direction.SOUTH); } } @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception { SnakeTimer.removeSnake(this.snake); SnakeTimer.broadcast(String.format("{'type': 'leave', 'id': %d}", Integer.valueOf(this.id))); } } ================================================ FILE: examples/java/websocket/src/main/resources/static/echo.html ================================================ Apache Tomcat WebSocket Examples: Echo
================================================ FILE: examples/java/websocket/src/main/resources/static/index.html ================================================ Apache Tomcat WebSocket Examples: Index

Please select the sample you would like to try.

================================================ FILE: examples/java/websocket/src/main/resources/static/snake.html ================================================ Apache Tomcat WebSocket Examples: Multiplayer Snake
================================================ FILE: examples/java/websocket/src/test/java/samples/websocket/echo/CustomContainerWebSocketsApplicationTests.java ================================================ /* * Copyright 2012-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.echo; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.util.SocketUtils; import org.springframework.web.socket.client.WebSocketConnectionManager; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import samples.websocket.client.GreetingService; import samples.websocket.client.SimpleClientWebSocketHandler; import samples.websocket.client.SimpleGreetingService; import samples.websocket.config.SampleWebSocketsApplication; import samples.websocket.echo.CustomContainerWebSocketsApplicationTests.CustomContainerConfiguration; import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = { SampleWebSocketsApplication.class, CustomContainerConfiguration.class }) @WebAppConfiguration @IntegrationTest @DirtiesContext public class CustomContainerWebSocketsApplicationTests { private static Log logger = LogFactory .getLog(CustomContainerWebSocketsApplicationTests.class); private static int PORT = SocketUtils.findAvailableTcpPort(); private static final String WS_URI = "ws://localhost:" + PORT + "/ws/echo/websocket"; @Configuration protected static class CustomContainerConfiguration { @Bean public EmbeddedServletContainerFactory embeddedServletContainerFactory() { return new TomcatEmbeddedServletContainerFactory("/ws", PORT); } } @Test public void runAndWait() throws Exception { ConfigurableApplicationContext context = SpringApplication.run( ClientConfiguration.class, "--spring.main.web_environment=false"); long count = context.getBean(ClientConfiguration.class).latch.getCount(); context.close(); assertEquals(0, count); } @Configuration static class ClientConfiguration implements CommandLineRunner { private final CountDownLatch latch = new CountDownLatch(1); @Override public void run(String... args) throws Exception { logger.info("Waiting for response: latch=" + this.latch.getCount()); this.latch.await(10, TimeUnit.SECONDS); logger.info("Got response: latch=" + this.latch.getCount()); } @Bean public WebSocketConnectionManager wsConnectionManager() { WebSocketConnectionManager manager = new WebSocketConnectionManager(client(), handler(), WS_URI); manager.setAutoStartup(true); return manager; } @Bean public StandardWebSocketClient client() { return new StandardWebSocketClient(); } @Bean public SimpleClientWebSocketHandler handler() { return new SimpleClientWebSocketHandler(greetingService(), this.latch); } @Bean public GreetingService greetingService() { return new SimpleGreetingService(); } } } ================================================ FILE: examples/java/websocket/src/test/java/samples/websocket/echo/SampleWebSocketsApplicationTests.java ================================================ /* * Copyright 2012-2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.echo; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.test.IntegrationTest; import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.socket.client.WebSocketConnectionManager; import org.springframework.web.socket.client.standard.StandardWebSocketClient; import samples.websocket.client.GreetingService; import samples.websocket.client.SimpleClientWebSocketHandler; import samples.websocket.client.SimpleGreetingService; import samples.websocket.config.SampleWebSocketsApplication; import static org.junit.Assert.assertEquals; @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = SampleWebSocketsApplication.class) @WebAppConfiguration @IntegrationTest("server.port:0") @DirtiesContext public class SampleWebSocketsApplicationTests { private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class); private static String WS_URI; @Value("${local.server.port}") private int port; @Before public void init() { WS_URI = "ws://localhost:" + this.port + "/echo/websocket"; } @Test public void runAndWait() throws Exception { ConfigurableApplicationContext context = SpringApplication.run( ClientConfiguration.class, "--spring.main.web_environment=false"); long count = context.getBean(ClientConfiguration.class).latch.getCount(); context.close(); assertEquals(0, count); } @Configuration static class ClientConfiguration implements CommandLineRunner { private final CountDownLatch latch = new CountDownLatch(1); @Override public void run(String... args) throws Exception { logger.info("Waiting for response: latch=" + this.latch.getCount()); this.latch.await(10, TimeUnit.SECONDS); logger.info("Got response: latch=" + this.latch.getCount()); } @Bean public WebSocketConnectionManager wsConnectionManager() { WebSocketConnectionManager manager = new WebSocketConnectionManager(client(), handler(), WS_URI); manager.setAutoStartup(true); return manager; } @Bean public StandardWebSocketClient client() { return new StandardWebSocketClient(); } @Bean public SimpleClientWebSocketHandler handler() { return new SimpleClientWebSocketHandler(greetingService(), this.latch); } @Bean public GreetingService greetingService() { return new SimpleGreetingService(); } } } ================================================ FILE: examples/java/websocket/src/test/java/samples/websocket/snake/SnakeTimerTests.java ================================================ /* * Copyright 2012-2013 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package samples.websocket.snake; import java.io.IOException; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.assertThat; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; public class SnakeTimerTests { @Test public void removeDysfunctionalSnakes() throws Exception { Snake snake = mock(Snake.class); doThrow(new IOException()).when(snake).sendMessage(anyString()); SnakeTimer.addSnake(snake); SnakeTimer.broadcast(""); assertThat(SnakeTimer.getSnakes().size(), is(0)); } } ================================================ FILE: examples/python/flask/.napi/manifests/1775598176791-d5c2217.json ================================================ { "id": "1775598176791-d5c2217", "branch": "main", "commitSha": "d5c22175a36c5de08482bfa3e1ea59758e888405", "commitShaDate": "2025-07-07T11:42:05+02:00", "createdAt": "2026-04-07T21:42:56.791Z", "manifest": { "api/data/elves.py": { "id": "api/data/elves.py", "filePath": "api/data/elves.py", "metrics": { "linesCount": 11, "codeLineCount": 11, "characterCount": 297, "codeCharacterCount": 239, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": {}, "dependents": { "api/services/elves.py": { "id": "api/services/elves.py", "symbols": { "Elf": "Elf", "elves": "elves", "ElfService": "ElfService" } } }, "symbols": { "Elf": { "id": "Elf", "type": "class", "positions": [ { "start": { "index": 0, "row": 0, "column": 0 }, "end": { "index": 176, "row": 6, "column": 36 } } ], "description": "", "metrics": { "linesCount": 7, "codeLineCount": 6, "characterCount": 176, "codeCharacterCount": 138, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": {}, "dependents": { "api/services/elves.py": { "id": "api/services/elves.py", "symbols": { "ElfService": "ElfService" } } } }, "elves": { "id": "elves", "type": "variable", "positions": [ { "start": { "index": 179, "row": 9, "column": 0 }, "end": { "index": 296, "row": 13, "column": 1 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 117, "codeCharacterCount": 101, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": {}, "dependents": { "api/services/elves.py": { "id": "api/services/elves.py", "symbols": { "ElfService": "ElfService" } } } } } }, "api/data/hobbits.py": { "id": "api/data/hobbits.py", "filePath": "api/data/hobbits.py", "metrics": { "linesCount": 11, "codeLineCount": 11, "characterCount": 323, "codeCharacterCount": 265, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": {}, "dependents": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "symbols": { "Hobbit": "Hobbit", "hobbits": "hobbits", "HobbitService": "HobbitService" } } }, "symbols": { "Hobbit": { "id": "Hobbit", "type": "class", "positions": [ { "start": { "index": 0, "row": 0, "column": 0 }, "end": { "index": 179, "row": 6, "column": 36 } } ], "description": "", "metrics": { "linesCount": 7, "codeLineCount": 6, "characterCount": 179, "codeCharacterCount": 141, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": {}, "dependents": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "symbols": { "HobbitService": "HobbitService" } } } }, "hobbits": { "id": "hobbits", "type": "variable", "positions": [ { "start": { "index": 182, "row": 9, "column": 0 }, "end": { "index": 322, "row": 13, "column": 1 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 140, "codeCharacterCount": 124, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": {}, "dependents": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "symbols": { "HobbitService": "HobbitService" } } } } } }, "api/services/elves.py": { "id": "api/services/elves.py", "filePath": "api/services/elves.py", "metrics": { "linesCount": 21, "codeLineCount": 21, "characterCount": 602, "codeCharacterCount": 419, "dependencyCount": 1, "dependentCount": 1, "cyclomaticComplexity": 5 }, "language": "python", "dependencies": { "api/data/elves.py": { "id": "api/data/elves.py", "isExternal": false, "symbols": { "Elf": "Elf", "elves": "elves" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "ElfService": "ElfService", "ElfListView": "ElfListView", "ElfCreateView": "ElfCreateView", "ElfDetailView": "ElfDetailView", "ElfUpdateView": "ElfUpdateView", "ElfDeleteView": "ElfDeleteView" } } }, "symbols": { "ElfService": { "id": "ElfService", "type": "class", "positions": [ { "start": { "index": 40, "row": 3, "column": 0 }, "end": { "index": 601, "row": 26, "column": 33 } } ], "description": "", "metrics": { "linesCount": 24, "codeLineCount": 20, "characterCount": 561, "codeCharacterCount": 382, "dependencyCount": 1, "dependentCount": 1, "cyclomaticComplexity": 5 }, "dependencies": { "api/data/elves.py": { "id": "api/data/elves.py", "isExternal": false, "symbols": { "Elf": "Elf", "elves": "elves" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "ElfListView": "ElfListView", "ElfCreateView": "ElfCreateView", "ElfDetailView": "ElfDetailView", "ElfUpdateView": "ElfUpdateView", "ElfDeleteView": "ElfDeleteView" } } } } } }, "api/services/hobbits.py": { "id": "api/services/hobbits.py", "filePath": "api/services/hobbits.py", "metrics": { "linesCount": 21, "codeLineCount": 21, "characterCount": 698, "codeCharacterCount": 515, "dependencyCount": 1, "dependentCount": 1, "cyclomaticComplexity": 5 }, "language": "python", "dependencies": { "api/data/hobbits.py": { "id": "api/data/hobbits.py", "isExternal": false, "symbols": { "Hobbit": "Hobbit", "hobbits": "hobbits" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "HobbitService": "HobbitService", "HobbitListView": "HobbitListView", "HobbitCreateView": "HobbitCreateView", "HobbitDetailView": "HobbitDetailView", "HobbitUpdateView": "HobbitUpdateView", "HobbitDeleteView": "HobbitDeleteView" } } }, "symbols": { "HobbitService": { "id": "HobbitService", "type": "class", "positions": [ { "start": { "index": 47, "row": 3, "column": 0 }, "end": { "index": 697, "row": 26, "column": 38 } } ], "description": "", "metrics": { "linesCount": 24, "codeLineCount": 20, "characterCount": 650, "codeCharacterCount": 471, "dependencyCount": 1, "dependentCount": 1, "cyclomaticComplexity": 5 }, "dependencies": { "api/data/hobbits.py": { "id": "api/data/hobbits.py", "isExternal": false, "symbols": { "Hobbit": "Hobbit", "hobbits": "hobbits" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "HobbitListView": "HobbitListView", "HobbitCreateView": "HobbitCreateView", "HobbitDetailView": "HobbitDetailView", "HobbitUpdateView": "HobbitUpdateView", "HobbitDeleteView": "HobbitDeleteView" } } } } } }, "api/views/elves.py": { "id": "api/views/elves.py", "filePath": "api/views/elves.py", "metrics": { "linesCount": 39, "codeLineCount": 39, "characterCount": 1770, "codeCharacterCount": 1279, "dependencyCount": 3, "dependentCount": 2, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": { "api/services/elves.py": { "id": "api/services/elves.py", "isExternal": false, "symbols": { "ElfService": "ElfService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request", "Blueprint": "Blueprint" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "elves_bp": "elves_bp", "app": "app" } }, "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "elves_bp": "elves_bp" } } }, "symbols": { "ElfCreateView": { "id": "ElfCreateView", "type": "class", "positions": [ { "start": { "index": 430, "row": 17, "column": 0 }, "end": { "index": 604, "row": 21, "column": 22 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 174, "codeCharacterCount": 142, "dependencyCount": 3, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/elves.py": { "id": "api/services/elves.py", "isExternal": false, "symbols": { "ElfService": "ElfService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "elves_bp": "elves_bp" } } } }, "ElfDeleteView": { "id": "ElfDeleteView", "type": "class", "positions": [ { "start": { "index": 1450, "row": 55, "column": 0 }, "end": { "index": 1589, "row": 58, "column": 19 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 139, "codeCharacterCount": 116, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/elves.py": { "id": "api/services/elves.py", "isExternal": false, "symbols": { "ElfService": "ElfService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "elves_bp": "elves_bp" } } } }, "ElfDetailView": { "id": "ElfDetailView", "type": "class", "positions": [ { "start": { "index": 762, "row": 30, "column": 0 }, "end": { "index": 900, "row": 33, "column": 18 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 138, "codeCharacterCount": 115, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/elves.py": { "id": "api/services/elves.py", "isExternal": false, "symbols": { "ElfService": "ElfService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "elves_bp": "elves_bp" } } } }, "ElfListView": { "id": "ElfListView", "type": "class", "positions": [ { "start": { "index": 157, "row": 7, "column": 0 }, "end": { "index": 285, "row": 10, "column": 20 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 128, "codeCharacterCount": 105, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/elves.py": { "id": "api/services/elves.py", "isExternal": false, "symbols": { "ElfService": "ElfService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "elves_bp": "elves_bp" } } } }, "ElfUpdateView": { "id": "ElfUpdateView", "type": "class", "positions": [ { "start": { "index": 1076, "row": 42, "column": 0 }, "end": { "index": 1273, "row": 46, "column": 26 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 197, "codeCharacterCount": 165, "dependencyCount": 3, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/elves.py": { "id": "api/services/elves.py", "isExternal": false, "symbols": { "ElfService": "ElfService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request" } } }, "dependents": { "api/views/elves.py": { "id": "api/views/elves.py", "symbols": { "elves_bp": "elves_bp" } } } }, "elves_bp": { "id": "elves_bp", "type": "variable", "positions": [ { "start": { "index": 115, "row": 4, "column": 0 }, "end": { "index": 154, "row": 4, "column": 39 } }, { "start": { "index": 341, "row": 14, "column": 0 }, "end": { "index": 427, "row": 14, "column": 86 } }, { "start": { "index": 662, "row": 25, "column": 0 }, "end": { "index": 759, "row": 27, "column": 1 } }, { "start": { "index": 965, "row": 37, "column": 0 }, "end": { "index": 1073, "row": 39, "column": 1 } }, { "start": { "index": 1339, "row": 50, "column": 0 }, "end": { "index": 1447, "row": 52, "column": 1 } }, { "start": { "index": 1658, "row": 62, "column": 0 }, "end": { "index": 1769, "row": 64, "column": 1 } } ], "description": "", "metrics": { "linesCount": 14, "codeLineCount": 14, "characterCount": 549, "codeCharacterCount": 525, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/views/elves.py": { "id": "api/views/elves.py", "isExternal": false, "symbols": { "ElfListView": "ElfListView", "ElfCreateView": "ElfCreateView", "ElfDetailView": "ElfDetailView", "ElfUpdateView": "ElfUpdateView", "ElfDeleteView": "ElfDeleteView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "Blueprint": "Blueprint" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "app": "app" } } } } } }, "api/views/hobbits.py": { "id": "api/views/hobbits.py", "filePath": "api/views/hobbits.py", "metrics": { "linesCount": 47, "codeLineCount": 47, "characterCount": 1979, "codeCharacterCount": 1418, "dependencyCount": 3, "dependentCount": 2, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "isExternal": false, "symbols": { "HobbitService": "HobbitService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request", "Blueprint": "Blueprint" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "hobbits_bp": "hobbits_bp", "app": "app" } }, "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "hobbits_bp": "hobbits_bp" } } }, "symbols": { "HobbitCreateView": { "id": "HobbitCreateView", "type": "class", "positions": [ { "start": { "index": 470, "row": 19, "column": 0 }, "end": { "index": 659, "row": 23, "column": 25 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 189, "codeCharacterCount": 157, "dependencyCount": 3, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "isExternal": false, "symbols": { "HobbitService": "HobbitService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "hobbits_bp": "hobbits_bp" } } } }, "HobbitDeleteView": { "id": "HobbitDeleteView", "type": "class", "positions": [ { "start": { "index": 1616, "row": 61, "column": 0 }, "end": { "index": 1770, "row": 64, "column": 19 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 154, "codeCharacterCount": 131, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "isExternal": false, "symbols": { "HobbitService": "HobbitService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "hobbits_bp": "hobbits_bp" } } } }, "HobbitDetailView": { "id": "HobbitDetailView", "type": "class", "positions": [ { "start": { "index": 830, "row": 32, "column": 0 }, "end": { "index": 989, "row": 35, "column": 21 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 159, "codeCharacterCount": 136, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "isExternal": false, "symbols": { "HobbitService": "HobbitService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "hobbits_bp": "hobbits_bp" } } } }, "HobbitListView": { "id": "HobbitListView", "type": "class", "positions": [ { "start": { "index": 166, "row": 7, "column": 0 }, "end": { "index": 306, "row": 10, "column": 22 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 140, "codeCharacterCount": 117, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "isExternal": false, "symbols": { "HobbitService": "HobbitService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "hobbits_bp": "hobbits_bp" } } } }, "hobbits_bp": { "id": "hobbits_bp", "type": "variable", "positions": [ { "start": { "index": 120, "row": 4, "column": 0 }, "end": { "index": 163, "row": 4, "column": 43 } }, { "start": { "index": 367, "row": 14, "column": 0 }, "end": { "index": 467, "row": 16, "column": 1 } }, { "start": { "index": 722, "row": 27, "column": 0 }, "end": { "index": 827, "row": 29, "column": 1 } }, { "start": { "index": 1062, "row": 39, "column": 0 }, "end": { "index": 1190, "row": 43, "column": 1 } }, { "start": { "index": 1485, "row": 54, "column": 0 }, "end": { "index": 1613, "row": 58, "column": 1 } }, { "start": { "index": 1847, "row": 68, "column": 0 }, "end": { "index": 1978, "row": 72, "column": 1 } } ], "description": "", "metrics": { "linesCount": 22, "codeLineCount": 22, "characterCount": 635, "codeCharacterCount": 575, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "isExternal": false, "symbols": { "HobbitListView": "HobbitListView", "HobbitCreateView": "HobbitCreateView", "HobbitDetailView": "HobbitDetailView", "HobbitUpdateView": "HobbitUpdateView", "HobbitDeleteView": "HobbitDeleteView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "Blueprint": "Blueprint" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "app": "app" } } } }, "HobbitUpdateView": { "id": "HobbitUpdateView", "type": "class", "positions": [ { "start": { "index": 1193, "row": 46, "column": 0 }, "end": { "index": 1411, "row": 50, "column": 29 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 218, "codeCharacterCount": 186, "dependencyCount": 3, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/services/hobbits.py": { "id": "api/services/hobbits.py", "isExternal": false, "symbols": { "HobbitService": "HobbitService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request" } } }, "dependents": { "api/views/hobbits.py": { "id": "api/views/hobbits.py", "symbols": { "hobbits_bp": "hobbits_bp" } } } } } }, "api/wizards/data.py": { "id": "api/wizards/data.py", "filePath": "api/wizards/data.py", "metrics": { "linesCount": 11, "codeLineCount": 11, "characterCount": 316, "codeCharacterCount": 258, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": {}, "dependents": { "api/wizards/services.py": { "id": "api/wizards/services.py", "symbols": { "Wizard": "Wizard", "wizards": "wizards", "WizardService": "WizardService" } } }, "symbols": { "Wizard": { "id": "Wizard", "type": "class", "positions": [ { "start": { "index": 0, "row": 0, "column": 0 }, "end": { "index": 179, "row": 6, "column": 36 } } ], "description": "", "metrics": { "linesCount": 7, "codeLineCount": 6, "characterCount": 179, "codeCharacterCount": 141, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": {}, "dependents": { "api/wizards/services.py": { "id": "api/wizards/services.py", "symbols": { "WizardService": "WizardService" } } } }, "wizards": { "id": "wizards", "type": "variable", "positions": [ { "start": { "index": 182, "row": 9, "column": 0 }, "end": { "index": 315, "row": 13, "column": 1 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 133, "codeCharacterCount": 117, "dependencyCount": 0, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": {}, "dependents": { "api/wizards/services.py": { "id": "api/wizards/services.py", "symbols": { "WizardService": "WizardService" } } } } } }, "api/wizards/services.py": { "id": "api/wizards/services.py", "filePath": "api/wizards/services.py", "metrics": { "linesCount": 22, "codeLineCount": 22, "characterCount": 742, "codeCharacterCount": 559, "dependencyCount": 1, "dependentCount": 1, "cyclomaticComplexity": 5 }, "language": "python", "dependencies": { "api/wizards/data.py": { "id": "api/wizards/data.py", "isExternal": false, "symbols": { "Wizard": "Wizard", "wizards": "wizards" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "WizardService": "WizardService", "WizardListView": "WizardListView", "WizardCreateView": "WizardCreateView", "WizardDetailView": "WizardDetailView", "WizardUpdateView": "WizardUpdateView", "WizardDeleteView": "WizardDeleteView" } } }, "symbols": { "WizardService": { "id": "WizardService", "type": "class", "positions": [ { "start": { "index": 91, "row": 3, "column": 0 }, "end": { "index": 741, "row": 26, "column": 38 } } ], "description": "", "metrics": { "linesCount": 24, "codeLineCount": 20, "characterCount": 650, "codeCharacterCount": 471, "dependencyCount": 1, "dependentCount": 1, "cyclomaticComplexity": 5 }, "dependencies": { "api/wizards/data.py": { "id": "api/wizards/data.py", "isExternal": false, "symbols": { "Wizard": "Wizard", "wizards": "wizards" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "WizardListView": "WizardListView", "WizardCreateView": "WizardCreateView", "WizardDetailView": "WizardDetailView", "WizardUpdateView": "WizardUpdateView", "WizardDeleteView": "WizardDeleteView" } } } } } }, "api/wizards/views.py": { "id": "api/wizards/views.py", "filePath": "api/wizards/views.py", "metrics": { "linesCount": 49, "codeLineCount": 49, "characterCount": 2120, "codeCharacterCount": 1446, "dependencyCount": 3, "dependentCount": 2, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": { "api/wizards/services.py": { "id": "api/wizards/services.py", "isExternal": false, "symbols": { "WizardService": "WizardService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request", "Blueprint": "Blueprint" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp", "app": "app" } }, "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp" } } }, "symbols": { "get_wizzards_bp": { "id": "get_wizzards_bp", "type": "function", "positions": [ { "start": { "index": 984, "row": 36, "column": 0 }, "end": { "index": 2120, "row": 70, "column": 21 } } ], "description": "", "metrics": { "linesCount": 35, "codeLineCount": 24, "characterCount": 1136, "codeCharacterCount": 614, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/views.py": { "id": "api/wizards/views.py", "isExternal": false, "symbols": { "WizardListView": "WizardListView", "WizardCreateView": "WizardCreateView", "WizardDetailView": "WizardDetailView", "WizardUpdateView": "WizardUpdateView", "WizardDeleteView": "WizardDeleteView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "Blueprint": "Blueprint" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "app": "app" } } } }, "WizardCreateView": { "id": "WizardCreateView", "type": "class", "positions": [ { "start": { "index": 252, "row": 10, "column": 0 }, "end": { "index": 441, "row": 14, "column": 25 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 189, "codeCharacterCount": 157, "dependencyCount": 3, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/services.py": { "id": "api/wizards/services.py", "isExternal": false, "symbols": { "WizardService": "WizardService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp" } } } }, "WizardDeleteView": { "id": "WizardDeleteView", "type": "class", "positions": [ { "start": { "index": 827, "row": 30, "column": 0 }, "end": { "index": 981, "row": 33, "column": 19 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 154, "codeCharacterCount": 131, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/services.py": { "id": "api/wizards/services.py", "isExternal": false, "symbols": { "WizardService": "WizardService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp" } } } }, "WizardDetailView": { "id": "WizardDetailView", "type": "class", "positions": [ { "start": { "index": 444, "row": 17, "column": 0 }, "end": { "index": 603, "row": 20, "column": 21 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 159, "codeCharacterCount": 136, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/services.py": { "id": "api/wizards/services.py", "isExternal": false, "symbols": { "WizardService": "WizardService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp" } } } }, "WizardListView": { "id": "WizardListView", "type": "class", "positions": [ { "start": { "index": 109, "row": 4, "column": 0 }, "end": { "index": 249, "row": 7, "column": 22 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 140, "codeCharacterCount": 117, "dependencyCount": 2, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/services.py": { "id": "api/wizards/services.py", "isExternal": false, "symbols": { "WizardService": "WizardService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp" } } } }, "WizardUpdateView": { "id": "WizardUpdateView", "type": "class", "positions": [ { "start": { "index": 606, "row": 23, "column": 0 }, "end": { "index": 824, "row": 27, "column": 29 } } ], "description": "", "metrics": { "linesCount": 5, "codeLineCount": 5, "characterCount": 218, "codeCharacterCount": 186, "dependencyCount": 3, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/services.py": { "id": "api/wizards/services.py", "isExternal": false, "symbols": { "WizardService": "WizardService" } }, "flask.views": { "id": "flask.views", "isExternal": true, "symbols": { "MethodView": "MethodView" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "request": "request" } } }, "dependents": { "api/wizards/views.py": { "id": "api/wizards/views.py", "symbols": { "get_wizzards_bp": "get_wizzards_bp" } } } } } }, "app.py": { "id": "app.py", "filePath": "app.py", "metrics": { "linesCount": 17, "codeLineCount": 17, "characterCount": 698, "codeCharacterCount": 538, "dependencyCount": 4, "dependentCount": 1, "cyclomaticComplexity": 1 }, "language": "python", "dependencies": { "api/wizards/views.py": { "id": "api/wizards/views.py", "isExternal": false, "symbols": { "get_wizzards_bp": "get_wizzards_bp" } }, "api/views/elves.py": { "id": "api/views/elves.py", "isExternal": false, "symbols": { "elves_bp": "elves_bp" } }, "api/views/hobbits.py": { "id": "api/views/hobbits.py", "isExternal": false, "symbols": { "hobbits_bp": "hobbits_bp" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "Flask": "Flask" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "hello_world": "hello_world", "liveness": "liveness", "readiness": "readiness" } } }, "symbols": { "app": { "id": "app", "type": "variable", "positions": [ { "start": { "index": 150, "row": 5, "column": 0 }, "end": { "index": 171, "row": 5, "column": 21 } }, { "start": { "index": 451, "row": 23, "column": 0 }, "end": { "index": 519, "row": 23, "column": 68 } }, { "start": { "index": 548, "row": 26, "column": 0 }, "end": { "index": 605, "row": 26, "column": 57 } }, { "start": { "index": 636, "row": 29, "column": 0 }, "end": { "index": 697, "row": 29, "column": 61 } } ], "description": "", "metrics": { "linesCount": 4, "codeLineCount": 4, "characterCount": 207, "codeCharacterCount": 207, "dependencyCount": 4, "dependentCount": 1, "cyclomaticComplexity": 1 }, "dependencies": { "api/wizards/views.py": { "id": "api/wizards/views.py", "isExternal": false, "symbols": { "get_wizzards_bp": "get_wizzards_bp" } }, "api/views/elves.py": { "id": "api/views/elves.py", "isExternal": false, "symbols": { "elves_bp": "elves_bp" } }, "api/views/hobbits.py": { "id": "api/views/hobbits.py", "isExternal": false, "symbols": { "hobbits_bp": "hobbits_bp" } }, "flask": { "id": "flask", "isExternal": true, "symbols": { "Flask": "Flask" } } }, "dependents": { "app.py": { "id": "app.py", "symbols": { "hello_world": "hello_world", "liveness": "liveness", "readiness": "readiness" } } } }, "hello_world": { "id": "hello_world", "type": "function", "positions": [ { "start": { "index": 174, "row": 8, "column": 0 }, "end": { "index": 246, "row": 10, "column": 39 } } ], "description": "", "metrics": { "linesCount": 3, "codeLineCount": 3, "characterCount": 72, "codeCharacterCount": 66, "dependencyCount": 1, "dependentCount": 0, "cyclomaticComplexity": 1 }, "dependencies": { "app.py": { "id": "app.py", "isExternal": false, "symbols": { "app": "app" } } }, "dependents": {} }, "liveness": { "id": "liveness", "type": "function", "positions": [ { "start": { "index": 249, "row": 13, "column": 0 }, "end": { "index": 314, "row": 15, "column": 27 } } ], "description": "", "metrics": { "linesCount": 3, "codeLineCount": 3, "characterCount": 65, "codeCharacterCount": 59, "dependencyCount": 1, "dependentCount": 0, "cyclomaticComplexity": 1 }, "dependencies": { "app.py": { "id": "app.py", "isExternal": false, "symbols": { "app": "app" } } }, "dependents": {} }, "readiness": { "id": "readiness", "type": "function", "positions": [ { "start": { "index": 353, "row": 18, "column": 0 }, "end": { "index": 420, "row": 20, "column": 27 } } ], "description": "", "metrics": { "linesCount": 3, "codeLineCount": 3, "characterCount": 67, "codeCharacterCount": 61, "dependencyCount": 1, "dependentCount": 0, "cyclomaticComplexity": 1 }, "dependencies": { "app.py": { "id": "app.py", "isExternal": false, "symbols": { "app": "app" } } }, "dependents": {} } } } } } ================================================ FILE: examples/python/flask/.napirc ================================================ { "language": "python", "python": { "version": "3.12" }, "project": { "include": ["app.py", "api/**"], "exclude": [] }, "outDir": "napi-output", "audit": { "file": { "maxCodeChar": 5000, "maxCodeLine": 300, "maxDependency": 20, "maxCyclomaticComplexity": 50 }, "symbol": { "maxCodeChar": 1000, "maxCodeLine": 80, "maxDependency": 10, "maxCyclomaticComplexity": 15 } } } ================================================ FILE: examples/python/flask/api/data/elves.py ================================================ class Elf: def __init__(self, data): self.id = data.get("id") self.name = data.get("name") def update(self, data): self.name = data.get("name") elves = [ {"id": 1, "name": "Legolas"}, {"id": 2, "name": "Thranduil"}, {"id": 3, "name": "Galadriel"}, ] ================================================ FILE: examples/python/flask/api/data/hobbits.py ================================================ class Hobbit: def __init__(self, data): self.id = data.get("id") self.name = data.get("name") def update(self, data): self.name = data.get("name") hobbits = [ {"id": 1, "name": "Frodo Baggins"}, {"id": 2, "name": "Samwise Gamgee"}, {"id": 3, "name": "Meriadoc Brandybuck"}, ] ================================================ FILE: examples/python/flask/api/services/elves.py ================================================ from api.data.elves import Elf, elves class ElfService: def get_elves(self): return elves def get_elf(self, elf_id): for elf in elves: if elf.id == elf_id: return elf return None def create_elf(self, data): new_elf = Elf(data) elves.append(new_elf) return new_elf def update_elf(self, elf_id, data): elf: Elf = elves[elf_id] elf.update(data) return elf def delete_elf(self, elf_id): for elf in elves: if elf.id == elf_id: elves.remove(elf) ================================================ FILE: examples/python/flask/api/services/hobbits.py ================================================ from api.data.hobbits import Hobbit, hobbits class HobbitService: def get_hobbits(self): return hobbits def get_hobbit(self, hobbit_id): for hobbit in hobbits: if hobbit.id == hobbit_id: return hobbit return None def create_hobbit(self, data): new_hobbit = Hobbit(data) hobbits.append(new_hobbit) return new_hobbit def update_hobbit(self, hobbit_id, data): hobbit: Hobbit = hobbits[hobbit_id] hobbit.update(data) return hobbit def delete_hobbit(self, hobbit_id): for hobbit in hobbits: if hobbit.id == hobbit_id: hobbits.remove(hobbit) ================================================ FILE: examples/python/flask/api/views/elves.py ================================================ from flask.views import MethodView from flask import request, Blueprint from api.services.elves import ElfService elves_bp = Blueprint("elves", __name__) class ElfListView(MethodView): def get(self, *args, **kwargs): elves = ElfService().get_elves() return elves # @nanoapi method:GET path:/api/elves group:elf_read elves_bp.add_url_rule("/", view_func=ElfListView.as_view("elf_list"), methods=["GET"]) class ElfCreateView(MethodView): def post(self, *args, **kwargs): data = request.get_json() new_elf = ElfService().create_elf(data) return new_elf # @nanoapi method:POST path:/api/elves group:elf_write elves_bp.add_url_rule( "/", view_func=ElfCreateView.as_view("elf_create"), methods=["POST"] ) class ElfDetailView(MethodView): def get(self, elf_id, *args, **kwargs): elf = ElfService().get_elf(elf_id) return elf # @nanoapi method:GET path:/api/elves/ group:elf_read elves_bp.add_url_rule( "/", view_func=ElfDetailView.as_view("elf_detail"), methods=["GET"] ) class ElfUpdateView(MethodView): def put(self, elf_id, *args, **kwargs): data = request.get_json() updated_elf = ElfService().update_elf(elf_id, data) return updated_elf # @nanoapi method:PUT path:/api/elves/ group:elf_write elves_bp.add_url_rule( "/", view_func=ElfUpdateView.as_view("elf_update"), methods=["PUT"] ) class ElfDeleteView(MethodView): def delete(self, elf_id, *args, **kwargs): ElfService().delete_elf(elf_id) return None # @nanoapi method:DELETE path:/api/elves/ group:elf_write elves_bp.add_url_rule( "/", view_func=ElfDeleteView.as_view("elf_delete"), methods=["DELETE"] ) ================================================ FILE: examples/python/flask/api/views/hobbits.py ================================================ from flask.views import MethodView from flask import request, Blueprint from api.services.hobbits import HobbitService hobbits_bp = Blueprint("hobbits", __name__) class HobbitListView(MethodView): def get(self, *args, **kwargs): hobbits = HobbitService().get_hobbits() return hobbits # @nanoapi method:GET path:/api/hobbits group:hobbit_read hobbits_bp.add_url_rule( "/", view_func=HobbitListView.as_view("hobbit_list"), methods=["GET"] ) class HobbitCreateView(MethodView): def post(self, *args, **kwargs): data = request.get_json() new_hobbit = HobbitService().create_hobbit(data) return new_hobbit # @nanoapi method:POST path:/api/hobbits group:hobbit_write hobbits_bp.add_url_rule( "/", view_func=HobbitCreateView.as_view("hobbit_create"), methods=["POST"] ) class HobbitDetailView(MethodView): def get(self, hobbit_id, *args, **kwargs): hobbit = HobbitService().get_hobbit(hobbit_id) return hobbit # @nanoapi method:GET path:/api/hobbits/ group:hobbit_read hobbits_bp.add_url_rule( "/", view_func=HobbitDetailView.as_view("hobbit_detail"), methods=["GET"], ) class HobbitUpdateView(MethodView): def put(self, hobbit_id, *args, **kwargs): data = request.get_json() updated_hobbit = HobbitService().update_hobbit(hobbit_id, data) return updated_hobbit # @nanoapi method:PUT path:/api/hobbits/ group:hobbit_write hobbits_bp.add_url_rule( "/", view_func=HobbitUpdateView.as_view("hobbit_update"), methods=["PUT"], ) class HobbitDeleteView(MethodView): def delete(self, hobbit_id, *args, **kwargs): HobbitService().delete_hobbit(hobbit_id) return None # @nanoapi method:DELETE path:/api/hobbits/ group:hobbit_write hobbits_bp.add_url_rule( "/", view_func=HobbitDeleteView.as_view("hobbit_delete"), methods=["DELETE"], ) ================================================ FILE: examples/python/flask/api/wizards/data.py ================================================ class Wizard: def __init__(self, data): self.id = data.get("id") self.name = data.get("name") def update(self, data): self.name = data.get("name") wizards = [ {"id": 1, "name": "Harry Potter"}, {"id": 2, "name": "Hermione Granger"}, {"id": 3, "name": "Ron Weasley"}, ] ================================================ FILE: examples/python/flask/api/wizards/services.py ================================================ from api.data.hobbits import Hobbit, hobbits from api.wizards.data import Wizard, wizards class WizardService: def get_wizards(self): return wizards def get_wizard(self, wizard_id): for wizard in wizards: if wizard.id == wizard_id: return wizard return None def create_wizard(self, data): new_wizard = Wizard(data) wizards.append(new_wizard) return new_wizard def update_wizard(self, wizard_id, data): wizard: Wizard = wizards[wizard_id] wizard.update(data) return wizard def delete_wizard(self, wizard_id): for wizard in wizards: if wizard.id == wizard_id: wizards.remove(wizard) ================================================ FILE: examples/python/flask/api/wizards/views.py ================================================ from flask.views import MethodView from flask import request, Blueprint from .services import WizardService class WizardListView(MethodView): def get(self, *args, **kwargs): wizards = WizardService().get_wizards() return wizards class WizardCreateView(MethodView): def post(self, *args, **kwargs): data = request.get_json() new_wizard = WizardService().create_wizard(data) return new_wizard class WizardDetailView(MethodView): def get(self, wizard_id, *args, **kwargs): wizard = WizardService().get_wizard(wizard_id) return wizard class WizardUpdateView(MethodView): def put(self, wizard_id, *args, **kwargs): data = request.get_json() updated_wizard = WizardService().update_wizard(wizard_id, data) return updated_wizard class WizardDeleteView(MethodView): def delete(self, wizard_id, *args, **kwargs): WizardService().delete_wizard(wizard_id) return None def get_wizzards_bp(): wizards_bp = Blueprint("wizards", __name__) # @nanoapi method:GET path:/api/wizards group:wizard_read wizards_bp.add_url_rule( "/", view_func=WizardListView.as_view("wizard_list"), methods=["GET"] ) # @nanoapi method:POST path:/api/wizards group:wizard_write wizards_bp.add_url_rule( "/", view_func=WizardCreateView.as_view("wizard_create"), methods=["POST"] ) # @nanoapi method:GET path:/api/wizards/ group:wizard_read wizards_bp.add_url_rule( "/", view_func=WizardDetailView.as_view("wizard_detail"), methods=["GET"], ) # @nanoapi method:PUT path:/api/wizards/ group:wizard_write wizards_bp.add_url_rule( "/", view_func=WizardUpdateView.as_view("wizard_update"), methods=["PUT"], ) # @nanoapi method:DELETE path:/api/wizards/ group:wizard_write wizards_bp.add_url_rule( "/", view_func=WizardDeleteView.as_view("wizard_delete"), methods=["DELETE"], ) return wizards_bp ================================================ FILE: examples/python/flask/app.py ================================================ from flask import Flask from .api.wizards.views import get_wizzards_bp from api.views.elves import elves_bp from api.views.hobbits import hobbits_bp app = Flask(__name__) @app.get("/") def hello_world(): return {"message": "Hello, World!"} @app.get("/liveness") def liveness(): return {"status": "ok"} # @nanoapi method:GET path:readiness @app.get("/readiness") def readiness(): return {"status": "ok"} # @nanoapi path:/api/wizards app.register_blueprint(get_wizzards_bp(), url_prefix="/api/wizards") # @nanoapi path:/api/elves app.register_blueprint(elves_bp, url_prefix="/api/elves") # @nanoapi path:/api/hobbits app.register_blueprint(hobbits_bp, url_prefix="/api/hobbits") ================================================ FILE: examples/python/flask/pyproject.toml ================================================ [tool.poetry] name = "flask-example" version = "0.1.0" description = "" authors = ["florianbgt "] readme = "README.md" [tool.poetry.dependencies] python = "^3.12" flask = "^3.1.0" [tool.poetry.group.dev.dependencies] ruff = "^0.7.4" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" ================================================ FILE: install_scripts/install.ps1 ================================================ # PowerShell script to install NanoAPI CLI on Windows $ErrorActionPreference = "Stop" # Define platform-specific filename $FILENAME = "napi.exe" $RELEASE_URL = "https://github.com/nanoapi-io/napi/releases/latest/download/" $DOWNLOAD_URL = "$RELEASE_URL$FILENAME" # Define install path (add this path to your system PATH if needed) $INSTALL_DIR = "$env:ProgramFiles\NanoAPI" $INSTALL_PATH = Join-Path $INSTALL_DIR "napi.exe" $BACKUP_PATH = "$INSTALL_PATH.bak" # Create install directory if it doesn't exist if (-Not (Test-Path $INSTALL_DIR)) { New-Item -ItemType Directory -Path $INSTALL_DIR | Out-Null } # Backup existing binary if (Test-Path $INSTALL_PATH) { Write-Output "Existing version found, creating a backup..." Move-Item -Force -Path $INSTALL_PATH -Destination $BACKUP_PATH } else { Write-Output "No existing version found, proceeding with installation." } # Download the binary $TEMP_PATH = Join-Path $env:TEMP $FILENAME Write-Output "Downloading the latest version of $FILENAME..." Invoke-WebRequest -Uri $DOWNLOAD_URL -OutFile $TEMP_PATH # Install the binary Write-Output "Installing new version..." try { Move-Item -Force -Path $TEMP_PATH -Destination $INSTALL_PATH Write-Output "Installation/Update successful! You can now use your CLI by typing 'napi' if it's in your PATH." } catch { Write-Error "Update failed! Restoring the old version..." if (Test-Path $BACKUP_PATH) { Move-Item -Force -Path $BACKUP_PATH -Destination $INSTALL_PATH Write-Output "Restored the old version. Please try again later." } else { Write-Error "Backup not found. Manual intervention required." } } ================================================ FILE: install_scripts/install.sh ================================================ #!/bin/bash # Determine platform and release URL for GitHub OS=$(uname -s) RELEASE_URL="https://github.com/nanoapi-io/napi/releases/latest/download/" # Define the appropriate binary based on the OS if [ "$OS" == "Darwin" ]; then FILENAME="napi.macos" elif [ "$OS" == "Linux" ]; then FILENAME="napi.linux" else echo "Unsupported OS: $OS" exit 1 fi # Set the installation path INSTALL_PATH="/usr/local/bin/napi" BACKUP_PATH="$INSTALL_PATH.bak" # Check if the executable already exists if [ -f "$INSTALL_PATH" ]; then echo "Existing version found, creating a backup..." sudo mv "$INSTALL_PATH" "$BACKUP_PATH" else echo "No existing version found, proceeding with installation." fi # Download the binary echo "Downloading the latest version of $FILENAME..." curl -L "$RELEASE_URL$FILENAME" -o "$FILENAME" # Make it executable chmod +x "$FILENAME" # Attempt to install the new version echo "Installing new version..." sudo mv "$FILENAME" "$INSTALL_PATH" # Check if the installation was successful if [ $? -eq 0 ]; then echo "Installation/Update successful! You can now use your CLI by typing 'napi' in the terminal." else # If the installation failed, restore the backup echo "Update failed! Restoring the old version..." sudo mv "$BACKUP_PATH" "$INSTALL_PATH" echo "Restored the old version. Please try again later." fi ================================================ FILE: media/README.md ================================================ ## Media The content in this folder is meant to be used when creating content about NanoAPI. It includes images and other media that can be used to promote the project. ### Fonts The main font for the logo is Plus Jakarta Sans. It can be found on [Google Fonts](https://fonts.google.com/specimen/Plus+Jakarta+Sans). ### Colors The main colors for the project are: - Background: #0B0A32 - Secondary Background: #15143D - Surface: #3A397C - Secondary Surface: #25235C - Border: #3A397C - Text: #FFFFFF - Gray: #B4B4C9 - Primary: #5848E8 - Secondary: #D62B80 ### Mission NanoAPI is a tool that helps developers refactor their codebase into smaller, more manageable pieces at build time. It is designed to help developers quickly refactor large/monolith codebases into smaller, more manageable pieces at build time. It increases robustness by reducing downtime through isolating. It helps developers better understand what their codebase is doing today. It creates a new development paradigm (develop monolith, deploy microservice) for projects moving fast. We aim to drag developers kicking and screaming into more energy efficient software. ### Past PR - [Sustainable Software](https://www.linkedin.com/pulse/nanoapi-founders-sustainable-software-why-our-planet-m63be/) ### Contact - [info@nanoapi.io](mailto:info@nanoapi.io) ================================================ FILE: scripts/get_version.ts ================================================ import denoJson from "../deno.json" with { type: "json" }; const encoder = new TextEncoder(); const version = encoder.encode(denoJson.version); Deno.stdout.writeSync(version); Deno.exit(0); ================================================ FILE: src/cli/handlers/extract/index.ts ================================================ import type { Arguments } from "yargs-types"; import type { localConfigSchema } from "../../middlewares/napiConfig.ts"; import type { z } from "zod"; import { extractSymbols } from "../../../symbolExtractor/index.ts"; import { getExtensionsForLanguage, getFilesFromDirectory, writeFilesToDirectory, } from "../../../helpers/fileSystem/index.ts"; import { join } from "@std/path"; import { napiConfigMiddleware } from "../../middlewares/napiConfig.ts"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; import type { globalConfigSchema } from "../../middlewares/globalConfig.ts"; const NAPI_DIR = ".napi"; const MANIFESTS_DIR = "manifests"; interface ManifestEnvelope { id: string; branch: string; commitSha: string; commitShaDate: string; createdAt: string; manifest: DependencyManifest; } function getLatestManifestId(workdir: string): string | null { const manifestsDir = join(workdir, NAPI_DIR, MANIFESTS_DIR); try { const entries: { name: string }[] = []; for (const entry of Deno.readDirSync(manifestsDir)) { if (entry.isFile && entry.name.endsWith(".json")) { entries.push(entry); } } if (entries.length === 0) return null; entries.sort((a, b) => b.name.localeCompare(a.name)); return entries[0].name.replace(".json", ""); } catch { return null; } } function loadManifest(workdir: string, manifestId: string): ManifestEnvelope { const manifestPath = join( workdir, NAPI_DIR, MANIFESTS_DIR, `${manifestId}.json`, ); const content = Deno.readTextFileSync(manifestPath); return JSON.parse(content) as ManifestEnvelope; } function builderFunction( yargs: Arguments & { globalConfig: z.infer; }, ) { return yargs .middleware(napiConfigMiddleware) .option("symbol", { type: "array" as const, description: "Symbols to extract (format: file|symbol where file is absolute path from .napirc and symbol is the symbol name)", string: true, requiresArg: true, demandOption: true, }) .option("manifestId", { type: "string", description: "The manifest ID to use for the extraction (defaults to latest)", requiresArg: true, }) .check( ( argv: Arguments & { globalConfig: z.infer; } & { symbol: string[]; }, ) => { const symbols = argv.symbol; if (!symbols || symbols.length === 0) { throw new Error("At least one symbol must be specified"); } for (const symbolSpec of symbols) { const splitSymbol = symbolSpec.split("|"); if (splitSymbol.length !== 2) { throw new Error( `Invalid symbol format: "${symbolSpec}". Expected format: file|symbol (e.g., "src/main.py|myFunction")`, ); } } return true; }, ); } function handler( argv: Arguments & { globalConfig: z.infer; } & { symbol: string[]; manifestId?: string; }, ) { const napiConfig = argv.napiConfig as z.infer; const start = Date.now(); console.info("🎯 Starting symbol extraction..."); try { let manifestId = argv.manifestId; if (!manifestId) { manifestId = getLatestManifestId(argv.workdir) ?? undefined; if (!manifestId) { console.error("❌ No manifests found in .napi/manifests/"); console.error(" Run 'napi generate' first to create a manifest."); Deno.exit(1); } console.info(`📄 Using latest manifest: ${manifestId}`); } else { console.info(`📄 Using manifest: ${manifestId}`); } let envelope: ManifestEnvelope; try { envelope = loadManifest(argv.workdir, manifestId); } catch (error) { console.error(`❌ Failed to load manifest: ${manifestId}`); if (error instanceof Deno.errors.NotFound) { console.error( ` File not found: .napi/manifests/${manifestId}.json`, ); } else { console.error( ` Error: ${error instanceof Error ? error.message : String(error)}`, ); } Deno.exit(1); } const dependencyManifest = envelope.manifest; console.info("🔍 Validating symbol specifications..."); const symbolsToExtract = new Map< string, { filePath: string; symbols: Set } >(); for (const symbolSpec of argv.symbol) { const [filePath, symbolName] = symbolSpec.split("|", 2); if (!dependencyManifest[filePath]) { console.warn(`⚠️ File not found in manifest: ${filePath}`); console.warn( " Make sure the file is included in your project configuration", ); } const existingEntry = symbolsToExtract.get(filePath) || { filePath, symbols: new Set(), }; existingEntry.symbols.add(symbolName); symbolsToExtract.set(filePath, existingEntry); } console.info( `📊 Extracting ${argv.symbol.length} symbols from ${symbolsToExtract.size} files:`, ); for (const [filePath, { symbols }] of symbolsToExtract) { console.info(` • ${filePath}: ${Array.from(symbols).join(", ")}`); } console.info("🔧 Scanning project files..."); const fileExtensions = getExtensionsForLanguage(napiConfig.language); const files = getFilesFromDirectory(argv.workdir, { includes: napiConfig.project.include, excludes: napiConfig.project.exclude, extensions: fileExtensions, logMessages: false, }); console.info(`📁 Found ${files.size} files to process`); console.info("⚙️ Extracting symbols..."); const extractedFiles = extractSymbols( files, dependencyManifest, symbolsToExtract, napiConfig, ); const unixTimestamp = Date.now(); const outputDir = join( argv.workdir, napiConfig.outDir, `extracted-${unixTimestamp}`, ); console.info(`💾 Writing extracted symbols to: ${outputDir}`); writeFilesToDirectory(extractedFiles, outputDir); const duration = Date.now() - start; console.info( `✅ Symbol extraction completed successfully in ${duration}ms`, ); console.info(`📄 Extracted files:`); console.info(` • ${extractedFiles.size} files generated`); console.info(` • Output directory: ${outputDir}`); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); console.error("❌ Symbol extraction failed"); console.error(` Error: ${errorMessage}`); console.error(""); console.error("💡 Common solutions:"); console.error( " • Check your symbol specifications (format: file|symbol)", ); console.error( " • Ensure the manifest is up to date: napi generate", ); console.error( " • Verify the specified files contain the requested symbols", ); Deno.exit(1); } } export default { command: "extract", describe: "extract symbols from your program", builder: builderFunction, handler, }; ================================================ FILE: src/cli/handlers/generate/index.ts ================================================ import type { Arguments } from "yargs-types"; import { type localConfigSchema, napiConfigMiddleware, } from "../../middlewares/napiConfig.ts"; import { getExtensionsForLanguage, getFilesFromDirectory, } from "../../../helpers/fileSystem/index.ts"; import { generateDependencyManifest, } from "../../../manifest/dependencyManifest/index.ts"; import type { z } from "zod"; import type { globalConfigSchema } from "../../middlewares/globalConfig.ts"; import { join } from "@std/path"; const NAPI_DIR = ".napi"; const MANIFESTS_DIR = "manifests"; function builder( yargs: Arguments & { globalConfig: z.infer; }, ) { return yargs .middleware(napiConfigMiddleware) .option("branch", { type: "string", description: "The branch to use for the manifest", }).option("commit-sha", { type: "string", description: "The commit SHA to use for the manifest", }).option("commit-sha-date", { type: "string", description: "The commit SHA date to use for the manifest", coerce: (value: string) => { const date = new Date(value); if (isNaN(date.getTime())) { throw new Error( "Invalid date format for commit-sha-date. Please use ISO 8601 format (e.g. 2024-01-01T00:00:00Z)", ); } return value; }, }).option("labelingApiKey", { type: "string", description: "The API key to use for the labeling", }); } async function getGitBranch(workDir: string): Promise { try { const command = new Deno.Command("git", { args: ["branch", "--show-current"], cwd: workDir, stdout: "piped", stderr: "piped", }); const { code, stdout } = await command.output(); if (code === 0) { const branch = new TextDecoder().decode(stdout).trim(); return branch || "main"; } return "main"; } catch { return "main"; } } async function getGitCommitSha(workDir: string): Promise { try { const command = new Deno.Command("git", { args: ["rev-parse", "HEAD"], cwd: workDir, stdout: "piped", stderr: "piped", }); const { code, stdout } = await command.output(); if (code === 0) { return new TextDecoder().decode(stdout).trim(); } return ""; } catch { return ""; } } async function getGitCommitDate(workDir: string): Promise { try { const command = new Deno.Command("git", { args: ["log", "-1", "--format=%cI"], cwd: workDir, stdout: "piped", stderr: "piped", }); const { code, stdout } = await command.output(); if (code === 0) { return new TextDecoder().decode(stdout).trim(); } return new Date().toISOString(); } catch { return new Date().toISOString(); } } function ensureManifestsDir(workdir: string): string { const manifestsDir = join(workdir, NAPI_DIR, MANIFESTS_DIR); try { Deno.mkdirSync(manifestsDir, { recursive: true }); } catch (error) { if (!(error instanceof Deno.errors.AlreadyExists)) { throw error; } } return manifestsDir; } async function handler( argv: Arguments & { globalConfig: z.infer; } & { branch?: string; commitSha?: string; commitShaDate?: string; labelingApiKey?: string; }, ) { const napiConfig = argv.napiConfig as z.infer; const globalConfig = argv.globalConfig as z.infer; let branch: string; let commitSha: string; let commitShaDate: string; if (argv.branch) { branch = argv.branch; console.info(`🌿 Using provided branch: ${branch}`); } else { console.info("🔍 Detecting Git branch..."); const detectedBranch = await getGitBranch(argv.workdir); const userBranch = prompt( `Enter branch name or leave empty:`, detectedBranch, ); branch = userBranch || detectedBranch; console.info(`🌿 Branch: ${branch}`); } if (argv.commitSha) { commitSha = argv.commitSha; console.info(`📝 Using provided commit: ${commitSha.substring(0, 8)}...`); } else { console.info("🔍 Detecting Git commit SHA..."); const detectedCommitSha = await getGitCommitSha(argv.workdir); const userCommitSha = prompt( `Enter commit SHA or leave empty:`, detectedCommitSha, ); commitSha = userCommitSha || detectedCommitSha; if (commitSha) { console.info(`📝 Commit SHA: ${commitSha.substring(0, 8)}...`); } } if (argv.commitShaDate) { commitShaDate = argv.commitShaDate; console.info(`📅 Using provided commit date: ${commitShaDate}`); } else { console.info("🔍 Detecting Git commit date..."); const detectedCommitShaDate = await getGitCommitDate(argv.workdir); const userCommitShaDate = prompt( `Enter commit date or leave empty:`, detectedCommitShaDate, ); commitShaDate = userCommitShaDate || detectedCommitShaDate; console.info(`📅 Commit date: ${commitShaDate}`); } const start = Date.now(); console.info("🔧 Generating dependency manifest..."); try { console.info(`📝 Language: ${napiConfig.language}`); console.info(`📁 Working directory: ${argv.workdir}`); const fileExtensions = getExtensionsForLanguage(napiConfig.language); console.info( `🔍 Looking for files with extensions: ${fileExtensions.join(", ")}`, ); const files = getFilesFromDirectory(argv.workdir, { includes: napiConfig.project.include, excludes: napiConfig.project.exclude, extensions: fileExtensions, logMessages: true, }); if (files.size === 0) { console.warn("⚠️ No files found matching your project configuration"); console.warn(" Check your include/exclude patterns in .napirc"); console.warn(""); console.warn("💡 Current patterns:"); console.warn(` Include: ${napiConfig.project.include.join(", ")}`); if (napiConfig.project.exclude) { console.warn(` Exclude: ${napiConfig.project.exclude.join(", ")}`); } Deno.exit(1); } console.info(`📊 Processing ${files.size} files...`); const dependencyManifest = await generateDependencyManifest( files, napiConfig, globalConfig, argv.labelingApiKey, ); const manifestsDir = ensureManifestsDir(argv.workdir); const timestamp = Date.now(); const shortSha = commitSha ? commitSha.substring(0, 7) : "unknown"; const manifestId = `${timestamp}-${shortSha}`; const manifestFileName = `${manifestId}.json`; const manifestEnvelope = { id: manifestId, branch, commitSha, commitShaDate, createdAt: new Date().toISOString(), manifest: dependencyManifest, }; const manifestPath = join(manifestsDir, manifestFileName); Deno.writeTextFileSync( manifestPath, JSON.stringify(manifestEnvelope, null, 2), ); const duration = Date.now() - start; console.info( `✅ Manifest saved successfully in ${duration}ms`, ); console.info(`📄 Generated manifest contains:`); console.info(` • ${Object.keys(dependencyManifest).length} files`); console.info(` • Dependencies and relationships mapped`); console.info(`\n💾 Saved to: ${manifestPath}`); console.info(`🔍 View it with: napi view`); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); console.error("❌ Failed to generate manifest"); console.error(` Error: ${errorMessage}`); console.error(""); console.error("💡 Common solutions:"); console.error(" • Check that your project files are accessible"); console.error(" • Verify your .napirc configuration"); Deno.exit(1); } } export default { command: "generate", describe: "generate a dependency manifest for your program", builder, handler, }; ================================================ FILE: src/cli/handlers/init/index.ts ================================================ import type { Arguments } from "yargs-types"; import { createConfig, getConfigFromWorkDir, } from "../../middlewares/napiConfig.ts"; import { join, normalize, relative, SEPARATOR } from "@std/path"; import type z from "zod"; import type { localConfigSchema } from "../../middlewares/napiConfig.ts"; import pythonStdlibList from "../../../scripts/generate_python_stdlib_list/output.json" with { type: "json", }; import { confirm, input, search, select } from "@inquirer/prompts"; import { globSync } from "glob"; import { cLanguage, csharpLanguage, javaLanguage, pythonLanguage, } from "../../../helpers/treeSitter/parsers.ts"; import type { globalConfigSchema } from "../../middlewares/globalConfig.ts"; import { ANTHROPIC_PROVIDER, GOOGLE_PROVIDER, OPENAI_PROVIDER, } from "../../../manifest/dependencyManifest/labeling/model.ts"; import { defaultAuditConfig } from "../../../manifest/auditManifest/types.ts"; function builder( yargs: Arguments & { globalConfig: z.infer; }, ) { return yargs; } async function handler( argv: Arguments & { globalConfig: z.infer; }, ) { try { try { if (getConfigFromWorkDir(argv.workdir)) { const confirmOverwrite = await confirm({ message: `⚠️ A .napirc file already exists in the selected directory. Do you want to overwrite it?`, default: false, }); if (!confirmOverwrite) { console.info("✅ Keeping existing configuration"); Deno.exit(0); } console.info("🔄 Proceeding with configuration overwrite"); } } catch { console.info( "📝 No existing valid configuration found, creating new one", ); } console.info("\n🔧 Starting interactive configuration..."); const napiConfig = await generateConfig(argv.workdir); console.info("\n📋 Generated configuration:"); console.info("─".repeat(50)); console.info(JSON.stringify(napiConfig, null, 2)); console.info("─".repeat(50)); const confirmSave = await confirm({ message: "Do you want to save this configuration?", default: true, }); if (confirmSave) { createConfig(napiConfig, argv.workdir); console.info("\n✅ Configuration saved successfully!"); console.info(`📄 Created: ${argv.workdir}${SEPARATOR}.napirc`); console.info("🎉 Your NanoAPI project is ready!"); console.info("\n💡 Next steps:"); console.info(" 1. Run: napi generate"); console.info(" 2. Run: napi view"); } else { console.info("❌ Configuration not saved"); console.info(" Run 'napi init' again when you're ready to configure"); Deno.exit(0); } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); console.error("❌ Initialization failed"); console.error(` Error: ${errorMessage}`); console.error("\n💡 Common solutions:"); console.error(" • Check that you have write permissions in the directory"); console.error(" • Ensure the directory exists and is accessible"); console.error(" • Try running the command again"); Deno.exit(1); } } export default { command: "init", describe: "Initialize a NanoAPI project with interactive configuration", builder, handler, }; function showMatchingFiles( workDir: string, pattern: string, maxFilesToShow = 10, ) { try { const files = globSync(pattern, { cwd: workDir, nodir: true, }); if (files.length === 0) { console.info(`No files match the pattern '${pattern}'`); return; } const totalMatches = files.length; const filesToShow = files.slice(0, maxFilesToShow); console.info(`\nPattern '${pattern}' matches ${totalMatches} file(s):`); filesToShow.forEach((file: string) => console.info(`- ${file}`)); if (totalMatches > maxFilesToShow) { console.info(`... and ${totalMatches - maxFilesToShow} more`); } } catch (error) { console.info(`Error previewing files for pattern '${pattern}': ${error}`); } } function showFinalFileSelection( workDir: string, includePatterns: string[], excludePatterns: string[], maxFilesToShow = 20, ) { try { const files = globSync(includePatterns, { cwd: workDir, nodir: true, ignore: excludePatterns, }); console.info("\n🔍 FINAL FILE SELECTION"); console.info( `After applying all patterns, ${files.length} files will be processed:`, ); const filesToShow = files.slice(0, maxFilesToShow); filesToShow.forEach((file: string) => console.info(`- ${file}`)); if (files.length > maxFilesToShow) { console.info(`... and ${files.length - maxFilesToShow} more`); } if (files.length === 0) { console.warn( "\n⚠️ WARNING: No files match your include/exclude patterns. Please review your configuration.", ); } } catch (error) { console.info(`Error showing final file selection: ${error}`); } } function getProjectStructureOverview(workDir: string): string[] { const overview: string[] = []; try { const traverseDirectory = (dir: string, depth: number) => { const entries = Deno.readDirSync(dir); for (const entry of entries) { const fullPath = join(dir, entry.name); const relativePath = relative(workDir, fullPath); try { const stat = Deno.statSync(fullPath); const isDirectory = stat.isDirectory; const indentation = " ".repeat(depth); const icon = isDirectory ? "📂" : "📄"; const line = `${indentation}${icon} ${relativePath}`; overview.push(line); if (isDirectory && depth < 2) { traverseDirectory(fullPath, depth + 1); } } catch (error) { console.info(`Could not access ${fullPath}: ${error}`); } } }; traverseDirectory(workDir, 0); } catch (error) { console.info(`Error getting project structure: ${error}`); } return overview; } async function collectIncludePatterns( workDir: string, language: string, ): Promise { const projectStructure = getProjectStructureOverview(workDir); console.info( ` Include patterns define which files NanoAPI will process and analyze. Examples: - '**${SEPARATOR}*.py' for all Python files - 'src${SEPARATOR}**' for all files in src directory - '*.py' for all Python files in the root directory `, ); const suggestedIncludes = suggestIncludePatterns(projectStructure, language); console.info("\nSuggested include patterns (based on project structure):"); suggestedIncludes.forEach((pattern) => console.info(`- ${pattern}`)); console.info("\nPreview of files that would be included:"); for (const pattern of suggestedIncludes) { showMatchingFiles(workDir, pattern); } const useSuggested = await confirm({ message: "Do you want to use the suggested include patterns?", default: true, }); if (useSuggested) { console.info("Using suggested include patterns."); return suggestedIncludes; } console.info( "Please enter the glob patterns for files to include (one per line):", ); let includePatterns: string[] = []; let continueAdding = true; let validSelection = false; while (!validSelection) { includePatterns = []; continueAdding = true; while (continueAdding) { const pattern = await input({ message: `Enter glob pattern (e.g., '**${SEPARATOR}*.py', 'src${SEPARATOR}**')`, validate: (value) => { if (!value.trim()) return "Pattern cannot be empty"; try { new RegExp(value.replace(/\*\*/g, "*").replace(/\*/g, ".*")); console.info(`\nPreviewing files matching '${value}':`); const files = globSync(value, { cwd: workDir, nodir: true, }); if (files.length === 0) { return `No files match the pattern '${value}'. Please check and try again.`; } const totalMatches = files.length; const filesToShow = files.slice(0, 5); filesToShow.forEach((file: string) => console.info(`- ${file}`)); if (totalMatches > 5) { console.info(`... and ${totalMatches - 5} more`); } return true; } catch { return "Invalid pattern"; } }, }); includePatterns.push(pattern); continueAdding = await confirm({ message: "Do you want to add another include pattern?", default: false, }); } if (includePatterns.length === 0) { console.info("No patterns provided, using default '**' (all files)"); includePatterns = ["**"]; } console.info("\nSelected include patterns:"); includePatterns.forEach((pattern) => console.info(`- ${pattern}`)); showFinalFileSelection(workDir, includePatterns, []); validSelection = await confirm({ message: "Are you satisfied with this file selection?", default: true, }); if (!validSelection) { console.info("\nLet's try again with different include patterns."); } } return includePatterns; } async function collectExcludePatterns( workDir: string, includePatterns: string[], language: string, outDir: string, ): Promise { console.info("\n❌ Specifying files to exclude from your project"); console.info( ` Exclude patterns define which files NanoAPI will ignore during processing. Examples: - 'node_modules${SEPARATOR}**' to exclude all node modules - '**${SEPARATOR}*.test.js' to exclude all JavaScript test files - '.git${SEPARATOR}**' to exclude git directory `, ); const suggestedExcludes = suggestExcludePatterns( includePatterns, language, outDir, ); console.info("\nSuggested exclude patterns (based on included files):"); suggestedExcludes.forEach((pattern) => console.info(`- ${pattern}`)); console.info("\nPreview of files that would be excluded:"); for (const pattern of suggestedExcludes) { showMatchingFiles(workDir, pattern); } const useSuggested = await confirm({ message: "Do you want to use the suggested exclude patterns?", default: true, }); if (useSuggested) { console.info("Using suggested exclude patterns."); return suggestedExcludes; } console.info( "Please enter the glob patterns for files to exclude (one per line):", ); let excludePatterns: string[] = []; let continueAdding = true; let validSelection = false; while (!validSelection) { excludePatterns = []; continueAdding = true; while (continueAdding) { const pattern = await input({ message: `Enter glob pattern (e.g., 'node_modules${SEPARATOR}**', '**${SEPARATOR}*.test.js')`, validate: (value) => { if (!value.trim()) return "Pattern cannot be empty"; try { new RegExp(value.replace(/\*\*/g, "*").replace(/\*/g, ".*")); console.info(`\nPreviewing files matching '${value}':`); const files = globSync(value, { cwd: workDir, nodir: true, }); if (files.length === 0) { console.info( `Note: No files currently match the pattern '${value}'`, ); return true; } const totalMatches = files.length; const filesToShow = files.slice(0, 5); filesToShow.forEach((file: string) => console.info(`- ${file}`)); if (totalMatches > 5) { console.info(`... and ${totalMatches - 5} more`); } return true; } catch { return "Invalid pattern"; } }, }); excludePatterns.push(pattern); continueAdding = await confirm({ message: "Do you want to add another exclude pattern?", default: false, }); } console.info("\nSelected exclude patterns:"); excludePatterns.forEach((pattern) => console.info(`- ${pattern}`)); showFinalFileSelection(workDir, includePatterns, excludePatterns); validSelection = await confirm({ message: "Are you satisfied with this file selection?", default: true, }); if (!validSelection) { console.info("\nLet's try again with different exclude patterns."); } } return excludePatterns; } function suggestIncludePatterns( projectStructure: string[], language: string, ): string[] { const suggestions: string[] = []; if (language === pythonLanguage) { if ( projectStructure.some((entry) => entry.includes(`📂 src${SEPARATOR}`)) ) { suggestions.push(`src${SEPARATOR}**${SEPARATOR}*.py`); } if ( projectStructure.some((entry) => entry.includes(`📂 lib${SEPARATOR}`)) ) { suggestions.push(`lib${SEPARATOR}**${SEPARATOR}*.py`); } if (suggestions.length === 0) { if ( projectStructure.some((entry) => entry.includes(`📂 app${SEPARATOR}`)) ) { suggestions.push(`app${SEPARATOR}**${SEPARATOR}*.py`); } } if (suggestions.length === 0) { suggestions.push(`**${SEPARATOR}*.py`); } } else if (language === csharpLanguage) { if ( projectStructure.some((entry) => entry.includes(`📂 src${SEPARATOR}`)) ) { suggestions.push(`src${SEPARATOR}**${SEPARATOR}*.cs`); } if ( projectStructure.some((entry) => entry.includes(`📂 lib${SEPARATOR}`)) ) { suggestions.push(`lib${SEPARATOR}**${SEPARATOR}*.cs`); } if (suggestions.length === 0) { if ( projectStructure.some((entry) => entry.includes(`📂 Controllers${SEPARATOR}`) ) ) { suggestions.push(`Controllers${SEPARATOR}**${SEPARATOR}*.cs`); } if ( projectStructure.some((entry) => entry.includes(`📂 Models${SEPARATOR}`) ) ) { suggestions.push(`Models${SEPARATOR}**${SEPARATOR}*.cs`); } if ( projectStructure.some((entry) => entry.includes(`📂 Services${SEPARATOR}`) ) ) { suggestions.push(`Services${SEPARATOR}**${SEPARATOR}*.cs`); } } if (suggestions.length === 0) { suggestions.push(`**${SEPARATOR}*.cs`); } } else if (language === cLanguage) { if ( projectStructure.some((entry) => entry.includes(`📂 src${SEPARATOR}`)) ) { suggestions.push(`src${SEPARATOR}**${SEPARATOR}*.c`); suggestions.push(`src${SEPARATOR}**${SEPARATOR}*.h`); } if ( projectStructure.some((entry) => entry.includes(`📂 lib${SEPARATOR}`)) ) { suggestions.push(`lib${SEPARATOR}**${SEPARATOR}*.c`); suggestions.push(`lib${SEPARATOR}**${SEPARATOR}*.h`); } if (suggestions.length === 0) { if ( projectStructure.some((entry) => entry.includes(`📂 include${SEPARATOR}`) ) ) { suggestions.push(`include${SEPARATOR}**${SEPARATOR}*.c`); suggestions.push(`include${SEPARATOR}**${SEPARATOR}*.h`); } } if (suggestions.length === 0) { suggestions.push(`**${SEPARATOR}*.c`); suggestions.push(`**${SEPARATOR}*.h`); } } else if (language === javaLanguage) { if ( projectStructure.some((entry) => entry.includes(`📂 src${SEPARATOR}`)) ) { suggestions.push(`src${SEPARATOR}**${SEPARATOR}*.java`); } if ( projectStructure.some((entry) => entry.includes(`📂 lib${SEPARATOR}`)) ) { suggestions.push(`lib${SEPARATOR}**${SEPARATOR}*.java`); } if (suggestions.length === 0) { if ( projectStructure.some((entry) => entry.includes(`📂 buildSrc${SEPARATOR}`) ) ) { suggestions.push(`buildSrc${SEPARATOR}**${SEPARATOR}*.java`); } if ( projectStructure.some((entry) => entry.includes(`📂 app${SEPARATOR}`)) ) { suggestions.push(`app${SEPARATOR}**${SEPARATOR}*.java`); } if ( projectStructure.some((entry) => entry.includes(`📂 core${SEPARATOR}`)) ) { suggestions.push(`core${SEPARATOR}**${SEPARATOR}*.java`); } if ( projectStructure.some((entry) => entry.includes(`📂 util${SEPARATOR}`)) ) { suggestions.push(`util${SEPARATOR}**${SEPARATOR}*.java`); } if ( projectStructure.some((entry) => entry.includes(`📂 libs${SEPARATOR}`)) ) { suggestions.push(`libs${SEPARATOR}**${SEPARATOR}*.java`); } } if (suggestions.length === 0) { suggestions.push(`**${SEPARATOR}*.java`); } } return suggestions; } function suggestExcludePatterns( _includePatterns: string[], language: string, outDir: string, ): string[] { const suggestions: string[] = []; suggestions.push(`${outDir}${SEPARATOR}**`); suggestions.push(`.napi${SEPARATOR}**`); suggestions.push(`.git${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}dist${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}build${SEPARATOR}**`); if (language === pythonLanguage) { suggestions.push(`**${SEPARATOR}__pycache__${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}*.pyc`); suggestions.push(`**${SEPARATOR}.pytest_cache${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}venv${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.env${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}*.egg-info${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.tox${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.coverage`); suggestions.push(`**${SEPARATOR}htmlcov${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.mypy_cache${SEPARATOR}**`); } else if (language === csharpLanguage) { suggestions.push(`**${SEPARATOR}bin${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}obj${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}packages${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.vs${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}TestResults${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}*.user`); suggestions.push(`**${SEPARATOR}*.suo`); suggestions.push(`**${SEPARATOR}.nuget${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}artifacts${SEPARATOR}**`); } else if (language === javaLanguage) { suggestions.push(`**${SEPARATOR}bin${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}obj${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.bin${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.obj${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}target${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.mvn${SEPARATOR}**`); suggestions.push(`**${SEPARATOR}.svn${SEPARATOR}**`); } return suggestions; } export async function generateConfig( workDir: string, ): Promise> { const language = await select({ message: "Select the language of your project", choices: [ { name: "Python", value: pythonLanguage }, { name: "C#", value: csharpLanguage }, { name: "C", value: cLanguage }, { name: "Java", value: javaLanguage }, ], }); let pythonConfig: z.infer["python"] | undefined = undefined; if (language === pythonLanguage) { const supportedVersions = Object.keys(pythonStdlibList); const pythonVersion = await search({ message: "Enter or search for your Python version (e.g., 3.9)", source: (term) => { if (!term) return supportedVersions; return supportedVersions.filter((version) => version.includes(term)); }, }); if (pythonVersion) { pythonConfig = { version: pythonVersion, }; } } let cConfig: z.infer["c"] | undefined = undefined; if (language === cLanguage) { const hasIncludeDirs = await confirm({ message: "Does your project have include directories for headers?", }); if (hasIncludeDirs) { const includeDirsInput = await input({ message: "Enter the include directories, separated by commas:", validate: (value) => { if (!value.trim()) return "Include directories cannot be empty"; return true; }, }); const includeDirs = includeDirsInput.split(",").map((dir) => dir.trim()); if (includeDirs.length > 0) { cConfig = { includedirs: includeDirs, }; } } } const outDir = await input({ message: "Enter the output directory for NanoAPI artifacts", default: "napi_out", validate: (value) => { if (!value.trim()) return "Output directory cannot be empty"; try { const normalizedPath = normalize(join(workDir, value)); if (!normalizedPath.startsWith(normalize(workDir))) { return "Output directory must be within the project directory"; } try { const stat = Deno.statSync(normalizedPath); if (stat && !stat.isDirectory) { return "A file with this name already exists. Please choose a different name"; } } catch (_error) { // Path doesn't exist yet, which is fine } return true; } catch (error) { if (error instanceof Error) { return `Invalid directory name: ${error.message}`; } return "Invalid directory name"; } }, }); console.info("\n🔍 ANALYZING PROJECT STRUCTURE..."); const includePatterns = await collectIncludePatterns(workDir, language); const excludePatterns = await collectExcludePatterns( workDir, includePatterns, language, outDir, ); showFinalFileSelection(workDir, includePatterns, excludePatterns); console.info("\n🏷️ LABELING CONFIGURATION"); console.info( "Labeling helps categorize and organize your code dependencies using AI models.", ); const enableLabeling = await confirm({ message: "Would you like to enable AI-powered labeling?", default: false, }); let labelingConfig: | z.infer["labeling"] | undefined = undefined; if (enableLabeling) { console.info("\n🤖 AI MODEL SELECTION"); console.info( "Choose an AI provider for labeling your dependencies:", ); const modelProvider = await select({ message: "Select AI model provider:", choices: [ { name: "OpenAI (GPT-4o-mini)", value: OPENAI_PROVIDER }, { name: "Google (Gemini 2.5 Flash)", value: GOOGLE_PROVIDER }, { name: "Anthropic (Claude 3.5 Sonnet)", value: ANTHROPIC_PROVIDER }, ], }) as | typeof OPENAI_PROVIDER | typeof GOOGLE_PROVIDER | typeof ANTHROPIC_PROVIDER; const maxConcurrency = await input({ message: "Enter maximum concurrent requests (leave empty for unlimited):", validate: (value) => { if (!value.trim()) return true; const num = parseInt(value); if (isNaN(num) || num <= 0) { return "Please enter a positive number or leave empty for unlimited"; } return true; }, }); labelingConfig = { modelProvider, maxConcurrency: maxConcurrency.trim() ? parseInt(maxConcurrency) : undefined, }; console.info("✅ Labeling configuration added"); } console.info("\n📊 AUDIT THRESHOLDS"); console.info( "Audit thresholds flag files and symbols that exceed size or complexity limits.", ); const enableAudit = await confirm({ message: "Would you like to configure custom audit thresholds?", default: false, }); let auditConfig: | z.infer["audit"] | undefined = undefined; if (enableAudit) { console.info( "\n📄 File-level thresholds (defaults shown, press Enter to keep):", ); const fileMaxCodeLine = await input({ message: `Max code lines per file [${defaultAuditConfig.file.maxCodeLine}]:`, }); const fileMaxCodeChar = await input({ message: `Max code characters per file [${defaultAuditConfig.file.maxCodeChar}]:`, }); const fileMaxDependency = await input({ message: `Max dependencies per file [${defaultAuditConfig.file.maxDependency}]:`, }); const fileMaxDependent = await input({ message: `Max dependents per file [${defaultAuditConfig.file.maxDependent}]:`, }); const fileMaxCyclomaticComplexity = await input({ message: `Max cyclomatic complexity per file [${defaultAuditConfig.file.maxCyclomaticComplexity}]:`, }); console.info( "\n🔤 Symbol-level thresholds (defaults shown, press Enter to keep):", ); const symbolMaxCodeLine = await input({ message: `Max code lines per symbol [${defaultAuditConfig.symbol.maxCodeLine}]:`, }); const symbolMaxCodeChar = await input({ message: `Max code characters per symbol [${defaultAuditConfig.symbol.maxCodeChar}]:`, }); const symbolMaxDependency = await input({ message: `Max dependencies per symbol [${defaultAuditConfig.symbol.maxDependency}]:`, }); const symbolMaxDependent = await input({ message: `Max dependents per symbol [${defaultAuditConfig.symbol.maxDependent}]:`, }); const symbolMaxCyclomaticComplexity = await input({ message: `Max cyclomatic complexity per symbol [${defaultAuditConfig.symbol.maxCyclomaticComplexity}]:`, }); const parseOptionalInt = (val: string): number | undefined => { const trimmed = val.trim(); if (!trimmed) return undefined; const num = parseInt(trimmed, 10); return isNaN(num) ? undefined : num; }; const fileOverrides = { maxCodeLine: parseOptionalInt(fileMaxCodeLine), maxCodeChar: parseOptionalInt(fileMaxCodeChar), maxDependency: parseOptionalInt(fileMaxDependency), maxDependent: parseOptionalInt(fileMaxDependent), maxCyclomaticComplexity: parseOptionalInt(fileMaxCyclomaticComplexity), }; const symbolOverrides = { maxCodeLine: parseOptionalInt(symbolMaxCodeLine), maxCodeChar: parseOptionalInt(symbolMaxCodeChar), maxDependency: parseOptionalInt(symbolMaxDependency), maxDependent: parseOptionalInt(symbolMaxDependent), maxCyclomaticComplexity: parseOptionalInt(symbolMaxCyclomaticComplexity), }; const cleanObj = (obj: Record) => { const result: Record = {}; for (const [k, v] of Object.entries(obj)) { if (v !== undefined) result[k] = v; } return Object.keys(result).length > 0 ? result : undefined; }; const fileClean = cleanObj(fileOverrides); const symbolClean = cleanObj(symbolOverrides); if (fileClean || symbolClean) { auditConfig = {}; if (fileClean) auditConfig.file = fileClean as typeof auditConfig.file; if (symbolClean) { auditConfig.symbol = symbolClean as typeof auditConfig.symbol; } } console.info("✅ Audit threshold configuration added"); } const config: z.infer = { language: language, project: { include: includePatterns, exclude: excludePatterns.length > 0 ? excludePatterns : undefined, }, outDir: outDir ? outDir : "napi_out", }; if (pythonConfig) { config.python = pythonConfig; } if (cConfig) { config.c = cConfig; } if (labelingConfig) { config.labeling = labelingConfig; } if (auditConfig) { config.audit = auditConfig; } return config; } ================================================ FILE: src/cli/handlers/set/apiKey.ts ================================================ import type { Arguments } from "yargs-types"; import type { z } from "zod"; import { type globalConfigSchema, setConfig, } from "../../middlewares/globalConfig.ts"; import { ANTHROPIC_PROVIDER, GOOGLE_PROVIDER, type ModelProvider, OPENAI_PROVIDER, } from "../../../manifest/dependencyManifest/labeling/model.ts"; import { input, select } from "@inquirer/prompts"; async function handler( argv: Arguments & { globalConfig: z.infer; }, ) { const globalConfig = argv.globalConfig as z.infer; const provider = await select({ message: "Select a provider", choices: [ { name: "Google", value: GOOGLE_PROVIDER }, { name: "OpenAI", value: OPENAI_PROVIDER }, { name: "Anthropic", value: ANTHROPIC_PROVIDER }, ], }) as ModelProvider; const apiKey = await input({ message: "Enter the API key", validate: (value) => { if (value.length === 0) { return "API key cannot be empty"; } return true; }, }); const labeling = globalConfig.labeling || { apiKeys: {} }; labeling.apiKeys[provider] = apiKey; setConfig({ ...globalConfig, labeling }); console.info("API key set successfully"); } export default { command: "apiKey", describe: "set an API key for a model provider in your global config", builder: () => {}, handler, }; ================================================ FILE: src/cli/handlers/set/index.ts ================================================ import apiKeyHandler from "./apiKey.ts"; import type { Arguments } from "yargs-types"; import type { globalConfigSchema } from "../../middlewares/globalConfig.ts"; import type { z } from "zod"; function builder( yargs: Arguments & { globalConfig: z.infer; }, ) { return yargs .command(apiKeyHandler) .demandCommand(1, "You need to specify a valid command"); } export default { command: "set", describe: "set a value in the global config", builder, handler: () => {}, }; ================================================ FILE: src/cli/handlers/view/index.ts ================================================ import type { Arguments } from "yargs-types"; import type { z } from "zod"; import type { globalConfigSchema } from "../../middlewares/globalConfig.ts"; import { type localConfigSchema, napiConfigMiddleware, } from "../../middlewares/napiConfig.ts"; import { dirname, fromFileUrl, join } from "@std/path"; import { generateAuditManifest } from "../../../manifest/auditManifest/index.ts"; import { type AuditConfig, defaultAuditConfig, } from "../../../manifest/auditManifest/types.ts"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; const NAPI_DIR = ".napi"; const MANIFESTS_DIR = "manifests"; interface ManifestEnvelope { id: string; branch: string; commitSha: string; commitShaDate: string; createdAt: string; manifest: DependencyManifest; } interface ManifestListItem { id: string; branch: string; commitSha: string; commitShaDate: string; createdAt: string; fileCount: number; } function getManifestsDir(workdir: string): string { return join(workdir, NAPI_DIR, MANIFESTS_DIR); } function listManifests(workdir: string): ManifestListItem[] { const manifestsDir = getManifestsDir(workdir); const items: ManifestListItem[] = []; try { for (const entry of Deno.readDirSync(manifestsDir)) { if (!entry.isFile || !entry.name.endsWith(".json")) continue; try { const content = Deno.readTextFileSync(join(manifestsDir, entry.name)); const envelope = JSON.parse(content) as ManifestEnvelope; items.push({ id: envelope.id, branch: envelope.branch, commitSha: envelope.commitSha, commitShaDate: envelope.commitShaDate, createdAt: envelope.createdAt, fileCount: Object.keys(envelope.manifest).length, }); } catch { // Skip malformed manifest files } } } catch { // Directory doesn't exist yet } items.sort((a, b) => b.createdAt.localeCompare(a.createdAt)); return items; } function loadManifest( workdir: string, manifestId: string, ): ManifestEnvelope | null { const manifestPath = join( getManifestsDir(workdir), `${manifestId}.json`, ); try { const content = Deno.readTextFileSync(manifestPath); return JSON.parse(content) as ManifestEnvelope; } catch { return null; } } function getViewerDistDir(): string { const thisDir = dirname(fromFileUrl(import.meta.url)); return join(thisDir, "..", "..", "..", "..", "viewer", "dist"); } function getContentType(path: string): string { if (path.endsWith(".html")) return "text/html; charset=utf-8"; if (path.endsWith(".js")) return "application/javascript; charset=utf-8"; if (path.endsWith(".css")) return "text/css; charset=utf-8"; if (path.endsWith(".json")) return "application/json; charset=utf-8"; if (path.endsWith(".svg")) return "image/svg+xml"; if (path.endsWith(".png")) return "image/png"; if (path.endsWith(".ico")) return "image/x-icon"; if (path.endsWith(".woff2")) return "font/woff2"; if (path.endsWith(".woff")) return "font/woff"; return "application/octet-stream"; } async function tryServeStatic( viewerDir: string, pathname: string, ): Promise { let filePath = join(viewerDir, pathname); try { const stat = Deno.statSync(filePath); if (stat.isDirectory) { filePath = join(filePath, "index.html"); } } catch { // File doesn't exist } try { const content = await Deno.readFile(filePath); return new Response(content, { headers: { "content-type": getContentType(filePath) }, }); } catch { return null; } } function jsonResponse(data: unknown, status = 200): Response { return new Response(JSON.stringify(data), { status, headers: { "content-type": "application/json; charset=utf-8", "access-control-allow-origin": "*", }, }); } async function openBrowser(url: string) { try { let cmd: string[]; if (Deno.build.os === "darwin") { cmd = ["open", url]; } else if (Deno.build.os === "windows") { cmd = ["cmd", "/c", "start", url]; } else { cmd = ["xdg-open", url]; } const command = new Deno.Command(cmd[0], { args: cmd.slice(1), stdout: "null", stderr: "null", }); const child = command.spawn(); await child.status; } catch { // Silently fail if browser can't be opened } } function findAvailablePort(startPort: number): number { for (let port = startPort; port < startPort + 100; port++) { try { const listener = Deno.listen({ port }); listener.close(); return port; } catch { continue; } } throw new Error("No available port found"); } function mergeAuditConfig( userAudit?: z.infer["audit"], ): AuditConfig { return { file: { ...defaultAuditConfig.file, ...userAudit?.file, }, symbol: { ...defaultAuditConfig.symbol, ...userAudit?.symbol, }, }; } function builder( yargs: Arguments & { globalConfig: z.infer; }, ) { return yargs .middleware(napiConfigMiddleware) .option("port", { type: "number", description: "Port to serve the viewer on", default: 3000, }); } async function handler( argv: Arguments & { globalConfig: z.infer; napiConfig: z.infer; } & { port: number; }, ) { const workdir = argv.workdir as string; const viewerDir = getViewerDistDir(); const auditConfig = mergeAuditConfig(argv.napiConfig?.audit); const port = findAvailablePort(argv.port); const handler = async (request: Request): Promise => { const url = new URL(request.url); const pathname = url.pathname; if (pathname === "/api/manifests" && request.method === "GET") { const manifests = listManifests(workdir); return jsonResponse(manifests); } const manifestDetailMatch = pathname.match( /^\/api\/manifests\/([^/]+)$/, ); if (manifestDetailMatch && request.method === "GET") { const manifestId = manifestDetailMatch[1]; const envelope = loadManifest(workdir, manifestId); if (!envelope) { return jsonResponse({ error: "Manifest not found" }, 404); } return jsonResponse(envelope); } const auditMatch = pathname.match( /^\/api\/manifests\/([^/]+)\/audit$/, ); if (auditMatch && request.method === "GET") { const manifestId = auditMatch[1]; const envelope = loadManifest(workdir, manifestId); if (!envelope) { return jsonResponse({ error: "Manifest not found" }, 404); } const auditManifest = generateAuditManifest( envelope.manifest, auditConfig, ); return jsonResponse(auditManifest); } // Serve static files from viewer dist const staticResponse = await tryServeStatic(viewerDir, pathname); if (staticResponse) return staticResponse; // SPA fallback: serve index.html for any unmatched route const indexResponse = await tryServeStatic(viewerDir, "/index.html"); if (indexResponse) return indexResponse; return new Response("Not Found", { status: 404 }); }; console.info(`🚀 Starting napi viewer...`); console.info( `📂 Serving manifests from: ${join(workdir, NAPI_DIR, MANIFESTS_DIR)}`, ); console.info(`🌐 Open: http://localhost:${port}`); console.info(`\nPress Ctrl+C to stop.\n`); await openBrowser(`http://localhost:${port}`); Deno.serve({ port }, handler); } export default { command: "view", describe: "open the dependency visualizer in your browser", builder, handler, }; ================================================ FILE: src/cli/index.ts ================================================ import yargs from "yargs"; import { checkVersionMiddleware, getCurrentVersion, } from "./middlewares/checkVersion.ts"; import initCommand from "./handlers/init/index.ts"; import setCommand from "./handlers/set/index.ts"; import generateCommand from "./handlers/generate/index.ts"; import viewCommand from "./handlers/view/index.ts"; import extractCommand from "./handlers/extract/index.ts"; import { globalConfigMiddleware } from "./middlewares/globalConfig.ts"; export const globalOptions = { workdir: { type: "string", default: Deno.cwd(), alias: "wd", description: "working directory", }, }; export function initCli() { yargs(Deno.args) .scriptName("napi") .usage("Usage: $0 [options]") .options(globalOptions) .middleware(checkVersionMiddleware) .middleware(globalConfigMiddleware) .command(initCommand) .command(setCommand) .command(generateCommand) .command(viewCommand) .command(extractCommand) .demandCommand(1, "You need to specify a command") .strict() .help() .alias("help", "h") .version(getCurrentVersion()) .alias("version", "v") .epilogue("For more information, visit https://github.com/nanoapi-io/napi") .parse(); } ================================================ FILE: src/cli/middlewares/checkVersion.ts ================================================ import type { Arguments } from "yargs-types"; import localPackageJson from "../../../deno.json" with { type: "json" }; export function getCurrentVersion() { return localPackageJson.version; } export async function checkVersionMiddleware(_args: Arguments) { const currentVersion = getCurrentVersion(); try { // Simple fetch with timeout to prevent blocking const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 5000); const response = await fetch( "https://api.github.com/repos/nanoapi-io/napi/releases/latest", { signal: controller.signal, headers: { "Accept": "application/vnd.github.v3+json" }, }, ); clearTimeout(timeoutId); if (!response.ok) { throw new Error( `GitHub API returned ${response.status}: ${response.statusText}`, ); } const data = await response.json(); const latestVersion = data.tag_name.replace(/^v/, ""); if (currentVersion !== latestVersion) { console.warn( ` You are using version ${currentVersion}. The latest version is ${latestVersion}. Please update to the latest version to continue using napi. You can update the version by running the following command: \`\`\`bash curl -fsSL https://raw.githubusercontent.com/nanoapi-io/napi/refs/heads/main/install_scripts/install.sh | bash \`\`\` Or you can download and install them manually from here: ${data.html_url} `, ); // Force the user to update to the latest version Deno.exit(1); } } catch (err) { console.warn( `Skipping version check. Failed to check for updates: ${ err instanceof Error ? err.message : "Unknown error" }`, ); // Continue execution without blocking } } ================================================ FILE: src/cli/middlewares/globalConfig.ts ================================================ import type { Arguments } from "yargs-types"; import { dirname, join } from "@std/path"; import z from "zod"; export const globalConfigSchema = z.object({ labeling: z.object({ apiKeys: z.object({ google: z.string().optional(), openai: z.string().optional(), anthropic: z.string().optional(), }), }).optional(), }); const defaultConfig: z.infer = {}; function getConfigPath() { const appName = "napi"; if (Deno.build.os === "windows") { // Windows: Use %APPDATA% const homeDir = Deno.env.get("USERPROFILE"); if (!homeDir) { throw new Error("USERPROFILE environment variable not found"); } const appData = Deno.env.get("APPDATA") || join(homeDir, "AppData", "Roaming"); return join(appData, appName, "config.json"); } else if (Deno.build.os === "darwin") { // macOS: Use ~/Library/Application Support const homeDir = Deno.env.get("HOME"); if (!homeDir) { throw new Error("HOME environment variable not found"); } return join( homeDir, "Library", "Application Support", appName, "config.json", ); } else { // Linux and others: Use ~/.config const homeDir = Deno.env.get("HOME"); if (!homeDir) { throw new Error("HOME environment variable not found"); } const configDir = Deno.env.get("XDG_CONFIG_HOME") || join(homeDir, ".config"); return join(configDir, appName, "config.json"); } } export function globalConfigMiddleware( args: Arguments & { workdir: string; }, ) { const configPath = getConfigPath(); let config: z.infer = defaultConfig; try { let exists = false; try { Deno.statSync(configPath); exists = true; } catch { exists = false; } if (exists) { const content = Deno.readTextFileSync(configPath); const result = globalConfigSchema.safeParse(JSON.parse(content)); if (!result.success) { // wrong config, generate a new one setConfig(config); args.globalConfig = config; return; } // config is valid, use it config = result.data; args.globalConfig = config; return; } else { // no config, generate a new one setConfig(config); args.globalConfig = config; return; } } catch (_error) { // failed to read or create config, generate a new one config = defaultConfig; setConfig(config); args.globalConfig = config; return; } } export function setConfig( config: z.infer, ) { const configPath = getConfigPath(); const dir = dirname(configPath); let dirExists = false; try { Deno.statSync(dir); dirExists = true; } catch { dirExists = false; } if (!dirExists) { Deno.mkdirSync(dir, { recursive: true }); } Deno.writeTextFileSync(configPath, JSON.stringify(config, null, 2)); } ================================================ FILE: src/cli/middlewares/napiConfig.ts ================================================ import { join } from "@std/path"; import type { Arguments } from "yargs-types"; import z from "zod"; import pythonStdlibList from "../../scripts/generate_python_stdlib_list/output.json" with { type: "json", }; import { cLanguage, csharpLanguage, javaLanguage, pythonLanguage, } from "../../helpers/treeSitter/parsers.ts"; import { ANTHROPIC_PROVIDER, GOOGLE_PROVIDER, OPENAI_PROVIDER, } from "../../manifest/dependencyManifest/labeling/model.ts"; const pythonVersions = Object.keys(pythonStdlibList); export const localConfigSchema = z.object({ language: z.enum([pythonLanguage, csharpLanguage, cLanguage, javaLanguage]), [pythonLanguage]: z .object({ version: z .string() .refine((val) => pythonVersions.includes(val), { message: `Python version must be one of: ${ pythonVersions.join(", ") }`, }) .optional(), }) .optional(), // python specific config [cLanguage]: z .object({ includedirs: z.array(z.string()).optional(), }) .optional(), // c specific config project: z.object({ include: z.array(z.string()), exclude: z.array(z.string()).optional(), }), outDir: z.string(), labeling: z.object({ modelProvider: z.enum([ GOOGLE_PROVIDER, OPENAI_PROVIDER, ANTHROPIC_PROVIDER, ]), maxConcurrency: z.number().optional(), }).optional(), audit: z.object({ file: z.object({ maxCodeChar: z.number().optional(), maxChar: z.number().optional(), maxCodeLine: z.number().optional(), maxLine: z.number().optional(), maxDependency: z.number().optional(), maxDependent: z.number().optional(), maxCyclomaticComplexity: z.number().optional(), }).optional(), symbol: z.object({ maxCodeChar: z.number().optional(), maxChar: z.number().optional(), maxCodeLine: z.number().optional(), maxLine: z.number().optional(), maxDependency: z.number().optional(), maxDependent: z.number().optional(), maxCyclomaticComplexity: z.number().optional(), }).optional(), }).optional(), }); const napiConfigFileName = ".napirc"; export function getConfigFromWorkDir(workdir: string) { const napircPath = join(workdir, napiConfigFileName); try { Deno.statSync(napircPath); } catch { throw new Error(`${napiConfigFileName} not found in ${workdir}`); } const napircContent = Deno.readTextFileSync(napircPath); const result = localConfigSchema.safeParse(JSON.parse(napircContent)); if (!result.success) { throw new Error("Invalid NapiConfig: " + result.error); } if (result.data) { return result.data; } } export function createConfig( napiConfig: z.infer, workdir: string, ) { const napircPath = join(workdir, napiConfigFileName); Deno.writeTextFileSync(napircPath, JSON.stringify(napiConfig, null, 2)); } export function napiConfigMiddleware( args: Arguments & { workdir: string; }, ) { try { // First, check if the workdir itself exists try { const stat = Deno.statSync(args.workdir); if (!stat.isDirectory) { console.error("❌ Error: Specified path is not a directory"); console.error(` Path: ${args.workdir}`); console.error(" Please specify a valid directory path."); Deno.exit(1); } } catch (error: unknown) { if (error instanceof Deno.errors.NotFound) { console.error("❌ Error: Directory not found"); console.error(` Path: ${args.workdir}`); console.error( " Please check that the directory exists and try again.", ); } else { console.error("❌ Error: Cannot access directory"); console.error(` Path: ${args.workdir}`); console.error( ` Reason: ${ error instanceof Error ? error.message : String(error) }`, ); } Deno.exit(1); } // Check if .napirc config file exists let isConfigExist = false; try { const stat = Deno.statSync(join(args.workdir, napiConfigFileName)); isConfigExist = stat.isFile; } catch { isConfigExist = false; } if (!isConfigExist) { console.error("❌ No .napirc configuration file found"); console.error(` Looking in: ${args.workdir}`); console.error(" Expected file: .napirc"); console.error(""); console.error("💡 To get started:"); console.error(" 1. Navigate to your project directory"); console.error(" 2. Run: napi init"); console.error(" 3. Follow the interactive setup"); Deno.exit(1); } // Then validate and load the config try { const napiConfig = getConfigFromWorkDir(args.workdir); args.napiConfig = napiConfig; } catch (error: unknown) { console.error("❌ Invalid .napirc configuration file"); console.error(` File: ${join(args.workdir, ".napirc")}`); console.error( ` Error: ${error instanceof Error ? error.message : String(error)}`, ); console.error(""); console.error("💡 To fix this:"); console.error(" 1. Check your .napirc file for syntax errors"); console.error(" 2. Or run 'napi init' to regenerate the configuration"); Deno.exit(1); } } catch (error: unknown) { // Catch any unexpected errors console.error("❌ Unexpected error while loading configuration"); console.error( ` ${error instanceof Error ? error.message : String(error)}`, ); console.error(""); console.error("💡 If this persists, please report this issue at:"); console.error(" https://github.com/nanoapi-io/napi/issues"); Deno.exit(1); } } ================================================ FILE: src/helpers/fileSystem/index.ts ================================================ import { globSync } from "glob"; import { dirname, join } from "@std/path"; import { cLanguage, csharpLanguage, javaLanguage, pythonLanguage, } from "../treeSitter/parsers.ts"; export function getExtensionsForLanguage(language: string) { const supportedLanguages: Record = { [pythonLanguage]: ["py"], [csharpLanguage]: ["cs", "csproj"], [cLanguage]: ["c", "h"], [javaLanguage]: ["java"], }; const supportedLanguage = supportedLanguages[language]; if (!supportedLanguage) { throw new Error(`Unsupported language: ${language}`); } return supportedLanguage; } export function getFilesFromDirectory( dir: string, options?: { includes?: string[]; excludes?: string[]; extensions?: string[]; logMessages?: boolean; }, ) { const defaultOptions = { includes: ["**"], excludes: [], extensions: [], logMessages: false, }; const mergedOptions = { ...defaultOptions, ...options }; if (mergedOptions.logMessages) console.info(`Getting files from ${dir}...`); const relativeFilePaths = globSync(mergedOptions.includes, { cwd: dir, nodir: true, ignore: mergedOptions.excludes, }); if (mergedOptions.logMessages) { console.info( `Found a total of ${relativeFilePaths.length} files in ${dir}`, ); } const files = new Map(); relativeFilePaths.forEach((relativeFilePath) => { let include = true; if (mergedOptions.extensions.length > 0) { const fileExtension = relativeFilePath.split(".").pop(); if (!fileExtension || !mergedOptions.extensions.includes(fileExtension)) { include = false; } } if (include) { const fullPath = join(dir, relativeFilePath); const fileContent = Deno.readTextFileSync(fullPath); // ensure the file content is in UNIX format const unixPath = relativeFilePath.replace(/\\/g, "/"); files.set(unixPath, { path: unixPath, content: fileContent, }); } else { if (mergedOptions.logMessages) { console.info(`❌ Not including ${relativeFilePath} (not supported)`); } } }); if (mergedOptions.logMessages) { console.info(`Included a total of ${files.size} files from ${dir}`); } return files; } export function writeFilesToDirectory( files: Map, dir: string, // Always in UNIX format ) { // As dir is always in UNIX format, we need to map it to windows format when writing files if (Deno.build.os === "windows") { dir = dir.replace(/\//g, "\\"); } // empty the directory first try { Deno.removeSync(dir, { recursive: true }); } catch { // directory doesn't exist } Deno.mkdirSync(dir, { recursive: true }); for (let { path, content } of files.values()) { if (Deno.build.os === "windows") { // Convert path to Windows format if necessary path = path.replace(/\//g, "\\"); } const fullPath = join(dir, path); Deno.mkdirSync(dirname(fullPath), { recursive: true }); Deno.writeTextFileSync(fullPath, content); } } ================================================ FILE: src/helpers/sourceCode/index.test.ts ================================================ import { describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { removeIndexesFromSourceCode } from "./index.ts"; describe("removeIndexesFromSourceCode", () => { it("should return the original source code if no indexes are provided", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, []); expect(result).toBe(sourceCode); }); it("should remove all the text if start and end indexes include all the text", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 0, endIndex: 10 }, ]); expect(result).toBe(""); }); it("should remove a single range", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 1, endIndex: 9 }, ]); expect(result).toBe("09"); }); it("should remove multiple non-overlapping indexes", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 0, endIndex: 1 }, // '0' { startIndex: 4, endIndex: 6 }, // '45' { startIndex: 9, endIndex: 10 }, // '9' ]); expect(result).toBe("123678"); }); it("should merge adjacent indexes", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 0, endIndex: 2 }, // '01' { startIndex: 2, endIndex: 3 }, // '2' ]); expect(result).toBe("3456789"); }); it("should merge overlapping indexes", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 0, endIndex: 3 }, // '012' { startIndex: 2, endIndex: 5 }, // '234' ]); expect(result).toBe("56789"); }); it("should handle indexes provided in random order", () => { const sourceCode = "0123456789"; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 8, endIndex: 9 }, // '89' { startIndex: 2, endIndex: 4 }, // '3' { startIndex: 6, endIndex: 8 }, // '67' ]); expect(result).toBe("01459"); }); it("should handle empty source code", () => { const sourceCode = ""; const result = removeIndexesFromSourceCode(sourceCode, [ { startIndex: 0, endIndex: 0 }, ]); expect(result).toBe(""); }); }); ================================================ FILE: src/helpers/sourceCode/index.ts ================================================ export function removeIndexesFromSourceCode( sourceCode: string, indexesToRemove: { startIndex: number; endIndex: number }[], ): string { // Sort ranges in ascending order by startIndex. indexesToRemove.sort((a, b) => a.startIndex - b.startIndex); // Merge overlapping or contiguous ranges. const mergedRanges: { startIndex: number; endIndex: number }[] = []; for (const range of indexesToRemove) { const last = mergedRanges[mergedRanges.length - 1]; if (last && range.startIndex <= last.endIndex) { // Merge overlapping or contiguous ranges. last.endIndex = Math.max(last.endIndex, range.endIndex); } else { mergedRanges.push({ ...range }); } } // Use native string manipulation instead of Buffer let result = ""; let lastIndex = 0; // Iterate over merged ranges in ascending order. mergedRanges.forEach(({ startIndex, endIndex }) => { // Append content from the end of the previous range to the start of the current range. result += sourceCode.substring(lastIndex, startIndex); lastIndex = endIndex; }); // Append any remaining content after the last range. result += sourceCode.substring(lastIndex); return result; } ================================================ FILE: src/helpers/treeSitter/parsers.ts ================================================ import Parser, { type Language } from "tree-sitter"; import Python from "tree-sitter-python"; import CSharp from "tree-sitter-c-sharp"; import C from "tree-sitter-c"; import Java from "tree-sitter-java"; const pythonParser = new Parser(); pythonParser.setLanguage(Python as Language); const pythonLanguage = Python.name as "python"; const csharpParser = new Parser(); csharpParser.setLanguage(CSharp as Language); const csharpLanguage = CSharp.name as "c-sharp"; const cParser = new Parser(); cParser.setLanguage(C as Language); const cLanguage = C.name as "c"; const javaParser = new Parser(); javaParser.setLanguage(Java as Language); const javaLanguage = Java.name as "java"; export { cLanguage, cParser, csharpLanguage, csharpParser, javaLanguage, javaParser, pythonLanguage, pythonParser, }; ================================================ FILE: src/index.test.ts ================================================ import { describe, it } from "@std/testing/bdd"; import { expect } from "@std/expect"; describe("Dummy Test Suite", () => { it("should pass this dummy test", () => { expect(true).toBe(true); }); }); ================================================ FILE: src/index.ts ================================================ import { initCli } from "./cli/index.ts"; initCli(); ================================================ FILE: src/languagePlugins/c/README.md ================================================ # NanoAPI C Plugin This plugin manages parsing and mapping of dependencies in C projects. **Warning :** This plugin relies on tree-sitter, which has an unreliable parser for C. Not every C project is entirely compatible. Warnings may be issued where tree-sitter finds errors. ## Class diagram ```mermaid classDiagram class CMetricsAnalyzer { +analyzeNode(node: Parser.SyntaxNode): CComplexityMetrics } class CExtractor { -manifest: DependencyManifest -registry: Map -includeResolver: CIncludeResolver +extractSymbols(symbolsMap: Map): Map } class CSymbolRegistry { -headerResolver: CHeaderResolver -files: Map +getRegistry(): Map } class CHeaderResolver { +resolveSymbols(file: File): ExportedSymbol[] } class CIncludeResolver { -symbolRegistry: Map -files: Map +getInclusions(): Map } class CInvocationResolver { -includeResolver: CIncludeResolver +getInvocationsForSymbol(symbol: Symbol): Invocations +getInvocationsForFile(filepath: string): Invocations } class CDependencyFormatter { -symbolRegistry: CSymbolRegistry -includeResolver: CIncludeResolver -invocationResolver: CInvocationResolver +formatFile(filepath: string): CDepFile } class Symbol { <> -name: string -declaration: ExportedSymbol } class FunctionSignature { -definition: FunctionDefinition -isMacro: boolean } class FunctionDefinition { -signature: FunctionSignature -isMacro: boolean } class DataType { -typedefs: Map } class Typedef { -datatype: DataType } class Variable { -isMacro: boolean } class CFile { -file: File -symbols: Map -type: CFileType } class ExportedSymbol { -name: string -type: SymbolType -specifiers: StorageClassSpecifier[] -qualifiers: TypeQualifier[] -node: Parser.SyntaxNode -identifierNode: Parser.SyntaxNode -filepath: string } class Inclusions { -filepath: string -symbols: Map -internal: string[] -standard: Map } class Invocations { -resolved: Map -unresolved: Set } class CDependency { -id: string -isExternal: boolean -symbols: Record } class CDepFile { -id: string -filePath: string -rootNode: Parser.SyntaxNode -lineCount: number -characterCount: number -dependencies: Record -symbols: Record } class CDepSymbol { -id: string -type: CDepSymbolType -lineCount: number -characterCount: number -node: Parser.SyntaxNode -dependents: Record -dependencies: Record } class CComplexityMetrics { -cyclomaticComplexity: number -codeLinesCount: number -linesCount: number -codeCharacterCount: number -characterCount: number } class CodeCounts { -lines: number -characters: number } class CommentSpan { -start: Point -end: Point } %% Relationships Symbol <|-- FunctionSignature Symbol <|-- FunctionDefinition Symbol <|-- DataType Symbol <|-- Typedef Symbol <|-- Variable CSymbolRegistry --> CFile CSymbolRegistry --> CHeaderResolver CIncludeResolver --> CSymbolRegistry CInvocationResolver --> CIncludeResolver CDependencyFormatter --> CSymbolRegistry CDependencyFormatter --> CIncludeResolver CDependencyFormatter --> CInvocationResolver CExtractor --> CSymbolRegistry CExtractor --> CIncludeResolver CExtractor --> CFile CFile --> Symbol Typedef --> DataType DataType --> Typedef Invocations --> Symbol Inclusions --> Symbol CDepFile --> CDependency CDepFile --> CDepSymbol CDepSymbol --> CDependent CDepSymbol --> CDependency CMetricsAnalyzer --> CComplexityMetrics CMetricsAnalyzer --> CodeCounts CMetricsAnalyzer --> CommentSpan ``` ================================================ FILE: src/languagePlugins/c/dependencyFormatting/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { CDependencyFormatter } from "./index.ts"; import { join } from "@std/path"; describe("CDependencyFormatter", () => { const cFilesMap = getCFilesMap(); const depFormatter = new CDependencyFormatter(cFilesMap); const burgersh = join(cFilesFolder, "burgers.h"); const burgersc = join(cFilesFolder, "burgers.c"); const personnelh = join(cFilesFolder, "personnel.h"); const main = join(cFilesFolder, "main.c"); test("main.c", () => { const fmain = depFormatter.formatFile(main); expect(fmain).toBeDefined(); expect(fmain.id).toBe(main); expect(fmain.dependencies[burgersh]).toBeDefined(); expect(fmain.dependencies[burgersc]).not.toBeDefined(); expect(fmain.dependencies[personnelh]).toBeDefined(); expect(fmain.dependencies[""]).toBeDefined(); expect(fmain.dependencies[personnelh].isExternal).toBe(false); expect(fmain.dependencies[burgersh].isExternal).toBe(false); expect(fmain.dependencies[""].isExternal).toBe(true); expect(fmain.dependencies[burgersh].symbols["Burger"]).toBeDefined(); expect(fmain.dependencies[burgersh].symbols["create_burger"]).toBeDefined(); expect(fmain.dependencies[personnelh].symbols["Employee"]).toBeDefined(); expect( fmain.dependencies[personnelh].symbols["create_employee"], ).toBeDefined(); expect( fmain.dependencies[personnelh].symbols["print_employee_details"], ).toBeDefined(); expect(fmain.symbols["main"]).toBeDefined(); expect(fmain.symbols["main"].type).toBe("function"); expect(fmain.symbols["main"].lineCount > 1).toBe(true); expect(fmain.symbols["main"].characterCount > 1).toBe(true); expect(fmain.symbols["main"].dependents).toBeDefined(); expect(fmain.symbols["main"].dependencies).toBeDefined(); expect(fmain.symbols["main"].dependencies[burgersh]).toBeDefined(); expect(fmain.symbols["main"].dependencies[burgersh].isExternal).toBe(false); expect(fmain.symbols["main"].dependencies[burgersh].symbols["Burger"]).toBe( "Burger", ); expect( fmain.symbols["main"].dependencies[burgersh].symbols["create_burger"], ).toBe("create_burger"); expect(fmain.symbols["main"].dependencies[personnelh]).toBeDefined(); expect(fmain.symbols["main"].dependencies[personnelh].isExternal).toBe( false, ); expect( fmain.symbols["main"].dependencies[personnelh].symbols["Employee"], ).toBe("Employee"); expect( fmain.symbols["main"].dependencies[personnelh].symbols["create_employee"], ).toBe("create_employee"); expect( fmain.symbols["main"].dependencies[personnelh].symbols[ "print_employee_details" ], ).toBe("print_employee_details"); expect(fmain.symbols["main"].dependencies[""]).not.toBeDefined(); }); }); ================================================ FILE: src/languagePlugins/c/dependencyFormatting/index.ts ================================================ import { C_DEP_FUNCTION_TYPE, type CDependency, type CDepFile, type CDepSymbol, type CDepSymbolType, } from "./types.ts"; import { CSymbolRegistry } from "../symbolRegistry/index.ts"; import { CIncludeResolver } from "../includeResolver/index.ts"; import { CInvocationResolver } from "../invocationResolver/index.ts"; import { type CFile, EnumMember, type Symbol, } from "../symbolRegistry/types.ts"; import type { Invocations } from "../invocationResolver/types.ts"; import type Parser from "tree-sitter"; import { C_VARIABLE_TYPE, type SymbolType } from "../headerResolver/types.ts"; export class CDependencyFormatter { symbolRegistry: CSymbolRegistry; includeResolver: CIncludeResolver; invocationResolver: CInvocationResolver; #registry: Map; constructor( files: Map, includeDirs: string[] = [], ) { this.symbolRegistry = new CSymbolRegistry(files); this.#registry = this.symbolRegistry.getRegistry(); this.includeResolver = new CIncludeResolver( this.symbolRegistry, includeDirs, ); this.invocationResolver = new CInvocationResolver(this.includeResolver); } #formatSymbolType(st: SymbolType): CDepSymbolType { if (["struct", "enum", "union", "typedef", "variable"].includes(st)) { return st as CDepSymbolType; } if ( ["function_signature", "function_definition", "macro_function"].includes( st, ) ) { return C_DEP_FUNCTION_TYPE; } if (st === "macro_constant") { return C_VARIABLE_TYPE as CDepSymbolType; } throw new Error(`Unknown symbol type: ${st}`); } /** * Formats the dependencies of a file. * @param fileDependencies - The dependencies of the file. * @returns A formatted record of dependencies. */ #formatDependencies( fileDependencies: Invocations, ): Record { const dependencies: Record = {}; const resolved = fileDependencies.resolved; for (const [symName, symbol] of resolved) { const filepath = symbol.symbol.declaration.filepath; const id = symName; if (!dependencies[filepath]) { dependencies[filepath] = { id: filepath, isExternal: false, symbols: {}, }; } dependencies[filepath].symbols[id] = id; } return dependencies; } #formatStandardIncludes(stdincludes: string[]): Record { const dependencies: Record = {}; for (const id of stdincludes) { if (!dependencies[id]) { dependencies[id] = { id: id, isExternal: true, symbols: {}, }; } } return dependencies; } /** * Formats the symbols of a file. * @param fileSymbols - The symbols of the file. * @returns A formatted record of symbols. */ #formatSymbols(fileSymbols: Map): Record { const symbols: Record = {}; for (const [symName, symbol] of fileSymbols) { const id = symName; const dependencies = this.invocationResolver.getInvocationsForSymbol( symbol, ); if (!symbols[id] && !(symbol instanceof EnumMember)) { symbols[id] = { id: id, type: this.#formatSymbolType(symbol.declaration.type), lineCount: symbol.declaration.node.endPosition.row - symbol.declaration.node.startPosition.row, characterCount: symbol.declaration.node.endIndex - symbol.declaration.node.startIndex, node: symbol.declaration.node, dependents: {}, dependencies: this.#formatDependencies(dependencies), }; } } return symbols; } formatFile(filepath: string): CDepFile { const file = this.#registry.get(filepath); if (!file) { throw new Error(`File not found: ${filepath}`); } const fileSymbols = file.symbols; const fileDependencies = this.invocationResolver.getInvocationsForFile( filepath, ); const includes = this.includeResolver.getInclusions().get(filepath); if (!includes) { throw new Error(`File not found: ${filepath}`); } const stdincludes = Array.from(includes.standard.keys()); const invokedDependencies = this.#formatDependencies(fileDependencies); const stdDependencies = this.#formatStandardIncludes(stdincludes); const allDependencies = { ...invokedDependencies, ...stdDependencies, }; const formattedFile: CDepFile = { id: filepath, filePath: file.file.path, rootNode: file.file.rootNode, lineCount: file.file.rootNode.endPosition.row, characterCount: file.file.rootNode.endIndex, dependencies: allDependencies, symbols: this.#formatSymbols(fileSymbols), }; return formattedFile; } } ================================================ FILE: src/languagePlugins/c/dependencyFormatting/types.ts ================================================ import type { C_ENUM_TYPE, C_STRUCT_TYPE, C_TYPEDEF_TYPE, C_UNION_TYPE, C_VARIABLE_TYPE, } from "../headerResolver/types.ts"; import type Parser from "tree-sitter"; /** * Represents a dependency in a C file */ export interface CDependency { id: string; isExternal: boolean; symbols: Record; } /** * Represents a dependent in a C file */ export interface CDependent { id: string; symbols: Record; } export const C_DEP_FUNCTION_TYPE = "function"; export type CDepSymbolType = | typeof C_ENUM_TYPE | typeof C_UNION_TYPE | typeof C_STRUCT_TYPE | typeof C_TYPEDEF_TYPE | typeof C_VARIABLE_TYPE | typeof C_DEP_FUNCTION_TYPE; /** * Represents a symbol in a C file */ export interface CDepSymbol { id: string; type: CDepSymbolType; lineCount: number; characterCount: number; node: Parser.SyntaxNode; dependents: Record; dependencies: Record; } /** * Represents a C file with its dependencies and symbols. */ export interface CDepFile { id: string; filePath: string; rootNode: Parser.SyntaxNode; lineCount: number; characterCount: number; dependencies: Record; symbols: Record; } ================================================ FILE: src/languagePlugins/c/extractor/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { cFilesFolder, dummyLocalConfig, getCFilesContentMap, } from "../testFiles/index.ts"; import { CExtractor } from "./index.ts"; import { join } from "@std/path"; import { generateCDependencyManifest } from "../../../manifest/dependencyManifest/c/index.ts"; describe("CExtractor", () => { const cContentMap = getCFilesContentMap(); const manifest = generateCDependencyManifest(cContentMap, dummyLocalConfig); const extractor = new CExtractor(cContentMap, manifest, dummyLocalConfig); const burgers = join(cFilesFolder, "burgers.h"); const burgersc = join(cFilesFolder, "burgers.c"); const main = join(cFilesFolder, "main.c"); const all = join(cFilesFolder, "all.h"); const errorsh = join(cFilesFolder, "errors.h"); test("extracts create_burger", () => { const symbolsToExtract = new Map< string, { filePath: string; symbols: Set } >(); symbolsToExtract.set(burgers, { filePath: burgers, symbols: new Set(["create_burger"]), }); const extractedFiles = extractor.extractSymbols(symbolsToExtract); expect(extractedFiles.size).toBe(2); const newManifest = generateCDependencyManifest( extractedFiles, dummyLocalConfig, ); expect(newManifest[burgers]).toBeDefined(); expect(newManifest[burgersc]).toBeDefined(); // Expected symbols to be kept expect(newManifest[burgers].symbols["create_burger"]).toBeDefined(); expect(newManifest[burgersc].symbols["create_burger"]).toBeDefined(); expect(newManifest[burgers].symbols["Condiment"]).toBeDefined(); expect(newManifest[burgers].symbols["Burger"]).toBeDefined(); expect(newManifest[burgers].symbols["Sauce"]).toBeDefined(); expect(newManifest[burgers].symbols["burger_count"]).toBeDefined(); expect(newManifest[burgers].symbols["BURGERS_H"]).toBeDefined(); expect(newManifest[burgers].symbols["ClassicSauces"]).toBeDefined(); // Expected symbols to be removed expect(newManifest[burgers].symbols["MAX_BURGERS"]).not.toBeDefined(); expect(newManifest[burgers].symbols["MAX"]).not.toBeDefined(); expect(newManifest[burgers].symbols["Fries"]).not.toBeDefined(); expect(newManifest[burgers].symbols["Drink_t"]).not.toBeDefined(); expect(newManifest[burgers].symbols["Drink"]).not.toBeDefined(); expect(newManifest[burgers].symbols["classicBurger"]).not.toBeDefined(); expect(newManifest[burgers].symbols["destroy_burger"]).not.toBeDefined(); expect(newManifest[burgers].symbols["get_burger_by_id"]).not.toBeDefined(); expect(newManifest[burgers].symbols["get_cheapest_burger"]).not .toBeDefined(); }); test("extracts Drink_t", () => { const symbolsToExtract = new Map< string, { filePath: string; symbols: Set } >(); symbolsToExtract.set(burgers, { filePath: burgers, symbols: new Set(["Drink_t"]), }); const extractedFiles = extractor.extractSymbols(symbolsToExtract); expect(extractedFiles.size).toBe(1); const newManifest = generateCDependencyManifest( extractedFiles, dummyLocalConfig, ); expect(newManifest[burgers]).toBeDefined(); // Expected symbols to be kept expect(newManifest[burgers].symbols["Drink_t"]).toBeDefined(); expect(newManifest[burgers].symbols["Drink"]).toBeDefined(); expect(newManifest[burgers].symbols["BURGERS_H"]).toBeDefined(); // Expected symbols to be removed expect(Object.keys(newManifest[burgers].symbols).length).toBe(3); }); test("keeps all.h", () => { const symbolsToExtract = new Map< string, { filePath: string; symbols: Set } >(); symbolsToExtract.set(main, { filePath: main, symbols: new Set(["main"]), }); const extractedFiles = extractor.extractSymbols(symbolsToExtract); expect(extractedFiles.size).toBe(6); const newManifest = generateCDependencyManifest( extractedFiles, dummyLocalConfig, ); expect(newManifest[all]).toBeDefined(); }); test("deletes impossible include", () => { const symbolsToExtract = new Map< string, { filePath: string; symbols: Set } >(); symbolsToExtract.set(errorsh, { filePath: errorsh, symbols: new Set(["typedef"]), }); const extractedFiles = extractor.extractSymbols(symbolsToExtract); expect(extractedFiles.size).toBe(1); expect(extractedFiles.get(errorsh)).toBeDefined(); expect( extractedFiles.get(errorsh)?.content.includes( `#include "thisfiledoesnotexist.h"`, ), ).toBe(false); }); }); ================================================ FILE: src/languagePlugins/c/extractor/index.ts ================================================ import { CSymbolRegistry } from "../symbolRegistry/index.ts"; import { CIncludeResolver } from "../includeResolver/index.ts"; import type Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; import type { CFile, Symbol } from "../symbolRegistry/types.ts"; import type { ExportedFile } from "./types.ts"; import { C_DECLARATION_QUERY } from "../headerResolver/queries.ts"; import { C_IFDEF_QUERY } from "./queries.ts"; import { CInvocationResolver } from "../invocationResolver/index.ts"; import type z from "zod"; import type { localConfigSchema } from "../../../cli/middlewares/napiConfig.ts"; import { join } from "@std/path"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; export class CExtractor { manifest: DependencyManifest; registry: Map; includeResolver: CIncludeResolver; invocationResolver: CInvocationResolver; constructor( files: Map, manifest: DependencyManifest, napiConfig: z.infer, ) { this.manifest = manifest; const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const [filePath, file] of files) { parsedFiles.set(filePath, { path: file.path, rootNode: cParser.parse(file.content).rootNode, }); } const symbolRegistry = new CSymbolRegistry(parsedFiles); this.registry = symbolRegistry.getRegistry(); const outDir = napiConfig.outDir; const includeDirs = napiConfig["c"]?.includedirs ?? []; if (outDir) { includeDirs.push( ...includeDirs.map((i) => join(outDir, i).replace(/\\/g, "/")), ); } this.includeResolver = new CIncludeResolver(symbolRegistry, includeDirs); this.invocationResolver = new CInvocationResolver(this.includeResolver); } /** * Finds the first-level dependencies of a symbol in the manifest. * @param symbol - The symbol to find dependencies for. * @returns An array of symbols that are dependencies of the given symbol. */ #findDependencies(symbol: Symbol): Symbol[] { const dependencies: Symbol[] = [symbol]; const symbolDependencies = this.manifest[symbol.declaration.filepath] ?.symbols[symbol.name].dependencies; for ( const [filepath, dependencyinfo] of Object.entries(symbolDependencies) ) { const dependencyFile = this.registry.get(filepath); if (dependencyFile) { for (const symbolName of Object.keys(dependencyinfo.symbols)) { const dependencySymbol = dependencyFile.symbols.get(symbolName); if (dependencySymbol) { dependencies.push(dependencySymbol); } } } } return dependencies; } /** * Finds all dependencies of a given symbol. * @param symbol - The symbol for which to find dependencies. * @returns An array of symbols. */ #findAllDependencies(symbol: Symbol): Symbol[] { const dependencies: Symbol[] = []; const visited = new Set(); const stack = [symbol]; while (stack.length > 0) { const currentSymbol = stack.pop()!; if (visited.has(currentSymbol)) { continue; } visited.add(currentSymbol); dependencies.push(currentSymbol); const currentDependencies = this.#findDependencies(currentSymbol); stack.push(...currentDependencies); } return dependencies; } /** * Build a map of files and their symbols to keep. * @param symbolsToKeep - The symbols to keep. * @returns A map of file paths to their corresponding ExportedFile objects. */ #buildFileMap(symbolsToKeep: Symbol[]): Map { const exportedFiles = new Map(); for (const symbol of symbolsToKeep) { const filepath = symbol.declaration.filepath; if (!exportedFiles.has(filepath)) { const fileInRegistry = this.registry.get(filepath); if (!fileInRegistry) { throw new Error(`File not found: ${filepath}`); } const originalFile = fileInRegistry.file; // Ifdefs are instances of things that aren't symbols yet invoke a symbol const ifdefs = C_IFDEF_QUERY.captures(originalFile.rootNode).map((n) => n.node.text ); const definesToKeep = ifdefs.map((i) => fileInRegistry.symbols.get(i)!) .filter((i) => i); const symbols = new Map(); for (const define of definesToKeep) { symbols.set(define.name, define); } exportedFiles.set(filepath, { symbols, originalFile, strippedFile: originalFile, }); } const exportedFile = exportedFiles.get(filepath)!; const symbolName = symbol.name; if (!exportedFile.symbols.has(symbolName)) { exportedFile.symbols.set(symbolName, symbol); } // Keep the files that recursively lead to a symbol we need const invocations = this.invocationResolver.getInvocationsForSymbol( symbol, ); const filestokeep = Array.from( invocations.resolved.values().map((s) => this.includeResolver.findInclusionChain( symbol.declaration.filepath, s.symbol, ) ).filter((c) => c !== undefined), ).flatMap((c) => c.flatMap((f) => this.registry.get(f)!)); for (const f of filestokeep) { if (!exportedFiles.has(f.file.path)) { const ifdefs = C_IFDEF_QUERY.captures(f.file.rootNode).map((n) => n.node.text ); const definesToKeep = ifdefs.map((i) => f.symbols.get(i)!) .filter((i) => i); const symbols = new Map(); for (const define of definesToKeep) { symbols.set(define.name, define); } exportedFiles.set(f.file.path, { symbols, originalFile: f.file, strippedFile: f.file, }); } } } return exportedFiles; } /** * Edits the files to include only the symbols that are needed. * @param files - The files to edit. */ #stripFiles(files: Map) { for (const [, file] of files) { const rootNode = file.originalFile.rootNode; const originalText = rootNode.text; // Original file content const symbolsToKeep = new Set( file.symbols.values().map((s) => s.declaration.node), ); const symbolsToRemove = new Set(); const matches = C_DECLARATION_QUERY.captures(rootNode); for (const match of matches) { const symbolNode = match.node; if (!symbolsToKeep.has(symbolNode)) { symbolsToRemove.add(symbolNode); } } // Helper function to recursively filter nodes const filterNodes = (node: Parser.SyntaxNode): string => { if (symbolsToRemove.has(node)) { return ""; // Skip this node } // If the node has children, process them recursively if (["translation_unit", "preproc_ifdef"].includes(node.type)) { let result = ""; let lastEndIndex = node.startIndex; for (const child of node.children) { // Append the text between the last node and the current child result += originalText.slice(lastEndIndex, child.startIndex); result += filterNodes(child); // Process the child lastEndIndex = child.endIndex; } // Append the text after the last child result += originalText.slice(lastEndIndex, node.endIndex); return result; } // If the node has no children, return its text return originalText.slice(node.startIndex, node.endIndex); }; // Rebuild the file content by filtering nodes const newFileContent = filterNodes(rootNode); // Compactify the file content const compactedContent = this.#compactifyFile(newFileContent); // Parse the new content and update the stripped file const strippedFile = cParser.parse(compactedContent); file.strippedFile = { path: file.originalFile.path, rootNode: strippedFile.rootNode, }; } } #removeDeletedIncludes( files: Map, ) { const newproject: Map< string, { path: string; rootNode: Parser.SyntaxNode } > = new Map(); for (const [key, value] of files) { newproject.set(key, value.strippedFile); } const newregistry = new CSymbolRegistry(newproject); const newincluderes = new CIncludeResolver( newregistry, this.includeResolver.includeDirs, ); newincluderes.getInclusions(); for (const [key, value] of files) { const unresolved = newincluderes.unresolvedDirectives.get(key); if (unresolved) { let filetext = value.strippedFile.rootNode.text; for (const path of unresolved) { filetext = filetext.replace(`#include "${path}"`, ""); } filetext = this.#compactifyFile(filetext); value.strippedFile.rootNode = cParser.parse(filetext).rootNode; } } } #compactifyFile( filetext: string, ): string { // Remove empty lines and useless semicolons filetext = filetext.replace(/^\s*;\s*$/gm, ""); // Remove empty lines with semicolons filetext = filetext.replace(/^\s*[\r\n]+/gm, "\n"); // Remove empty lines return filetext; } /** * Finds the dependencies for a map of symbols. * @param symbolsMap - A map of file paths to their corresponding symbols. * @returns A set of symbols that are dependencies of the given symbols. */ #findDependenciesForMap( symbolsMap: Map< string, { filePath: string; symbols: Set; } >, ): Symbol[] { const symbolsToExtract: Symbol[] = []; for (const [filePath, symbolInfo] of symbolsMap) { const file = this.registry.get(filePath); if (!file) { throw new Error(`File not found: ${filePath}`); } const symbols = Array.from(symbolInfo.symbols); for (const symbolName of symbols) { const symbol = file.symbols.get(symbolName); if (symbol) { const dependencies = this.#findAllDependencies(symbol); symbolsToExtract.push(...dependencies); } } } // Remove duplicates return Array.from(new Set(symbolsToExtract)); } extractSymbols( symbolsMap: Map< string, { filePath: string; symbols: Set; } >, ): Map { const symbolsToExtract = this.#findDependenciesForMap(symbolsMap); const filesToExport = this.#buildFileMap(symbolsToExtract); this.#stripFiles(filesToExport); this.#removeDeletedIncludes(filesToExport); const exportedFiles = new Map(); for (const [filePath, file] of filesToExport) { const content = file.strippedFile.rootNode.text; exportedFiles.set(filePath, { path: filePath, content, }); } return exportedFiles; } } ================================================ FILE: src/languagePlugins/c/extractor/queries.ts ================================================ import Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; export const C_IFDEF_QUERY = new Parser.Query( cParser.getLanguage(), `(preproc_ifdef name: (identifier) @def)`, ); ================================================ FILE: src/languagePlugins/c/extractor/types.ts ================================================ import type { Symbol } from "../symbolRegistry/types.ts"; import type Parser from "tree-sitter"; export interface ExportedFile { symbols: Map; originalFile: { path: string; rootNode: Parser.SyntaxNode; }; strippedFile: { path: string; rootNode: Parser.SyntaxNode; }; } ================================================ FILE: src/languagePlugins/c/headerResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { CHeaderResolver } from "./index.ts"; import { join } from "@std/path"; describe("CHeaderResolver", () => { const cFilesMap = getCFilesMap(); const resolver = new CHeaderResolver(); const burgers = join(cFilesFolder, "burgers.h"); const crashcases = join(cFilesFolder, "crashcases.h"); const errorsh = join(cFilesFolder, "errors.h"); const oldmanh = join(cFilesFolder, "oldman.h"); const file = cFilesMap.get(burgers); if (!file) { throw new Error(`File not found: ${burgers}`); } const ccfile = cFilesMap.get(crashcases); if (!ccfile) { throw new Error(`File not found: ${crashcases}`); } const errorsfile = cFilesMap.get(errorsh); if (!errorsfile) { throw new Error(`File not found: ${errorsh}`); } const exportedSymbols = resolver.resolveSymbols(file); test("should resolve symbols in C header files", () => { expect(exportedSymbols).toHaveLength(16); }); test("resolves structs", () => { const burger = exportedSymbols.find((symbol) => symbol.name === "Burger"); expect(burger).toBeDefined(); if (!burger) { throw new Error("burger is undefined"); } expect(burger.type).toBe("struct"); expect(burger.specifiers).toEqual([]); expect(burger.qualifiers).toEqual([]); expect(burger.node.type).toBe("struct_specifier"); if (!burger.identifierNode) { throw new Error("burger.identifierNode is undefined"); } expect(burger.identifierNode.type).toBe("type_identifier"); expect(burger.filepath).toBe(burgers); }); test("resolves unions", () => { const sauce = exportedSymbols.find((symbol) => symbol.name === "Sauce"); expect(sauce).toBeDefined(); if (!sauce) { throw new Error("sauce is undefined"); } expect(sauce.type).toBe("union"); expect(sauce.specifiers).toEqual([]); expect(sauce.qualifiers).toEqual([]); expect(sauce.node.type).toBe("union_specifier"); if (!sauce.identifierNode) { throw new Error("sauce.identifierNode is undefined"); } expect(sauce.identifierNode.type).toBe("type_identifier"); expect(sauce.filepath).toBe(burgers); }); test("resolves enums", () => { const condiment = exportedSymbols.find( (symbol) => symbol.name === "Condiment", ); expect(condiment).toBeDefined(); if (!condiment) { throw new Error("condiment is undefined"); } expect(condiment.type).toBe("enum"); expect(condiment.specifiers).toEqual([]); expect(condiment.qualifiers).toEqual([]); expect(condiment.node.type).toBe("enum_specifier"); if (!condiment.identifierNode) { throw new Error("condiment.identifierNode is undefined"); } expect(condiment.identifierNode.type).toBe("type_identifier"); expect(condiment.filepath).toBe(burgers); const classicsauces = exportedSymbols.find( (symbol) => symbol.name === "ClassicSauces", ); expect(classicsauces).toBeDefined(); if (!classicsauces) { throw new Error("classicsauces is undefined"); } expect(classicsauces.type).toBe("enum"); const drink_t = exportedSymbols.find((symbol) => symbol.name === "Drink_t"); expect(drink_t).toBeDefined(); if (!drink_t) { throw new Error("drink_t is undefined"); } expect(drink_t.type).toBe("enum"); expect(drink_t.specifiers).toEqual([]); expect(drink_t.qualifiers).toEqual([]); expect(drink_t.node.type).toBe("enum_specifier"); if (!drink_t.identifierNode) { throw new Error("drink_t.identifierNode is undefined"); } expect(drink_t.identifierNode.type).toBe("type_identifier"); expect(drink_t.filepath).toBe(burgers); }); test("resolves variables", () => { const classicburger = exportedSymbols.find( (symbol) => symbol.name === "classicBurger", ); expect(classicburger).toBeDefined(); if (!classicburger) { throw new Error("classicburger is undefined"); } expect(classicburger.type).toBe("variable"); expect(classicburger.specifiers).toEqual([]); expect(classicburger.qualifiers).toEqual(["const"]); expect(classicburger.node.type).toBe("declaration"); if (!classicburger.identifierNode) { throw new Error("classicburger.identifierNode is undefined"); } expect(classicburger.identifierNode.type).toBe("identifier"); expect(classicburger.filepath).toBe(burgers); const burger_count = exportedSymbols.find( (symbol) => symbol.name === "burger_count", ); expect(burger_count).toBeDefined(); if (!burger_count) { throw new Error("burger_count is undefined"); } expect(burger_count.type).toBe("variable"); expect(burger_count.specifiers).toEqual(["static"]); expect(burger_count.qualifiers).toEqual([]); expect(burger_count.node.type).toBe("declaration"); if (!burger_count.identifierNode) { throw new Error("burger_count.identifierNode is undefined"); } expect(burger_count.identifierNode.type).toBe("identifier"); expect(burger_count.filepath).toBe(burgers); }); test("resolves macro constants", () => { const burgers_h = exportedSymbols.find( (symbol) => symbol.name === "BURGERS_H", ); expect(burgers_h).toBeDefined(); if (!burgers_h) { throw new Error("burgers_h is undefined"); } expect(burgers_h.type).toBe("macro_constant"); expect(burgers_h.specifiers).toEqual([]); expect(burgers_h.qualifiers).toEqual([]); expect(burgers_h.node.type).toBe("preproc_def"); if (!burgers_h.identifierNode) { throw new Error("burgers_h.identifierNode is undefined"); } expect(burgers_h.identifierNode.type).toBe("identifier"); expect(burgers_h.filepath).toBe(burgers); const max_burgers = exportedSymbols.find( (symbol) => symbol.name === "MAX_BURGERS", ); expect(max_burgers).toBeDefined(); if (!max_burgers) { throw new Error("max_burgers is undefined"); } expect(max_burgers.type).toBe("macro_constant"); expect(max_burgers.specifiers).toEqual([]); expect(max_burgers.qualifiers).toEqual([]); expect(max_burgers.node.type).toBe("preproc_def"); if (!max_burgers.identifierNode) { throw new Error("max_burgers.identifierNode is undefined"); } expect(max_burgers.identifierNode.type).toBe("identifier"); expect(max_burgers.filepath).toBe(burgers); }); test("resolves function signatures", () => { const function_names = exportedSymbols .filter((symbol) => symbol.type === "function_signature") .map((symbol) => symbol.name); expect(function_names).toHaveLength(4); expect(function_names).toContain("get_burger_by_id"); expect(function_names).toContain("get_cheapest_burger"); expect(function_names).toContain("create_burger"); expect(function_names).toContain("destroy_burger"); expect(function_names).not.toContain("MAX"); }); test("resolves macro functions", () => { const max_macro = exportedSymbols.find((symbol) => symbol.name === "MAX"); expect(max_macro).toBeDefined(); if (!max_macro) { throw new Error("max_macro is undefined"); } expect(max_macro.type).toBe("macro_function"); expect(max_macro.specifiers).toEqual([]); expect(max_macro.qualifiers).toEqual([]); expect(max_macro.node.type).toBe("preproc_function_def"); if (!max_macro.identifierNode) { throw new Error("max_macro.identifierNode is undefined"); } expect(max_macro.identifierNode.type).toBe("identifier"); expect(max_macro.filepath).toBe(burgers); }); test("resolves typedefs", () => { const fries = exportedSymbols.find((symbol) => symbol.name === "Fries"); expect(fries).toBeDefined(); if (!fries) { throw new Error("fries is undefined"); } expect(fries.type).toBe("typedef"); expect(fries.specifiers).toEqual([]); expect(fries.qualifiers).toEqual([]); expect(fries.node.type).toBe("type_definition"); if (!fries.identifierNode) { throw new Error("fries.identifierNode is undefined"); } expect(fries.identifierNode.type).toBe("type_identifier"); expect(fries.filepath).toBe(burgers); const drink = exportedSymbols.find((symbol) => symbol.name === "Drink"); expect(drink).toBeDefined(); if (!drink) { throw new Error("drink is undefined"); } expect(drink.type).toBe("typedef"); expect(drink.specifiers).toEqual([]); expect(drink.qualifiers).toEqual([]); expect(drink.node.type).toBe("type_definition"); if (!drink.identifierNode) { throw new Error("drink.identifierNode is undefined"); } expect(drink.identifierNode.type).toBe("type_identifier"); expect(drink.filepath).toBe(burgers); }); test("Crash Cases", () => { const ccexportedSymbols = resolver.resolveSymbols(ccfile); expect(ccexportedSymbols).toBeDefined(); const sprite = ccexportedSymbols.find((s) => s.name === "Sprite"); expect(sprite).toBeDefined(); if (!sprite) { throw new Error("sprite is undefined"); } expect(sprite.type).toBe("struct"); expect(sprite.specifiers).toEqual([]); expect(sprite.qualifiers).toEqual([]); expect(sprite.node.type).toBe("struct_specifier"); if (!sprite.identifierNode) { throw new Error("sprite.identifierNode is undefined"); } expect(sprite.identifierNode.type).toBe("type_identifier"); expect(sprite.filepath).toBe(crashcases); const placeholderfunction = ccexportedSymbols.find( (s) => s.name === "PlaceholderFunction", ); expect(placeholderfunction).toBeDefined(); if (!placeholderfunction) { throw new Error("placeholderfunction is undefined"); } expect(placeholderfunction.type).toBe("function_signature"); expect(placeholderfunction.specifiers).toEqual([]); expect(placeholderfunction.qualifiers).toEqual([]); expect(placeholderfunction.node.type).toBe("declaration"); if (!placeholderfunction.identifierNode) { throw new Error("placeholderfunction.identifierNode is undefined"); } expect(placeholderfunction.identifierNode.type).toBe("identifier"); expect(placeholderfunction.filepath).toBe(crashcases); const gmtfwa = ccexportedSymbols.find( (s) => s.name === "gMovementTypeFuncs_WanderAround", ); expect(gmtfwa).toBeDefined(); if (!gmtfwa) { throw new Error("gmtfwa is undefined"); } expect(gmtfwa.type).toBe("variable"); expect(gmtfwa.specifiers).toEqual([]); expect(gmtfwa.qualifiers).toEqual([]); expect(gmtfwa.node.type).toBe("declaration"); if (!gmtfwa.identifierNode) { throw new Error("gmtfwa.identifierNode is undefined"); } expect(gmtfwa.identifierNode.type).toBe("identifier"); expect(gmtfwa.filepath).toBe(crashcases); }); test("resolves unnamed types", () => { const exportedErrorSymbols = resolver.resolveSymbols(errorsfile); expect(exportedErrorSymbols).toBeDefined(); const symbolNames = exportedErrorSymbols.map((symbol) => symbol.name); expect(symbolNames).toContain("#NAPI_UNNAMED_ENUM_0"); }); test("resolves typedefs with same name as associated struct", () => { const oldmanSymbols = resolver.resolveSymbols( cFilesMap.get(oldmanh)!, ); expect(oldmanSymbols).toBeDefined(); const oldmen = oldmanSymbols.filter((s) => s.name === "OldMan"); expect(oldmen).toHaveLength(2); const oldman = oldmen.find((s) => s.type === "typedef"); expect(oldman).toBeDefined(); if (!oldman) { throw new Error("OldMan is undefined"); } expect(oldman.type).toBe("typedef"); expect(oldman.specifiers).toEqual([]); expect(oldman.qualifiers).toEqual([]); expect(oldman.node.type).toBe("type_definition"); if (!oldman.identifierNode) { throw new Error("oldman.identifierNode is undefined"); } expect(oldman.identifierNode.type).toBe("type_identifier"); expect(oldman.filepath).toBe(oldmanh); }); }); ================================================ FILE: src/languagePlugins/c/headerResolver/index.ts ================================================ import type { ExportedSymbol, StorageClassSpecifier, SymbolType, TypeQualifier, } from "./types.ts"; import { C_DECLARATION_QUERY } from "./queries.ts"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; import type Parser from "tree-sitter"; export class CHeaderResolver { parser: Parser = cParser; #unnamedSymbolCounter = 0; /** * Resolves the symbols in a C header file. * @param file The file to resolve. * @returns An array of exported symbols. */ resolveSymbols(file: { path: string; rootNode: Parser.SyntaxNode; }): ExportedSymbol[] { const exportedSymbols: ExportedSymbol[] = []; const query = C_DECLARATION_QUERY; const captures = query.captures(file.rootNode); for (const capture of captures) { if (capture.name !== "decl" && capture.name !== "function_definition") { let idNode: Parser.SyntaxNode | null; if (capture.name !== "typedef") { idNode = capture.node.childForFieldName("name"); } else { idNode = capture.node.childForFieldName( "declarator", ); } let name: string; if (!idNode) { name = `#NAPI_UNNAMED_${capture.name.toUpperCase()}_${this .#unnamedSymbolCounter++}`; } else { name = idNode.text; } exportedSymbols.push({ name: name, type: capture.name as SymbolType, node: capture.node, identifierNode: idNode, filepath: file.path, specifiers: [], qualifiers: [], }); } else { const specifiers = capture.node.children .filter((child) => child.type === "storage_class_specifier") .map((child) => child.text); const qualifiers = capture.node.children .filter((child) => child.type === "type_qualifier") .map((child) => child.text); let currentNode = capture.node; // Traverse the tree to find the identifier node // This is a workaround for the fact that the identifier node is not always the first child // (e.g. in pointers or arrays) while ( !currentNode.childForFieldName("declarator") || currentNode.childForFieldName("declarator")?.type !== "identifier" ) { if (!currentNode.childForFieldName("declarator")) { if (!currentNode.firstNamedChild) { throw new Error( `Could not find a named child for ${currentNode.text}`, ); } currentNode = currentNode.firstNamedChild; } else { if (!currentNode.childForFieldName("declarator")) { throw new Error( `Could not find a declarator for ${currentNode.text}`, ); } currentNode = currentNode.childForFieldName( "declarator", ) as Parser.SyntaxNode; } } const type = capture.name === "function_definition" ? "function_definition" : currentNode.type === "function_declarator" ? "function_signature" : "variable"; const idNode = currentNode.childForFieldName("declarator"); if (!idNode) { throw new Error( `Couldn't find identifier node for symbol :\n${capture.node.text}`, ); } exportedSymbols.push({ name: idNode.text, type: type as SymbolType, node: capture.node, identifierNode: idNode, filepath: file.path, specifiers: specifiers as StorageClassSpecifier[], qualifiers: qualifiers as TypeQualifier[], }); } } return exportedSymbols; } } ================================================ FILE: src/languagePlugins/c/headerResolver/queries.ts ================================================ import { cParser } from "../../../helpers/treeSitter/parsers.ts"; import Parser from "tree-sitter"; /** Query that catches every declaration including macros in a header file * Does not catch function definitions */ export const C_DECLARATION_QUERY = new Parser.Query( cParser.getLanguage(), ` (translation_unit [ (declaration) @decl (struct_specifier name: (_)) @struct (enum_specifier) @enum (union_specifier name: (_)) @union (function_definition) @function_definition (type_definition type:[ (struct_specifier name: (_) body: (_)) @struct (struct_specifier !name) (struct_specifier !body) (enum_specifier name: (_) body: (_)) @enum (enum_specifier !name) (enum_specifier !body) (union_specifier name: (_) body: (_)) @union (union_specifier !name) (union_specifier !body) (type_identifier) (primitive_type) ] ) @typedef ]) (preproc_ifdef [ (declaration) @decl (struct_specifier name: (_)) @struct (enum_specifier) @enum (union_specifier name: (_)) @union (function_definition) @function_definition (type_definition type:[ (struct_specifier name: (_) body: (_)) @struct (struct_specifier !name) (struct_specifier !body) (enum_specifier name: (_) body: (_)) @enum (enum_specifier !name) (enum_specifier !body) (union_specifier name: (_) body: (_)) @union (union_specifier !name) (union_specifier !body) (type_identifier) (primitive_type) ] ) @typedef ]) (preproc_def) @macro_constant (preproc_function_def) @macro_function `, ); ================================================ FILE: src/languagePlugins/c/headerResolver/types.ts ================================================ import type Parser from "tree-sitter"; // Constants representing different types of symbols in C export const C_STRUCT_TYPE = "struct"; export const C_UNION_TYPE = "union"; export const C_ENUM_TYPE = "enum"; export const C_FUNCTION_DEFINITION_TYPE = "function_definition"; export const C_FUNCTION_SIGNATURE_TYPE = "function_signature"; export const C_MACRO_FUNCTION_TYPE = "macro_function"; export const C_MACRO_CONSTANT_TYPE = "macro_constant"; export const C_VARIABLE_TYPE = "variable"; export const C_TYPEDEF_TYPE = "typedef"; // Constants representing different storage class specifiers in C export const C_AUTO_SPECIFIER = "auto"; export const C_REGISTER_SPECIFIER = "register"; export const C_STATIC_SPECIFIER = "static"; export const C_EXTERN_SPECIFIER = "extern"; // Constants representing different type qualifiers in C export const C_CONST_QUALIFIER = "const"; export const C_VOLATILE_QUALIFIER = "volatile"; export const C_RESTRICT_QUALIFIER = "restrict"; export const C_ATOMIC_QUALIFIER = "_Atomic"; /** Type alias for the different symbol types */ export type SymbolType = | typeof C_STRUCT_TYPE | typeof C_UNION_TYPE | typeof C_ENUM_TYPE | typeof C_FUNCTION_DEFINITION_TYPE | typeof C_FUNCTION_SIGNATURE_TYPE | typeof C_MACRO_FUNCTION_TYPE | typeof C_MACRO_CONSTANT_TYPE | typeof C_VARIABLE_TYPE | typeof C_TYPEDEF_TYPE; /** Type alias for the different storage class specifiers */ export type StorageClassSpecifier = | typeof C_AUTO_SPECIFIER | typeof C_REGISTER_SPECIFIER | typeof C_STATIC_SPECIFIER | typeof C_EXTERN_SPECIFIER; /** Type alias for the different type qualifiers */ export type TypeQualifier = | typeof C_CONST_QUALIFIER | typeof C_VOLATILE_QUALIFIER | typeof C_RESTRICT_QUALIFIER; /** Interface representing an exported symbol */ export interface ExportedSymbol { /** The name of the symbol */ name: string; /** The type of the symbol (i.e. struct, union, enum, etc.) */ type: SymbolType; /** The storage class specifiers of the symbol (i.e. static, extern) */ specifiers: StorageClassSpecifier[]; /** The type qualifiers of the symbol (i.e. const, volatile) */ qualifiers: TypeQualifier[]; /** The syntax node corresponding to the symbol */ node: Parser.SyntaxNode; /** The syntax node corresponding to the identifier */ identifierNode: Parser.SyntaxNode | null; /** The path of the symbol's file */ filepath: string; } ================================================ FILE: src/languagePlugins/c/includeResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { CSymbolRegistry } from "../symbolRegistry/index.ts"; import { CIncludeResolver } from "./index.ts"; import { join } from "@std/path"; import type { FunctionSignature } from "../symbolRegistry/types.ts"; describe("CIncludeResolver", () => { const cFilesMap = getCFilesMap(); const registry = new CSymbolRegistry(cFilesMap); const includeResolver = new CIncludeResolver(registry); const burgersh = join(cFilesFolder, "burgers.h"); const burgersc = join(cFilesFolder, "burgers.c"); const allh = join(cFilesFolder, "all.h"); const main = join(cFilesFolder, "main.c"); const errorsh = join(cFilesFolder, "errors.h"); const inclusions = includeResolver.getInclusions(); test("resolves inclusions for burgers.h", () => { const bhinclusions = inclusions.get(burgersh); if (!bhinclusions) { throw new Error(`Inclusions not found for: ${burgersh}`); } expect(bhinclusions.filepath).toBe(burgersh); expect(bhinclusions.symbols.size).toBe(0); expect(bhinclusions.internal.children.size).toBe(0); expect(bhinclusions.standard.size).toBe(1); expect(Array.from(bhinclusions.standard.keys())[0]).toBe(""); }); test("resolves inclusions for burgers.c", () => { const bcinclusions = inclusions.get(burgersc); if (!bcinclusions) { throw new Error(`Inclusions not found for: ${burgersc}`); } expect(bcinclusions.filepath).toBe(burgersc); expect(bcinclusions.symbols.size).toBe(32); expect(bcinclusions.internal.children.size).toBe(1); expect(bcinclusions.internal.children.get("burgers.h")).toBeDefined(); expect(bcinclusions.standard.size).toBe(4); const stdincludes = Array.from(bcinclusions.standard.keys()); expect(stdincludes).toContain(""); expect(stdincludes).toContain(""); expect(stdincludes).toContain(""); }); test("resolves inclusions for main.c", () => { const maininclusions = inclusions.get(main); if (!maininclusions) { throw new Error(`Inclusions not found for: ${main}`); } expect(maininclusions.filepath).toBe(main); expect(maininclusions.symbols.size).toBe(32 + 17); expect(maininclusions.symbols.get("create_burger")?.includefile.file.path) .toBe(allh); expect(maininclusions.internal.children.size).toBe(1); expect(maininclusions.internal.children.get("burgers.h")).not.toBeDefined(); expect(maininclusions.internal.children.get("personnel.h")).not .toBeDefined(); expect(maininclusions.internal.children.get("all.h")).toBeDefined(); const allinclusions = maininclusions.internal.children.get("all.h")!; expect(allinclusions.children.get("burgers.h")).toBeDefined(); expect(allinclusions.children.get("personnel.h")).toBeDefined(); expect(maininclusions.standard.size).toBe(2); const stdincludes = Array.from(maininclusions.standard.keys()); expect(stdincludes).toContain(""); }); test("finds signatures for functions", () => { const burgers = registry.getRegistry().get(burgersh)?.symbols; if (!burgers) { throw new Error(`File not found: ${burgersh}`); } const create_burger = burgers.get("create_burger") as FunctionSignature; const destroy = burgers.get("destroy_burger") as FunctionSignature; const get = burgers.get("get_burger_by_id") as FunctionSignature; const cheapest = burgers.get("get_cheapest_burger") as FunctionSignature; expect(create_burger.definition).toBeDefined(); expect(destroy.definition).toBeDefined(); expect(get.definition).toBeDefined(); expect(cheapest.definition).toBeDefined(); }); test("correctly registers inexistant files", () => { const unresolvedIncludes = includeResolver.unresolvedDirectives.get( errorsh, ); expect(unresolvedIncludes).toBeDefined(); expect(unresolvedIncludes).toContainEqual("thisfiledoesnotexist.h"); }); }); ================================================ FILE: src/languagePlugins/c/includeResolver/index.ts ================================================ import type { InclusionNode, Inclusions } from "./types.ts"; import { C_INCLUDE_QUERY, C_STANDARD_INCLUDE_QUERY } from "./queries.ts"; import type { CSymbolRegistry } from "../symbolRegistry/index.ts"; import type Parser from "tree-sitter"; import { type CFile, FunctionDefinition, FunctionSignature, type Symbol, } from "../symbolRegistry/types.ts"; import { dirname, join } from "@std/path"; export class CIncludeResolver { symbolRegistry: Map; files: Map; unresolvedDirectives: Map>; includeDirs: string[] = []; #inclusions?: Map; #inclusionCache: Map; constructor(symbolRegistry: CSymbolRegistry, includeDirs: string[] = []) { this.symbolRegistry = symbolRegistry.getRegistry(); this.files = symbolRegistry.files; this.#inclusionCache = new Map(); this.unresolvedDirectives = new Map(); this.includeDirs = includeDirs; } getFile(filepath: string, sourcepath: string): CFile | undefined { const filepaths = Array.from(this.symbolRegistry.keys()); // 1. Check current file's directory const sourceDir = dirname(sourcepath); const pathfromrelative = join(sourceDir, filepath).replace(/\\/g, "/"); const corresponding1 = filepaths.find((f) => f === pathfromrelative); if (corresponding1) { return this.symbolRegistry.get(corresponding1); } // 2. Check include directories for (const dir of this.includeDirs) { const pathfrominclude = join(dir, filepath).replace(/\\/g, "/"); const corresponding = filepaths.find((f) => f === pathfrominclude); if (corresponding) { return this.symbolRegistry.get(corresponding); } } // 3. Check from workspace root const corresponding2 = filepaths.find((f) => f === filepath); if (corresponding2) { return this.symbolRegistry.get(corresponding2); } return undefined; } /** * Looks for a chain of inclusions starting from the given file * that leads to the given symbol. * @param file The file to start the search from. * @param symbol The symbol to search for. * @returns An array of file paths representing the chain of inclusions. */ findInclusionChain( start: string, symbol: Symbol, ): string[] | undefined { const inclusions = this.getInclusions().get(start); if (!inclusions) { return undefined; } const chain: string[] = []; // Look inside the tree for a node that has a filepath that matches the symbol's file path const findInclusion = (node: InclusionNode): boolean => { if (node.filepath === symbol.declaration.filepath) { chain.push(node.filepath); return true; } for (const child of node.children.values()) { if (findInclusion(child)) { chain.push(node.filepath); return true; } } return false; }; if (findInclusion(inclusions.internal)) { chain.reverse(); // Reverse to get the chain from start to symbol return chain; } return undefined; } /** * Resolves the inclusions of a file. * @param file The file to resolve inclusions for. * @returns The inclusions of the file. */ #resolveInclusions( file: CFile, visitedFiles = new Set(), ): Inclusions { const inclusions: Inclusions = { filepath: file.file.path, symbols: new Map(), internal: { name: ".", children: new Map(), filepath: file.file.path, }, standard: new Map(), }; // Add the current file to the visited set to prevent infinite recursion visitedFiles.add(file.file.path); // Check for file in cache if (this.#inclusionCache.has(file)) { return this.#inclusionCache.get(file)!; } const includeNodes = C_INCLUDE_QUERY.captures(file.file.rootNode); const standardIncludeNodes = C_STANDARD_INCLUDE_QUERY.captures( file.file.rootNode, ); for (const node of includeNodes) { const path = node.node.text; const includedfile = this.getFile(path, file.file.path); if (!includedfile) { if (!this.unresolvedDirectives.has(file.file.path)) { this.unresolvedDirectives.set(file.file.path, new Set()); } this.unresolvedDirectives.get(file.file.path)?.add(path); } else if (!visitedFiles.has(includedfile.file.path)) { // Add the included file's symbols to the current file's symbols for (const [name, symbol] of includedfile.symbols) { inclusions.symbols.set(name, { symbol: symbol, includefile: includedfile, }); } // Recursively resolve inclusions for the included file const nestedInclusions = this.#resolveInclusions( includedfile, visitedFiles, ); for (const [name, symbol] of nestedInclusions.symbols) { inclusions.symbols.set(name, { symbol: symbol.symbol, includefile: includedfile, }); } inclusions.internal.children.set(path, { name: path, filepath: includedfile.file.path, children: nestedInclusions.internal.children, parent: inclusions.internal, }); for (const node of nestedInclusions.standard) { inclusions.standard.set(node[0], node[1]); } // Associate function definitions to their signatures const funcdefs = Array.from( file.symbols.entries().filter(([, s]) => s instanceof FunctionDefinition ).map(([, s]) => s as FunctionDefinition), ); for (const funcdef of funcdefs) { if (inclusions.symbols.has(funcdef.name)) { const symbol = inclusions.symbols.get(funcdef.name); if (symbol && symbol.symbol instanceof FunctionSignature) { funcdef.signature = symbol.symbol; symbol.symbol.definition = funcdef; } } } } } for (const node of standardIncludeNodes) { const child = node.node.childForFieldName("path"); if (child) { inclusions.standard.set(child.text, node.node); } } // Save inclusions in cache this.#inclusionCache.set(file, inclusions); return inclusions; } /** * Retrieves the inclusions of all files and caches the results. * @returns A map of file paths to their inclusions. */ getInclusions() { if (!this.#inclusions) { this.#inclusions = new Map(); for (const file of this.symbolRegistry.values()) { const inclusions = this.#resolveInclusions(file); this.#inclusions.set(file.file.path, inclusions); } } return this.#inclusions; } } ================================================ FILE: src/languagePlugins/c/includeResolver/queries.ts ================================================ import Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; export const C_INCLUDE_QUERY = new Parser.Query( cParser.getLanguage(), ` (preproc_include path: (string_literal (string_content) @include)) `, ); export const C_STANDARD_INCLUDE_QUERY = new Parser.Query( cParser.getLanguage(), ` (preproc_include path: (system_lib_string)) @include `, ); ================================================ FILE: src/languagePlugins/c/includeResolver/types.ts ================================================ import type Parser from "tree-sitter"; import type { CFile, Symbol } from "../symbolRegistry/types.ts"; /** Interface representing the #include statements of a file */ export interface Inclusions { /** The path of the file */ filepath: string; /** The imported symbols from internal imports */ symbols: Map; /** The tree of recursive inclusions of the internal imports */ internal: InclusionNode; /** The list of include directives of the standard imports */ standard: Map; } /** Interface representing a symbol imported through an include directive */ export interface IncludedSymbol { /** The corresponding symbol */ symbol: Symbol; /** The file that allows the usage of this symbol */ includefile: CFile; // Due to recursive inclusions, the file may not be the one that exports // said symbol. Check "all.h" in the test files. } /** Interface representing a node in the internal inclusion tree */ export interface InclusionNode { /** The path to the inclusion relative to its parent, or "." if root */ name: string; /** The complete file path */ filepath: string; /** The inclusions it contains, relative to itself */ children: Map; /** The parent of the node */ parent?: InclusionNode; } ================================================ FILE: src/languagePlugins/c/invocationResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { CSymbolRegistry } from "../symbolRegistry/index.ts"; import { CIncludeResolver } from "../includeResolver/index.ts"; import { CInvocationResolver } from "./index.ts"; import type { Symbol } from "../symbolRegistry/types.ts"; import { join } from "@std/path"; describe("CInvocationResolver", () => { const cFilesMap = getCFilesMap(); const symbolRegistry = new CSymbolRegistry(cFilesMap); const includeResolver = new CIncludeResolver(symbolRegistry); const invocationResolver = new CInvocationResolver(includeResolver); const burgersh = join(cFilesFolder, "burgers.h"); const burgersc = join(cFilesFolder, "burgers.c"); const personnelc = join(cFilesFolder, "personnel.c"); const crashcasesh = join(cFilesFolder, "crashcases.h"); const errorsh = join(cFilesFolder, "errors.h"); const main = join(cFilesFolder, "main.c"); const registry = symbolRegistry.getRegistry(); test("resolves invocations for burgers.h", () => { const symbols = registry.get(burgersh)?.symbols; if (!symbols) { throw new Error(`Symbol not found for: ${burgersh}`); } const sauce = symbols.get("Sauce") as Symbol; const sauceinvocations = invocationResolver.getInvocationsForSymbol(sauce); expect(sauceinvocations.resolved.size).toBe(1); expect(sauceinvocations.resolved.get("ClassicSauces")).toBeDefined(); const fries = symbols.get("Fries") as Symbol; const friesinvocations = invocationResolver.getInvocationsForSymbol(fries); expect(friesinvocations.resolved.size).toBe(1); expect(friesinvocations.resolved.get("Sauce")).toBeDefined(); const burger = symbols.get("Burger") as Symbol; const burgerinvocations = invocationResolver.getInvocationsForSymbol( burger, ); expect(burgerinvocations.resolved.size).toBe(2); expect(burgerinvocations.resolved.get("Condiment")).toBeDefined(); expect(burgerinvocations.resolved.get("Sauce")).toBeDefined(); }); test("resolves invocations for burgers.c", () => { const symbols = registry.get(burgersc)?.symbols; if (!symbols) { throw new Error(`Symbol not found for: ${burgersc}`); } const create_burger = symbols.get("create_burger") as Symbol; const create_burger_invocations = invocationResolver .getInvocationsForSymbol(create_burger); const create_resolved = Array.from( create_burger_invocations.resolved.keys(), ); expect(create_resolved.length).toBe(5); expect(create_resolved).toContain("create_burger"); expect(create_resolved).toContain("Burger"); expect(create_resolved).toContain("Condiment"); expect(create_resolved).toContain("Sauce"); expect(create_resolved).toContain("burger_count"); const destroy_burger = symbols.get("destroy_burger") as Symbol; const destroy_burger_invocations = invocationResolver .getInvocationsForSymbol(destroy_burger); const destroy_resolved = Array.from( destroy_burger_invocations.resolved.keys(), ); expect(destroy_resolved.length).toBe(2); expect(destroy_resolved).toContain("destroy_burger"); expect(destroy_resolved).toContain("Burger"); const symbolsc = registry.get(burgersc)?.symbols; if (!symbolsc) { throw new Error(`Symbol not found for: ${burgersc}`); } const burgers = symbolsc.get("burgers") as Symbol; const burgers_invocations = invocationResolver.getInvocationsForSymbol( burgers, ); const burgers_resolved = Array.from(burgers_invocations.resolved.keys()); expect(burgers_resolved.length).toBe(2); expect(burgers_resolved).toContain("Burger"); expect(burgers_resolved).toContain("MAX_BURGERS"); }); test("resolves invocations for personnel.c", () => { const symbols = registry.get(personnelc)?.symbols; if (!symbols) { throw new Error(`Symbol not found for: ${personnelc}`); } const create_employee = symbols.get("create_employee") as Symbol; const create_employee_invocations = invocationResolver .getInvocationsForSymbol(create_employee); const create_resolved = Array.from( create_employee_invocations.resolved.keys(), ); expect(create_resolved.length).toBe(6); expect(create_resolved).toContain("create_employee"); expect(create_resolved).toContain("Employee"); expect(create_resolved).toContain("Department"); expect(create_resolved).toContain("MAX_EMPLOYEES"); expect(create_resolved).toContain("employee_count"); expect(create_resolved).toContain("employees"); }); test("resolves invocations for main.c", () => { const symbols = registry.get(main)?.symbols; if (!symbols) { throw new Error(`Symbol not found for: ${main}`); } const main_func = symbols.get("main") as Symbol; const main_invocations = invocationResolver.getInvocationsForSymbol( main_func, ); const main_resolved = Array.from(main_invocations.resolved.keys()); expect(main_resolved.length).toBe(9); expect(main_resolved).toContain("create_burger"); expect(main_resolved).toContain("Burger"); expect(main_resolved).toContain("create_employee"); expect(main_resolved).toContain("Employee"); expect(main_resolved).toContain("Condiment"); expect(main_resolved).toContain("Sauce"); }); test("Crash Cases", () => { const symbols = registry.get(crashcasesh)?.symbols; if (!symbols) { throw new Error(`Symbol not found for: ${crashcasesh}`); } const crash = symbols.get("CpuFastFill") as Symbol; const crash_invocations = invocationResolver.getInvocationsForSymbol(crash); const crash_resolved = Array.from(crash_invocations.resolved.keys()); expect(crash_resolved.length).toBe(2); expect(crash_resolved).toContain("CpuFastSet"); expect(crash_resolved).toContain("CPU_FAST_SET_SRC_FIXED"); }); test("Errors", () => { const symbols = registry.get(errorsh)?.symbols; if (!symbols) { throw new Error(`Symbol not found for: ${errorsh}`); } const xbox = symbols.get("xbox") as Symbol; const xbox_invocations = invocationResolver.getInvocationsForSymbol(xbox); const xbox_resolved = Array.from(xbox_invocations.resolved.keys()); expect(xbox_resolved.length).toBe(1); expect(xbox_resolved).toContain("#NAPI_UNNAMED_ENUM_0"); }); }); ================================================ FILE: src/languagePlugins/c/invocationResolver/index.ts ================================================ import type { Invocations } from "./types.ts"; import { C_INVOCATION_QUERY, C_MACRO_CONTENT_QUERY } from "./queries.ts"; import type { CIncludeResolver } from "../includeResolver/index.ts"; import { type CFile, DataType, EnumMember, FunctionDefinition, FunctionSignature, type Symbol, } from "../symbolRegistry/types.ts"; import type Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; import type { IncludedSymbol } from "../includeResolver/types.ts"; export class CInvocationResolver { includeResolver: CIncludeResolver; constructor(includeResolver: CIncludeResolver) { this.includeResolver = includeResolver; } getInvocationsForNode( node: Parser.SyntaxNode, filepath: string, symbolname: string | undefined = undefined, ): Invocations { const availableSymbols = this.includeResolver .getInclusions() .get(filepath)?.symbols; const currentfile = this.includeResolver.symbolRegistry.get(filepath)!; const localSymbols = currentfile.symbols; const unresolved = new Set(); const resolved = new Map(); const captures = C_INVOCATION_QUERY.captures(node); for (const capture of captures) { const name = capture.node.text; // if the symbol name is the same as the one we are looking at, skip it if (symbolname && name === symbolname) { continue; } if (availableSymbols && availableSymbols.has(name)) { const availableSymbol = availableSymbols.get(name); if (!availableSymbol) { unresolved.add(name); continue; } resolved.set(name, availableSymbol); } else if (localSymbols && localSymbols.has(name)) { const localSymbol = localSymbols.get(name); if (!localSymbol) { unresolved.add(name); continue; } resolved.set(name, { includefile: currentfile, symbol: localSymbol, }); } else { unresolved.add(name); } } // Check for macro invocations // The logic of a macro is set in a single (preproc_arg) node. // If we parse that node's text, we can find the invocations. const macroCaptures = C_MACRO_CONTENT_QUERY.captures(node); for (const capture of macroCaptures) { const contentNode = cParser.parse(capture.node.text).rootNode; const contentInvocations = this.getInvocationsForNode( contentNode, filepath, symbolname, ); for (const [key, value] of contentInvocations.resolved) { if (!resolved.has(key)) { resolved.set(key, value); } } for (const value of contentInvocations.unresolved) { unresolved.add(value); } } return { resolved, unresolved, }; } #getSymbolFile(symbol: Symbol): CFile { const file = this.includeResolver.symbolRegistry.get( symbol.declaration.filepath, ); if (!file) { throw Error(`File ${symbol.declaration.filepath} not found!`); // Exclamation point because that's really out of the ordinary } return file; } getInvocationsForSymbol(symbol: Symbol) { const filepath = symbol.declaration.filepath; const node = symbol.declaration.node; const name = symbol.name; const invocations = this.getInvocationsForNode(node, filepath, name); const resolved = invocations.resolved; if (symbol instanceof FunctionSignature && symbol.definition) { resolved.set(symbol.name, { includefile: this.#getSymbolFile(symbol.definition), symbol: symbol.definition, }); } if (symbol instanceof FunctionDefinition && symbol.signature) { resolved.set(symbol.name, { includefile: this.#getSymbolFile(symbol.signature), symbol: symbol.signature, }); } if (symbol instanceof DataType) { const typedefs = symbol.typedefs; for (const [key, value] of typedefs) { resolved.set(key, { includefile: this.#getSymbolFile(value), symbol: value, }); } } // Replace enum members with their parent enum for (const [key, value] of resolved) { if (value.symbol instanceof EnumMember) { const parent = value.symbol.parent; if (!resolved.has(parent.name) && parent.name !== symbol.name) { resolved.set(parent.name, { includefile: value.includefile, symbol: parent, }); } resolved.delete(key); } } return { resolved: invocations.resolved, unresolved: invocations.unresolved, }; } getInvocationsForFile(filepath: string): Invocations { let symbols = this.includeResolver.symbolRegistry.get(filepath)?.symbols; if (!symbols) { symbols = new Map(); } let unresolved = new Set(); const resolved = new Map(); for (const symbol of symbols.values()) { const invocations = this.getInvocationsForSymbol(symbol); unresolved = new Set([...unresolved, ...invocations.unresolved]); for (const [key, value] of invocations.resolved) { if (!resolved.has(key)) { resolved.set(key, value); } } } return { resolved, unresolved, }; } } ================================================ FILE: src/languagePlugins/c/invocationResolver/queries.ts ================================================ import Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; export const C_INVOCATION_QUERY = new Parser.Query( cParser.getLanguage(), ` (type_identifier) @type (identifier) @id `, // The nuclear option, but unlike C# we can afford to do it. ); export const C_MACRO_CONTENT_QUERY = new Parser.Query( cParser.getLanguage(), ` (preproc_arg) @macro `, ); ================================================ FILE: src/languagePlugins/c/invocationResolver/types.ts ================================================ import type { IncludedSymbol } from "../includeResolver/types.ts"; /** Interface representing the invoked symbols in a file */ export interface Invocations { /** Symbols that are part of the project */ resolved: Map; /** Symbols that are not part of the project or local variables */ unresolved: Set; } ================================================ FILE: src/languagePlugins/c/metrics/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CMetricsAnalyzer } from "./index.ts"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { join } from "@std/path"; describe("CMetricsAnalyzer", () => { const analyzer = new CMetricsAnalyzer(); const files = getCFilesMap(); const analyzeFile = (filePath: string) => { const absolutePath = join(cFilesFolder, filePath); const file = files.get(absolutePath); if (!file) { throw new Error(`File not found: ${absolutePath}`); } return analyzer.analyzeNode(file.rootNode); }; test("burgers.c", () => { const metrics = analyzeFile("burgers.c"); expect(metrics.characterCount >= 1485).toBe(true); expect(metrics.codeCharacterCount < 1485).toBe(true); expect(metrics.linesCount >= 53).toBe(true); expect(metrics.codeLinesCount < 53).toBe(true); expect(metrics.cyclomaticComplexity >= 6).toBe(true); }); test("burgers.h", () => { const metrics = analyzeFile("burgers.h"); expect(metrics.characterCount >= 1267).toBe(true); expect(metrics.codeCharacterCount < 1267).toBe(true); expect(metrics.linesCount >= 71).toBe(true); expect(metrics.codeLinesCount < 71).toBe(true); expect(metrics.cyclomaticComplexity).toBe(1); }); test("crashcases.h", () => { const metrics = analyzeFile("crashcases.h"); expect(metrics.characterCount >= 956).toBe(true); expect(metrics.codeCharacterCount < 956).toBe(true); expect(metrics.linesCount >= 33).toBe(true); expect(metrics.codeLinesCount < 33).toBe(true); expect(metrics.cyclomaticComplexity).toBe(0); }); }); ================================================ FILE: src/languagePlugins/c/metrics/index.ts ================================================ import { C_COMMENT_QUERY, C_COMPLEXITY_QUERY } from "./queries.ts"; import type { CComplexityMetrics, CodeCounts, CommentSpan } from "./types.ts"; import type Parser from "tree-sitter"; export class CMetricsAnalyzer { /** * Calculates metrics for a C symbol. * @param node - The syntax node to analyze. * @returns An object containing the complexity metrics. */ public analyzeNode(node: Parser.SyntaxNode): CComplexityMetrics { if (node.type === "preproc_function_def") { const value = node.childForFieldName("value"); if (value) { node = value; } } const complexityCount = this.getComplexityCount(node); const linesCount = node.endPosition.row - node.startPosition.row + 1; const codeCounts = this.getCodeCounts(node); const codeLinesCount = codeCounts.lines; const characterCount = node.endIndex - node.startIndex; const codeCharacterCount = codeCounts.characters; return { cyclomaticComplexity: complexityCount, linesCount, codeLinesCount, characterCount, codeCharacterCount, }; } private getComplexityCount(node: Parser.SyntaxNode): number { const complexityMatches = C_COMPLEXITY_QUERY.captures(node); return complexityMatches.length; } /** * Finds comments in the given node and returns their spans. * @param node - The AST node to analyze * @returns An object containing pure comment lines and comment spans */ private findComments( node: Parser.SyntaxNode, lines: string[], ): { pureCommentLines: Set; commentSpans: CommentSpan[]; } { const pureCommentLines = new Set(); const commentSpans: CommentSpan[] = []; const commentCaptures = C_COMMENT_QUERY.captures(node); for (const capture of commentCaptures) { const commentNode = capture.node; // Record the comment span for character counting commentSpans.push({ start: { row: commentNode.startPosition.row, column: commentNode.startPosition.column, }, end: { row: commentNode.endPosition.row, column: commentNode.endPosition.column, }, }); // Check if the comment starts at the beginning of the line (ignoring whitespace) const lineIdx = commentNode.startPosition.row - node.startPosition.row; if (lineIdx >= 0 && lineIdx < lines.length) { const lineText = lines[lineIdx]; const textBeforeComment = lineText.substring( 0, commentNode.startPosition.column, ); // If there's only whitespace before the comment, it's a pure comment line if (textBeforeComment.trim().length === 0) { for ( let line = commentNode.startPosition.row; line <= commentNode.endPosition.row; line++ ) { pureCommentLines.add(line); } } } } return { pureCommentLines, commentSpans }; } /** * Finds all empty lines in a node * * @param node The syntax node to analyze * @param lines The lines of text in the node * @returns Set of line numbers that are empty */ private findEmptyLines( node: Parser.SyntaxNode, lines: string[], ): Set { const emptyLines = new Set(); for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; if (lines[i].trim().length === 0) { emptyLines.add(lineIndex); } } return emptyLines; } private getCodeCounts(node: Parser.SyntaxNode): CodeCounts { const lines = node.text.split(/\r?\n/); const linesCount = lines.length; // Find comments and their spans const { pureCommentLines, commentSpans } = this.findComments(node, lines); // Find empty lines const emptyLines = this.findEmptyLines(node, lines); // Calculate code lines const nonCodeLines = new Set([...pureCommentLines, ...emptyLines]); const codeLinesCount = linesCount - nonCodeLines.size; let codeCharCount = 0; // Process each line individually for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; const line = lines[i]; // Skip empty lines and pure comment lines if (emptyLines.has(lineIndex) || pureCommentLines.has(lineIndex)) { continue; } // Process line for code characters let lineText = line; // Remove comment content from the line if present for (const span of commentSpans) { if (span.start.row === lineIndex) { // Comment starts on this line lineText = lineText.substring(0, span.start.column); } } // Count normalized code characters (trim excessive whitespace) const normalizedText = lineText.trim().replace(/\s+/g, " "); codeCharCount += normalizedText.length; } return { lines: codeLinesCount, characters: codeCharCount, }; } } ================================================ FILE: src/languagePlugins/c/metrics/queries.ts ================================================ import Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; // Tree-sitter query to find complexity-related nodes export const C_COMPLEXITY_QUERY = new Parser.Query( cParser.getLanguage(), ` (if_statement) @complexity (while_statement) @complexity (for_statement) @complexity (do_statement) @complexity (case_statement) @complexity (conditional_expression) @complexity (preproc_ifdef) @complexity `, ); export const C_COMMENT_QUERY = new Parser.Query( cParser.getLanguage(), ` (comment) @comment `, ); ================================================ FILE: src/languagePlugins/c/metrics/types.ts ================================================ /** * Interface for code volumes */ export interface CodeCounts { /** Number of lines of code */ lines: number; /** Number of characters of code */ characters: number; } export interface CommentSpan { start: { row: number; column: number }; end: { row: number; column: number }; } /** * Represents complexity metrics for a C symbol */ export interface CComplexityMetrics { /** Cyclomatic complexity (McCabe complexity) */ cyclomaticComplexity: number; /** Code lines (not including whitespace or comments) */ codeLinesCount: number; /** Total lines (including whitespace and comments) */ linesCount: number; /** Characters of actual code (excluding comments and excessive whitespace) */ codeCharacterCount: number; /** Total characters in the entire symbol */ characterCount: number; } ================================================ FILE: src/languagePlugins/c/symbolRegistry/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { CSymbolRegistry } from "./index.ts"; import { DataType, FunctionDefinition, FunctionSignature, Typedef, Variable, } from "./types.ts"; import { join } from "@std/path"; describe("CSymbolRegistry", () => { const cFilesMap = getCFilesMap(); const registry = new CSymbolRegistry(cFilesMap).getRegistry(); const burgersh = join(cFilesFolder, "burgers.h"); const burgersc = join(cFilesFolder, "burgers.c"); const oldmanh = join(cFilesFolder, "oldman.h"); const hsymbols = registry.get(burgersh); if (!hsymbols) { throw new Error(`File not found: ${burgersh}`); } const csymbols = registry.get(burgersc); if (!csymbols) { throw new Error(`File not found: ${burgersc}`); } test("registers symbols for burgers.h", () => { expect(hsymbols.type).toBe(".h"); expect(hsymbols.symbols.size).toBe(32); }); test("registers datatypes for burgers.h", () => { expect(hsymbols.symbols.get("Burger")).toBeDefined(); expect(hsymbols.symbols.get("Sauce")).toBeDefined(); expect(hsymbols.symbols.get("Condiment")).toBeDefined(); expect(hsymbols.symbols.get("ClassicSauces")).toBeDefined(); expect(hsymbols.symbols.get("Drink_t")).toBeDefined(); expect(hsymbols.symbols.get("Burger")).toBeInstanceOf(DataType); expect(hsymbols.symbols.get("Sauce")).toBeInstanceOf(DataType); expect(hsymbols.symbols.get("Condiment")).toBeInstanceOf(DataType); expect(hsymbols.symbols.get("ClassicSauces")).toBeInstanceOf(DataType); expect(hsymbols.symbols.get("Drink_t")).toBeInstanceOf(DataType); }); test("registers enum members for burgers.h", () => { expect(hsymbols.symbols.get("NONE")).toBeDefined(); expect(hsymbols.symbols.get("SALAD")).toBeDefined(); expect(hsymbols.symbols.get("TOMATO")).toBeDefined(); expect(hsymbols.symbols.get("ONION")).toBeDefined(); expect(hsymbols.symbols.get("CHEESE")).toBeDefined(); expect(hsymbols.symbols.get("PICKLE")).toBeDefined(); expect(hsymbols.symbols.get("KETCHUP")).toBeDefined(); expect(hsymbols.symbols.get("MUSTARD")).toBeDefined(); expect(hsymbols.symbols.get("BBQ")).toBeDefined(); expect(hsymbols.symbols.get("MAYO")).toBeDefined(); expect(hsymbols.symbols.get("SPICY")).toBeDefined(); expect(hsymbols.symbols.get("COKE")).toBeDefined(); expect(hsymbols.symbols.get("ICED_TEA")).toBeDefined(); expect(hsymbols.symbols.get("LEMONADE")).toBeDefined(); expect(hsymbols.symbols.get("WATER")).toBeDefined(); expect(hsymbols.symbols.get("COFFEE")).toBeDefined(); }); test("registers typedefs for burgers.h", () => { const drink = hsymbols.symbols.get("Drink"); const fries = hsymbols.symbols.get("Fries"); expect(drink).toBeDefined(); expect(fries).toBeDefined(); expect(drink).toBeInstanceOf(Typedef); expect(fries).toBeInstanceOf(Typedef); expect((drink as Typedef).datatype).toBeDefined(); expect((fries as Typedef).datatype).not.toBeDefined(); expect((drink as Typedef).datatype).toBeInstanceOf(DataType); expect((drink as Typedef).datatype?.name).toBe("Drink_t"); }); test("registers functions for burgers.h", () => { const max = hsymbols.symbols.get("MAX"); const create = hsymbols.symbols.get("create_burger"); const destroy = hsymbols.symbols.get("destroy_burger"); const get = hsymbols.symbols.get("get_burger_by_id"); const cheapest = hsymbols.symbols.get("get_cheapest_burger"); expect(max).toBeDefined(); expect(create).toBeDefined(); expect(destroy).toBeDefined(); expect(get).toBeDefined(); expect(cheapest).toBeDefined(); expect(max).toBeInstanceOf(FunctionDefinition); expect(create).toBeInstanceOf(FunctionSignature); expect(destroy).toBeInstanceOf(FunctionSignature); expect(get).toBeInstanceOf(FunctionSignature); expect(cheapest).toBeInstanceOf(FunctionSignature); expect((max as FunctionDefinition).isMacro).toBe(true); expect((create as FunctionSignature).isMacro).toBe(false); expect((destroy as FunctionSignature).isMacro).toBe(false); expect((get as FunctionSignature).isMacro).toBe(false); expect((cheapest as FunctionSignature).isMacro).toBe(false); expect((max as FunctionDefinition).declaration.node.type).toBe( "preproc_function_def", ); expect((create as FunctionSignature).definition).not.toBeDefined(); expect((destroy as FunctionSignature).definition).not.toBeDefined(); expect((get as FunctionSignature).definition).not.toBeDefined(); expect((cheapest as FunctionSignature).definition).not.toBeDefined(); }); test("registers variables for burgers.h", () => { const burgers_count = hsymbols.symbols.get("burger_count"); const max_burgers = hsymbols.symbols.get("MAX_BURGERS"); const burgers_h = hsymbols.symbols.get("BURGERS_H"); expect(burgers_h).toBeDefined(); expect(max_burgers).toBeDefined(); expect(burgers_count).toBeDefined(); expect(burgers_h).toBeInstanceOf(Variable); expect(max_burgers).toBeInstanceOf(Variable); expect(burgers_count).toBeInstanceOf(Variable); expect((burgers_h as Variable).isMacro).toBe(true); expect((max_burgers as Variable).isMacro).toBe(true); expect((burgers_count as Variable).isMacro).toBe(false); }); test("registers symbols for burgers.c", () => { expect(csymbols.type).toBe(".c"); expect(csymbols.symbols.size).toBe(5); }); test("registers variables for burgers.c", () => { const burgers = csymbols.symbols.get("burgers"); expect(burgers).toBeDefined(); expect(burgers).toBeInstanceOf(Variable); expect((burgers as Variable).isMacro).toBe(false); }); test("registers main", () => { const main = join(cFilesFolder, "main.c"); const mainsymbols = registry.get(main); if (!mainsymbols) { throw new Error(`File not found: ${main}`); } expect(mainsymbols.type).toBe(".c"); expect(mainsymbols.symbols.size).toBe(1); const mainfunc = mainsymbols.symbols.get("main"); expect(mainfunc).toBeDefined(); expect(mainfunc).toBeInstanceOf(FunctionDefinition); expect((mainfunc as FunctionDefinition).isMacro).toBe(false); expect((mainfunc as FunctionDefinition).declaration.node.type).toBe( "function_definition", ); expect((mainfunc as FunctionDefinition).declaration.filepath).toBe(main); expect((mainfunc as FunctionDefinition).declaration.node.type).toBe( "function_definition", ); }); test("prioritizes typedefs", () => { const oldmanSymbols = registry.get(oldmanh); if (!oldmanSymbols) { throw new Error(`File not found: ${oldmanh}`); } expect(oldmanSymbols.type).toBe(".h"); expect(oldmanSymbols.symbols.size).toBe(14); const oldman = oldmanSymbols.symbols.get("OldMan"); expect(oldman).toBeDefined(); expect(oldman).toBeInstanceOf(Typedef); expect((oldman as Typedef).datatype).not.toBeDefined(); expect((oldman as Typedef).name).toBe("OldMan"); expect((oldman as Typedef).declaration.node.type).toBe("type_definition"); expect((oldman as Typedef).declaration.filepath).toBe(oldmanh); }); }); ================================================ FILE: src/languagePlugins/c/symbolRegistry/index.ts ================================================ import type { ExportedSymbol } from "../headerResolver/types.ts"; import { CHeaderResolver } from "../headerResolver/index.ts"; import { CFile, DataType, Enum, FunctionDefinition, FunctionSignature, type Symbol, Typedef, Variable, } from "./types.ts"; import { C_TYPEDEF_TYPE_QUERY } from "./queries.ts"; import type Parser from "tree-sitter"; export class CSymbolRegistry { headerResolver: CHeaderResolver; files: Map; #registry?: Map; constructor( files: Map, ) { this.headerResolver = new CHeaderResolver(); this.files = files; } /** * Converts an ExportedSymbol to a Symbol. * @param es The ExportedSymbol to convert. * @returns The converted Symbol. */ #convertSymbol(es: ExportedSymbol): Symbol { if (["struct", "union"].includes(es.type)) { return new DataType( es.name, es, ); } if (es.type === "enum") { return new Enum( es.name, es, ); } if (es.type === "typedef") { return new Typedef( es.name, es, ); } if (es.type === "function_definition" || es.type === "macro_function") { return new FunctionDefinition( es.name, es, es.node.type === "preproc_function_def", ); } if (es.type === "function_signature") { return new FunctionSignature( es.name, es, es.node.type === "preproc_function_def", ); } if (es.type === "variable" || es.type === "macro_constant") { return new Variable( es.name, es, es.node.type === "preproc_def", ); } throw new Error(`Could not find symbol type for ${es.name} (${es.type})`); } /** * Builds the symbol registry by iterating over all header and source files. * It resolves the symbols in the header files and associates them with their * corresponding source files. */ #buildRegistry() { this.#registry = new Map(); // Iterate over all header files and build the registry const headerFiles = Array.from(this.files.values()).filter((file) => file.path.endsWith(".h") ); for (const file of headerFiles) { const exportedSymbols = this.headerResolver.resolveSymbols(file); const header = new CFile(file, ".h"); for (const es of exportedSymbols) { const symbol = this.#convertSymbol(es); if (symbol instanceof DataType) { // Typedefs get priority over structs if they both have the same name // That is because if they both have the same name, that means that the typedef // also includes the struct's definition if (!header.symbols.has(symbol.name)) { header.symbols.set(symbol.name, symbol); } } else { header.symbols.set(symbol.name, symbol); } if (symbol instanceof Enum) { for (const [, enumMember] of symbol.members) { header.symbols.set(enumMember.name, enumMember); } } } // Add the header file to the registry this.#registry.set(file.path, header); } // Iterate over all source files to find function definitions. const sourceFiles = Array.from(this.files.values()).filter((file) => file.path.endsWith(".c") ); for (const file of sourceFiles) { // We still need to resolve the symbols in the source files // because they can depend on eachother. // However they are not global. const exportedSymbols = this.headerResolver.resolveSymbols(file); const source = new CFile(file, ".c"); for (const es of exportedSymbols) { const symbol = this.#convertSymbol(es); if (symbol instanceof DataType && !source.symbols.has(symbol.name)) { // Typedefs get priority over structs if they both have the same name // That is because if they both have the same name, that means that the typedef // also includes the struct's definition source.symbols.set(symbol.name, symbol); } else { source.symbols.set(symbol.name, symbol); } if (symbol instanceof Enum) { for (const [name, enumMember] of symbol.members) { source.symbols.set(name, enumMember); } } } // Add the source file to the registry this.#registry.set(file.path, source); } // Associate typedefs with their corresponding data types for (const [, header] of this.#registry.entries()) { for ( const symbol of header.symbols .values() .filter((s) => s instanceof Typedef) .map((s) => s as Typedef) ) { const typedefNode = symbol.declaration.node; const typeCaptures = C_TYPEDEF_TYPE_QUERY.captures(typedefNode); if (typeCaptures.length > 0) { const typeCapture = typeCaptures[0]; const typeNode = typeCapture.node; const typeName = typeNode.text; for (const [, header] of this.#registry.entries()) { if (header.symbols.has(typeName)) { const dataType = header.symbols.get(typeName); if (dataType instanceof DataType) { symbol.datatype = dataType; dataType.typedefs.set(symbol.name, symbol); } } } } } } } /** * Returns the symbol registry. If the registry has not been built yet, it builds it first. * @returns The symbol registry. */ getRegistry(): Map { if (!this.#registry) { this.#buildRegistry(); } if (!this.#registry) { throw new Error("Couldn't build registry for project"); } return this.#registry; } } ================================================ FILE: src/languagePlugins/c/symbolRegistry/queries.ts ================================================ import Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; export const C_FUNCTION_DEF_QUERY = new Parser.Query( cParser.getLanguage(), ` (function_definition) @fdef `, ); export const C_TYPEDEF_TYPE_QUERY = new Parser.Query( cParser.getLanguage(), ` (type_definition type: [ (type_identifier) @name (struct_specifier name: (type_identifier) @name) (enum_specifier name: (type_identifier) @name) (union_specifier name: (type_identifier) @name) ]) `, ); ================================================ FILE: src/languagePlugins/c/symbolRegistry/types.ts ================================================ import { C_VARIABLE_TYPE, type ExportedSymbol, } from "../headerResolver/types.ts"; import type Parser from "tree-sitter"; /** Interface representing a C symbol */ export class Symbol { /** The name of the symbol */ name: string; /** The corresponding ExportedSymbol object */ declaration: ExportedSymbol; constructor(name: string, declaration: ExportedSymbol) { this.name = name; this.declaration = declaration; } } /** Interface representing a C function signature */ export class FunctionSignature extends Symbol { /** The definition of the function in a source file */ definition?: FunctionDefinition; /** Whether the function is a macro or not */ isMacro: boolean; constructor( name: string, declaration: ExportedSymbol, isMacro: boolean, ) { super(name, declaration); this.isMacro = isMacro; this.definition = undefined; } } /** Interface representing a C function definition */ export class FunctionDefinition extends Symbol { /** The declaration of the function in a header file */ signature?: FunctionSignature; /** Whether the function is a macro or not */ isMacro: boolean; constructor(name: string, declaration: ExportedSymbol, isMacro: boolean) { super(name, declaration); this.isMacro = isMacro; this.signature = undefined; } } /** Interface representing a C variable */ export class DataType extends Symbol { typedefs: Map; constructor(name: string, declaration: ExportedSymbol) { super(name, declaration); this.typedefs = new Map(); } } /** Interface representing a C typedef */ export class Typedef extends Symbol { datatype?: DataType; } export class Variable extends Symbol { /** Whether the variable is a macro or not */ isMacro: boolean; constructor(name: string, declaration: ExportedSymbol, isMacro: boolean) { super(name, declaration); this.isMacro = isMacro; } } export class EnumMember extends Symbol { parent: Enum; constructor(name: string, declaration: ExportedSymbol, parent: Enum) { super(name, declaration); this.parent = parent; } } export class Enum extends DataType { members: Map; constructor(name: string, declaration: ExportedSymbol) { super(name, declaration); const enumerators = declaration.node.childForFieldName("body"); this.members = new Map(); if (enumerators) { for ( const enumerator of enumerators.children.filter((c) => c.type === "enumerator" ) ) { const idNode = enumerator.childForFieldName("name"); if (!idNode) { continue; } const member = new EnumMember( idNode.text, { name: idNode.text, type: C_VARIABLE_TYPE, filepath: declaration.filepath, specifiers: [], qualifiers: ["const"], node: enumerator, identifierNode: idNode, }, this, ); this.members.set(enumerator.text, member); } } } } export const C_SOURCE_FILE = ".c"; export const C_HEADER_FILE = ".h"; export type CFileType = typeof C_SOURCE_FILE | typeof C_HEADER_FILE; /** Interface representing a C file */ export class CFile { /** The corresponding header file */ file: { path: string; rootNode: Parser.SyntaxNode; }; /** The symbols defined in the header file */ symbols: Map; /** The type of the file (i.e. .c or .h) */ type: CFileType; constructor( file: { path: string; rootNode: Parser.SyntaxNode }, filetype: CFileType, ) { this.file = file; this.type = filetype; this.symbols = new Map(); } } ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/.clangd ================================================ CompileFlags: Add: [-xc, -std=c99] ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/.napirc ================================================ { "language": "c", "project": { "include": [ "**/*.c", "**/*.h" ], "exclude": [ ".git/**", "**/dist/**", "**/build/**", "**/bin/**", "**/obj/**", "**/packages/**", "**/.vs/**", "**/TestResults/**", "**/*.user", "**/*.suo", "**/.nuget/**", "**/artifacts/**", "**/packages/**", "**/util/**", "**/test/**", "**/perf/**", "**/napi_out/**", "**/rapports/**" ] }, "outDir": "napi_out", "metrics": { "file": { "maxChar": 100000, "maxLine": 1000, "maxDep": 10 }, "symbol": { "maxChar": 50000, "maxLine": 500, "maxDep": 5 } } } ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/all.h ================================================ #include "burgers.h" #include "personnel.h" ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/burgers.c ================================================ // TOTAL SYMBOL COUNT : 1 // TOTAL FUNCTION COUNT : 0 #include "burgers.h" #include #include #include static struct Burger* burgers[MAX_BURGERS]; struct Burger* create_burger(char name[50], enum Condiment condiments[5], union Sauce sauce) { struct Burger* new_burger = malloc(sizeof(struct Burger)); if (new_burger == NULL) { return NULL; // Memory allocation failed } new_burger->id = ++burger_count; strncpy(new_burger->name, name, sizeof(new_burger->name) - 1); new_burger->name[sizeof(new_burger->name) - 1] = '\0'; // Ensure null-termination memcpy(new_burger->condiments, condiments, sizeof(new_burger->condiments)); new_burger->sauce = sauce; return new_burger; } void destroy_burger(struct Burger* burger) { if (burger != NULL) { free(burger); } } struct Burger* get_burger_by_id(int id) { for (int i = 0; i < burger_count; i++) { if (burgers[i] != NULL && burgers[i]->id == id) { return burgers[i]; } } return NULL; // Burger not found } struct Burger* get_cheapest_burger() { struct Burger* cheapest_burger = NULL; float min_price = __FLT_MAX__; // Initialize to maximum float value for (int i = 0; i < burger_count; i++) { if (burgers[i] != NULL && burgers[i]->price < min_price) { min_price = burgers[i]->price; cheapest_burger = burgers[i]; } } return cheapest_burger; } ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/burgers.h ================================================ // TOTAL SYMBOL COUNT : 16 // TOTAL FUNCTION COUNT : 5 #ifndef BURGERS_H #define BURGERS_H #include #define MAX_BURGERS 100 #define MAX(x, y) ((x) > (y) ? (x) : (y)) enum Condiment { NONE = 0, SALAD = 30, TOMATO = 40, ONION = 50, CHEESE = 60, PICKLE = 70, }; enum ClassicSauces { KETCHUP = 0, MAYO = 1, MUSTARD = 2, BBQ = 3, SPICY = 4, }; union Sauce { enum ClassicSauces classic_sauce; char custom_sauce[50]; }; typedef struct { int id; union Sauce sauce; _Bool salted; float price; } Fries; typedef enum Drink_t { COKE = 0, ICED_TEA = 1, LEMONADE = 2, COFFEE = 3, WATER = 4, } Drink; struct Burger { int id; char name[50]; float price; enum Condiment condiments[5]; union Sauce sauce; }; const struct Burger classicBurger = { .id = 1, .name = "Classic Burger", .price = 5.99, .condiments = {SALAD, TOMATO, ONION, CHEESE}, .sauce = {.classic_sauce = KETCHUP} }; static int burger_count = 0; struct Burger* create_burger(char name[50], enum Condiment condiments[5], union Sauce sauce); void destroy_burger(struct Burger* burger); struct Burger* get_burger_by_id(int id); struct Burger* get_cheapest_burger(); #endif ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/crashcases.h ================================================ // Code segments taken from the Pokémon Fire Red/Leaf Green decomp project // https://github.com/pret/pokefirered #define CPU_FAST_SET_SRC_FIXED 0x01000000 void CpuFastSet(const void *src, void *dest, unsigned int control); #define CpuFastFill(value, dest, size) \ { \ vu32 tmp = (vu32)(value); \ CpuFastSet((void *)&tmp, \ dest, \ CPU_FAST_SET_SRC_FIXED | ((size)/(32/8) & 0x1FFFFF)); \ } struct ObjectEvent { int id; }; struct Sprite { int x; int y; }; int PlaceholderFunction(struct ObjectEvent *oe, struct Sprite *s); int (*const gMovementTypeFuncs_WanderAround[])(struct ObjectEvent *, struct Sprite *) = { PlaceholderFunction, PlaceholderFunction, PlaceholderFunction, PlaceholderFunction, PlaceholderFunction, PlaceholderFunction, PlaceholderFunction, }; ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/errors.h ================================================ #include "thisfiledoesnotexist.h" // Unnamed enum enum { ONE, TWO, THREE, VIVA, L, ALGERIE }; int xbox = ONE; struct { int useless; // USELESS? }; // Non-standard typedef struct Salut_t { int id; int waow; } typedef Salut; // Syntax error int x =??; ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/main.c ================================================ #include "all.h" #include int main(void) { struct Burger* burger = create_burger("Cheeseburger", (enum Condiment[]){SALAD, ONION}, (union Sauce){KETCHUP}); printf("Burger ID: %d\n", burger->id); printf("Burger Name: %s\n", burger->name); printf("Burger Price: %.2f\n", burger->price); printf("Burger Condiments: "); for (int i = 0; i < 5; i++) { if (burger->condiments[i] != 0) { printf("%d ", burger->condiments[i]); } } printf("\n"); printf("Burger Sauce: %d\n", burger->sauce.classic_sauce); Employee* emp1 = create_employee(1, "Alice", "Manager", IT, 75000.0); print_employee_details(emp1); return 0; } ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/oldman.h ================================================ // Code segments taken from the Pokémon Fire Red/Leaf Green decomp project // https://github.com/pret/pokefirered #include #define BARD_SONG_LENGTH 14 #define PLAYER_NAME_LENGTH 7 #define TRAINER_ID_LENGTH 4 #define NUM_STORYTELLER_TALES 5 #define GIDDY_MAX_TALES 6 #define GIDDY_MAX_QUESTIONS 10 #define NUM_TRADER_ITEMS 5 struct MauvilleManCommon { int id; }; struct MauvilleManBard { /*0x00*/ int id; /*0x02*/ int songLyrics[BARD_SONG_LENGTH]; /*0x0E*/ int temporaryLyrics[BARD_SONG_LENGTH]; /*0x1A*/ int playerName[PLAYER_NAME_LENGTH + 1]; /*0x22*/ int filler_2DB6[0x3]; /*0x25*/ int playerTrainerId[TRAINER_ID_LENGTH]; /*0x29*/ bool hasChangedSong; /*0x2A*/ int language; }; /*size = 0x2C*/ struct MauvilleManStoryteller { int id; bool alreadyRecorded; int filler2[2]; int gameStatIDs[NUM_STORYTELLER_TALES]; int trainerNames[NUM_STORYTELLER_TALES][PLAYER_NAME_LENGTH]; int statValues[NUM_STORYTELLER_TALES][4]; int language[NUM_STORYTELLER_TALES]; }; struct MauvilleManGiddy { /*0x00*/ int id; /*0x01*/ int taleCounter; /*0x02*/ int questionNum; /*0x04*/ int randomWords[GIDDY_MAX_TALES]; /*0x18*/ int questionList[GIDDY_MAX_QUESTIONS]; /*0x20*/ int language; }; /*size = 0x2C*/ struct MauvilleManHipster { int id; bool alreadySpoken; int language; }; struct MauvilleOldManTrader { int id; int decorIds[NUM_TRADER_ITEMS]; int playerNames[NUM_TRADER_ITEMS][11]; int alreadyTraded; int language[NUM_TRADER_ITEMS]; }; typedef union OldMan { struct MauvilleManCommon common; struct MauvilleManBard bard; struct MauvilleManGiddy giddy; struct MauvilleManHipster hipster; struct MauvilleOldManTrader trader; struct MauvilleManStoryteller storyteller; int filler[0x40]; } OldMan; ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/personnel.c ================================================ #include "personnel.h" #include #include #include Employee* create_employee(int id, const char* name, const char* position, enum Department department, float salary) { if (employee_count >= MAX_EMPLOYEES) { return NULL; // Maximum employee limit reached } Employee* new_employee = malloc(sizeof(Employee)); if (new_employee == NULL) { return NULL; // Memory allocation failed } new_employee->id = id; strncpy(new_employee->name, name, sizeof(new_employee->name) - 1); new_employee->name[sizeof(new_employee->name) - 1] = '\0'; // Ensure null-termination strncpy(new_employee->position, position, sizeof(new_employee->position) - 1); new_employee->position[sizeof(new_employee->position) - 1] = '\0'; // Ensure null-termination new_employee->department = department; new_employee->salary = salary; employees[employee_count++] = new_employee; return new_employee; } void destroy_employee(Employee* employee) { if (employee != NULL) { free(employee); } } Employee* get_employee_by_id(int id) { for (int i = 0; i < employee_count; i++) { if (employees[i] != NULL && employees[i]->id == id) { return employees[i]; } } return NULL; // Employee not found } Employee* get_highest_paid_employee() { Employee* highest_paid_employee = NULL; float max_salary = -1.0f; // Initialize to a negative value for (int i = 0; i < employee_count; i++) { if (employees[i] != NULL && employees[i]->salary > max_salary) { max_salary = employees[i]->salary; highest_paid_employee = employees[i]; } } return highest_paid_employee; } Employee** get_employees_by_department(enum Department department, int* count) { Employee** department_employees = malloc(MAX_EMPLOYEES * sizeof(Employee*)); if (department_employees == NULL) { *count = 0; return NULL; // Memory allocation failed } *count = 0; for (int i = 0; i < employee_count; i++) { if (employees[i] != NULL && employees[i]->department == department) { department_employees[(*count)++] = employees[i]; } } return department_employees; } void print_employee_details(const Employee* employee) { if (employee != NULL) { printf("ID: %d\n", employee->id); printf("Name: %s\n", employee->name); printf("Position: %s\n", employee->position); printf("Department: %d\n", employee->department); printf("Salary: %.2f\n", employee->salary); } else { printf("Employee not found.\n"); } } ================================================ FILE: src/languagePlugins/c/testFiles/cFiles/personnel.h ================================================ // TOTAL SYMBOL COUNT : 12 // TOTAL FUNCTION COUNT : 6 #ifndef PERSONNEL_H #define PERSONNEL_H #include #define MAX_EMPLOYEES 100 enum Department { HR = 0, IT = 1, SALES = 2, MARKETING = 3, FINANCE = 4, }; typedef struct { int id; char name[50]; char position[50]; enum Department department; float salary; } Employee; static int employee_count = 0; static Employee* employees[MAX_EMPLOYEES]; Employee* create_employee(int id, const char* name, const char* position, enum Department department, float salary); void destroy_employee(Employee* employee); Employee* get_employee_by_id(int id); Employee* get_highest_paid_employee(); Employee** get_employees_by_department(enum Department department, int* count); void print_employee_details(const Employee* employee); #endif ================================================ FILE: src/languagePlugins/c/testFiles/index.ts ================================================ import * as fs from "node:fs"; import { extname, join } from "@std/path"; import type Parser from "tree-sitter"; import { cLanguage, cParser } from "../../../helpers/treeSitter/parsers.ts"; import type z from "zod"; import type { localConfigSchema } from "../../../cli/middlewares/napiConfig.ts"; if (!import.meta.dirname) { throw new Error("import.meta.dirname is not defined"); } export const cFilesFolder = join(import.meta.dirname, "cFiles"); const cFilesMap = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); /** * Recursively finds all C files in the given directory and its subdirectories. * @param dir - The directory to search in. */ function findCFiles(dir: string) { const files = fs.readdirSync(dir); files.forEach((file) => { const fullPath = join(dir, file); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { if ( !fullPath.includes(".extracted/") && !fullPath.includes("bin/") && !fullPath.includes("obj/") ) { findCFiles(fullPath); } } else if ( extname(fullPath) === ".c" || extname(fullPath) === ".h" ) { const content = fs.readFileSync(fullPath, "utf8"); const tree = cParser.parse(content); cFilesMap.set(fullPath, { path: fullPath, rootNode: tree.rootNode }); } }); } export function getCFilesMap(): Map< string, { path: string; rootNode: Parser.SyntaxNode } > { findCFiles(cFilesFolder); return cFilesMap; } export function getCFilesContentMap(): Map< string, { path: string; content: string } > { findCFiles(cFilesFolder); const contentMap = new Map(); for (const [filePath, file] of cFilesMap) { contentMap.set(filePath, { path: file.path, content: file.rootNode.text, }); } return contentMap; } export const dummyLocalConfig = { language: cLanguage, project: { include: [], exclude: [], }, outDir: "./dist", c: { includedirs: [], }, } as z.infer; ================================================ FILE: src/languagePlugins/c/warnings/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CWarningManager } from "./index.ts"; import { cFilesFolder, getCFilesMap } from "../testFiles/index.ts"; import { join } from "@std/path"; describe("CWarningManager", () => { const files = getCFilesMap(); const manager = new CWarningManager(files); const diagnostics = manager.diagnostics; test("finds all diagnostics", () => { expect(diagnostics.length).toBe(2); }); test("diagnostics are of correct type", () => { expect(diagnostics[0].message).toContain("Tree-sitter error"); expect(diagnostics[1].message).toContain("Unnamed"); }); test("diagnostics are in correct files", () => { expect(diagnostics[0].filename).toBe( join(cFilesFolder, "errors.h"), ); expect(diagnostics[1].filename).toBe( join(cFilesFolder, "errors.h"), ); }); }); ================================================ FILE: src/languagePlugins/c/warnings/index.ts ================================================ import { type ManifestDiagnostics, TreeSitterError, UnnamedDatatypeWarning, } from "./types.ts"; import type Parser from "tree-sitter"; import { C_ERROR_QUERY, C_UNNAMED_DATATYPE_QUERY } from "./queries.ts"; export class CWarningManager { diagnostics: ManifestDiagnostics[] = []; constructor( files: Map, ) { for (const [, { path, rootNode }] of files) { this.diagnostics.push( ...this.getDiagnostics(path, rootNode), ); } } private getDiagnostics( path: string, rootNode: Parser.SyntaxNode, ): ManifestDiagnostics[] { const diagnostics: ManifestDiagnostics[] = []; const errorQuery = C_ERROR_QUERY.captures(rootNode); const unnamedDatatypeQuery = C_UNNAMED_DATATYPE_QUERY.captures(rootNode); for (const capture of errorQuery) { diagnostics.push(new TreeSitterError(path, capture)); } for (const capture of unnamedDatatypeQuery) { diagnostics.push(new UnnamedDatatypeWarning(path, capture)); } return diagnostics; } /** * Print the diagnostics to the console. * @param count The number of diagnostics to print. If undefined, all diagnostics will be printed. * @example * ```ts * const manager = new CWarningManager(files); * // Prints up to 5 diagnostics * manager.printDiagnostics(5); * // Prints all diagnostics * manager.printDiagnostics(); * ``` */ public printDiagnostics(count: number | undefined = undefined): void { if (!count) { count = this.diagnostics.length; } if (count > this.diagnostics.length) { count = this.diagnostics.length; } for (let i = 0; i < count; i++) { const diagnostic = this.diagnostics[i]; console.warn(diagnostic.message); } } } ================================================ FILE: src/languagePlugins/c/warnings/queries.ts ================================================ import Parser from "tree-sitter"; import { cParser } from "../../../helpers/treeSitter/parsers.ts"; export const C_ERROR_QUERY = new Parser.Query( cParser.getLanguage(), ` (ERROR) @error `, ); export const C_UNNAMED_DATATYPE_QUERY = new Parser.Query( cParser.getLanguage(), ` (translation_unit [ (struct_specifier !name) @struct (union_specifier !name) @union ]) (preproc_ifdef [ (struct_specifier !name) @struct (union_specifier !name) @union ]) `, ); ================================================ FILE: src/languagePlugins/c/warnings/types.ts ================================================ import type Parser from "tree-sitter"; export interface ManifestDiagnostics { filename: string; line: number; column: number; message: string; } export class TreeSitterError implements ManifestDiagnostics { filename: string; line: number; column: number; message: string; constructor( filename: string, capture: Parser.QueryCapture, ) { this.filename = filename; this.line = capture.node.startPosition.row + 1; this.column = capture.node.startPosition.column + 1; this.message = `[WARN] (${filename}:${this.line}:${this.column}) | Tree-sitter error`; } } export class UnnamedDatatypeWarning implements ManifestDiagnostics { filename: string; line: number; column: number; datatype: string; message: string; constructor( filename: string, capture: Parser.QueryCapture, ) { this.filename = filename; this.line = capture.node.startPosition.row + 1; this.column = capture.node.startPosition.column + 1; this.datatype = capture.name; this.message = `[WARN] (${filename}:${this.line}:${this.column}) | Unnamed ${this.datatype}`; } } ================================================ FILE: src/languagePlugins/csharp/README.md ================================================ # NanoAPI C# Plugin This plugin manages parsing and mapping of dependencies in C#/.NET projects. ## Class diagram ```mermaid classDiagram class CSharpDependencyFormatter { - invResolver: CSharpInvocationResolver - usingResolver: CSharpUsingResolver - nsMapper: CSharpNamespaceMapper - projectMapper: CSharpProjectMapper + formatFile(filepath: string): CSharpFile | undefined } class CSharpDependency { + id: string + isExternal: boolean + symbols: Record + isNamespace?: boolean } class CSharpDependent { + id: string + symbols: Record } class CSharpSymbol { + id: string + type: SymbolType + lineCount: number + characterCount: number + dependents: Record + dependencies: Record } class CSharpFile { + id: string + filepath: string + lineCount: number + characterCount: number + dependencies: Record + symbols: Record } class CSharpInvocationResolver { - parser: Parser - nsMapper: CSharpNamespaceMapper - usingResolver: CSharpUsingResolver - resolvedImports: ResolvedImports - cache: Map - extensions: ExtensionMethodMap + getInvocationsFromFile(filepath: string): Invocations + getInvocationsFromNode(node: Parser.SyntaxNode, filepath: string): Invocations + isUsedInFile(filepath: string, symbol: SymbolNode): boolean } class Invocations { + resolvedSymbols: SymbolNode[] + unresolved: string[] } class CSharpNamespaceMapper { - files: Map - nsResolver: CSharpNamespaceResolver - nsTree: NamespaceNode - exportsCache: Map - fileExports: Map + getFile(key: string): File + buildNamespaceTree(): NamespaceNode + findNamespaceInTree(tree: NamespaceNode, namespaceName: string): NamespaceNode | null + findClassInTree(tree: NamespaceNode, className: string): SymbolNode | null + getExportsForFile(filepath: string): SymbolNode[] + getFullNSName(namespace: NamespaceNode): string } class NamespaceNode { + name: string + exports: SymbolNode[] + childrenNamespaces: NamespaceNode[] + parentNamespace?: NamespaceNode } class SymbolNode { + name: string + type: SymbolType + namespace: string + filepath: string + node: Parser.SyntaxNode } class CSharpNamespaceResolver { - parser: Parser - currentFile: string - cache: Map + getNamespacesFromFile(file: File): Namespace[] + getExportsFromNamespaces(namespaces: Namespace[]): ExportedSymbol[] } class Namespace { + name: string + node: Parser.SyntaxNode + identifierNode?: Parser.SyntaxNode + exports: ExportedSymbol[] + childrenNamespaces: Namespace[] } class ExportedSymbol { + name: string + type: SymbolType + node: Parser.SyntaxNode + identifierNode: Parser.SyntaxNode + namespace?: string + filepath: string + parent?: ExportedSymbol } class CSharpProjectMapper { + rootFolder: string + subprojects: DotNetProject[] + findSubprojectForFile(filePath: string): DotNetProject | null + updateGlobalUsings(globalUsings: ResolvedImports, subproject: DotNetProject) + getGlobalUsings(filepath: string): ResolvedImports } class DotNetProject { + rootFolder: string + csprojPath: string + csprojContent: string + globalUsings: ResolvedImports } class CSharpUsingResolver { - nsMapper: CSharpNamespaceMapper - usingDirectives: UsingDirective[] - cachedImports: Map - projectmapper: CSharpProjectMapper - cachedExternalDeps: Set + parseUsingDirectives(filepath: string): UsingDirective[] + resolveUsingDirectives(filepath: string): ResolvedImports + getGlobalUsings(filepath: string): ResolvedImports + findClassInImports(imports: ResolvedImports, className: string, filepath: string): SymbolNode | null } class UsingDirective { + node: Parser.SyntaxNode + type: UsingType + filepath: string + id: string + alias?: string } class InternalSymbol { + usingtype: UsingType + filepath: string + alias?: string + symbol?: SymbolNode + namespace?: NamespaceNode } class ExternalSymbol { + usingtype: UsingType + filepath: string + alias?: string + name: string } class ResolvedImports { + internal: InternalSymbol[] + external: ExternalSymbol[] } class File { + path: string + rootNode: Parser.SyntaxNode } class ExtractedFile { + subproject: DotNetProject + namespace: string + symbol: SymbolNode + imports: UsingDirective[] + name: string } class CSharpExtractor { - manifest: DependencyManifest - projectMapper: CSharpProjectMapper - nsMapper: CSharpNamespaceMapper - usingResolver: CSharpUsingResolver + extractSymbol(symbol: SymbolNode): ExtractedFile[] + extractAndSaveSymbol(symbol: SymbolNode): void + extractSymbolByName(symbolName: string): ExtractedFile[] | undefined + extractAndSaveSymbolFromFile(filePath:string, symbolName: string): void + getContent(file: ExtractedFile): string } class CSharpExtensionResolver { - namespaceMapper: CSharpNamespaceMapper - extensions: ExtensionMethodMap + getExtensions(): ExtensionMethodMap } class ExtensionMethod { + node: Parser.SyntaxNode + symbol: SymbolNode + name: string + type: string + extendedType: string + typeParameters?: string[] } class ExtensionMethodMap { + [namespace: string]: ExtensionMethod[] } enum SymbolType enum UsingType class Parser { + getLanguage(): any + parse(content: string): any } CSharpDependencyFormatter --> CSharpInvocationResolver CSharpDependencyFormatter --> CSharpUsingResolver CSharpDependencyFormatter --> CSharpNamespaceMapper CSharpDependencyFormatter --> CSharpProjectMapper CSharpInvocationResolver --> CSharpNamespaceMapper CSharpInvocationResolver --> CSharpUsingResolver CSharpInvocationResolver --> ResolvedImports CSharpNamespaceMapper --> CSharpNamespaceResolver CSharpNamespaceMapper --> NamespaceNode CSharpNamespaceMapper --> SymbolNode CSharpNamespaceResolver --> Namespace CSharpNamespaceResolver --> ExportedSymbol CSharpProjectMapper --> DotNetProject CSharpProjectMapper --> ResolvedImports CSharpUsingResolver --> CSharpNamespaceMapper CSharpUsingResolver --> CSharpProjectMapper CSharpUsingResolver --> UsingDirective CSharpUsingResolver --> ResolvedImports UsingDirective --> UsingType InternalSymbol --> UsingType InternalSymbol --> SymbolNode InternalSymbol --> NamespaceNode ExternalSymbol --> UsingType ResolvedImports --> InternalSymbol ResolvedImports --> ExternalSymbol File --> Parser CSharpExtractor --> DependencyManifest CSharpExtractor --> CSharpProjectMapper CSharpExtractor --> CSharpNamespaceMapper CSharpExtractor --> CSharpUsingResolver ExtractedFile --> DotNetProject ExtractedFile --> SymbolNode ExtractedFile --> UsingDirective CSharpExtensionResolver --> CSharpNamespaceMapper CSharpExtensionResolver --> ExtensionMethodMap ExtensionMethodMap --> ExtensionMethod ``` ================================================ FILE: src/languagePlugins/csharp/dependencyFormatting/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpDependencyFormatter } from "./index.ts"; import { csharpFilesFolder, getCSharpFilesMap, getCsprojFilesMap, } from "../testFiles/index.ts"; import { join } from "@std/path"; import type { File } from "../namespaceResolver/index.ts"; describe("Dependency formatting", () => { const parsedfiles: Map = getCSharpFilesMap(); const csprojfiles = getCsprojFilesMap(); const formatter = new CSharpDependencyFormatter(parsedfiles, csprojfiles); test("SemiNamespaced.cs", () => { const snpath = join(csharpFilesFolder, "SemiNamespaced.cs"); const seminamespaced = formatter.formatFile(snpath); expect(seminamespaced).toBeDefined(); if (!seminamespaced) return; expect(seminamespaced.id).toBe(snpath); expect(seminamespaced.dependencies[snpath]).toBeDefined(); expect(Object.keys(seminamespaced.symbols).length).toBe(3); }); }); ================================================ FILE: src/languagePlugins/csharp/dependencyFormatting/index.ts ================================================ import type { SymbolType } from "../namespaceResolver/index.ts"; import { CSharpInvocationResolver, type Invocations, } from "../invocationResolver/index.ts"; import { CSharpNamespaceMapper, type SymbolNode, } from "../namespaceMapper/index.ts"; import type Parser from "tree-sitter"; import { CSharpUsingResolver, type ResolvedImports, } from "../usingResolver/index.ts"; import { CSharpProjectMapper } from "../projectMapper/index.ts"; /** * Represents a dependency in a C# file. */ export interface CSharpDependency { /** * The unique identifier of the dependency. */ id: string; /** * Indicates whether the dependency is external. */ isExternal: boolean; /** * A record of symbols associated with the dependency. */ symbols: Record; /** * Indicates whether the dependency is a namespace. */ isNamespace?: boolean; } /** * Represents a dependent in a C# file. */ export interface CSharpDependent { /** * The unique identifier of the dependent. */ id: string; /** * A record of symbols associated with the dependent. */ symbols: Record; } /** * Represents a symbol in a C# file. */ export interface CSharpSymbol { /** * The unique identifier of the symbol. */ id: string; /** * The type of the symbol. */ type: SymbolType; /** * The number of lines the symbol spans. */ lineCount: number; /** * The number of characters the symbol spans. */ characterCount: number; /** * The syntax node representing the symbol. */ node: Parser.SyntaxNode; /** * A record of dependents associated with the symbol. */ dependents: Record; /** * A record of dependencies associated with the symbol. */ dependencies: Record; } /** * Represents a C# file with its metadata and dependencies. */ export interface CSharpFile { /** * The unique identifier of the file. */ id: string; /** * The file path of the C# file. */ filepath: string; /** * The root syntax node of the file. */ rootNode: Parser.SyntaxNode; /** * The number of lines in the file. */ lineCount: number; /** * The number of characters in the file. */ characterCount: number; /** * A record of dependencies associated with the file. */ dependencies: Record; /** * A record of symbols defined in the file. */ symbols: Record; } export class CSharpDependencyFormatter { private invResolver: CSharpInvocationResolver; private usingResolver: CSharpUsingResolver; private nsMapper: CSharpNamespaceMapper; private projectMapper: CSharpProjectMapper; /** * Constructs a new CSharpDependencyFormatter. * @param files - A map of file paths to their corresponding syntax nodes. */ constructor( parsedFiles: Map, csprojFiles: Map, ) { this.nsMapper = new CSharpNamespaceMapper(parsedFiles); this.projectMapper = new CSharpProjectMapper(csprojFiles); this.invResolver = new CSharpInvocationResolver( this.nsMapper, this.projectMapper, ); this.usingResolver = new CSharpUsingResolver( this.nsMapper, this.projectMapper, ); for (const [fp] of parsedFiles) { this.usingResolver.resolveUsingDirectives(fp); } } /** * Formats exported symbols into a record of CSharpSymbol. * @param exportedSymbols - An array of exported symbols. * @returns A record of symbol names to their corresponding CSharpSymbol. */ private formatSymbols( exportedSymbols: SymbolNode[], ): Record { const symbols: Record = {}; for (const symbol of exportedSymbols) { const fullname = (symbol.namespace !== "" ? symbol.namespace + "." : "") + symbol.name; const dependencies = this.invResolver.getInvocationsFromNode( symbol.node, symbol.filepath, ); dependencies.resolvedSymbols = dependencies.resolvedSymbols.filter( (sm) => sm.name !== symbol.name, ); symbols[fullname] = { id: fullname, type: symbol.type, node: symbol.node, lineCount: symbol.node.endPosition.row - symbol.node.startPosition.row, characterCount: symbol.node.endIndex - symbol.node.startIndex, dependencies: this.formatDependencies(dependencies), dependents: {}, }; } return symbols; } /** * Formats invocations into a record of CSharpDependency. * @param invocations - The invocations to format. * @returns A record of dependency IDs to their corresponding CSharpDependency. */ private formatDependencies( invocations: Invocations, ): Record { const dependencies: Record = {}; for (const resolvedSymbol of invocations.resolvedSymbols) { const namespace = resolvedSymbol.namespace; const filepath = resolvedSymbol.filepath; const id = namespace !== "" ? namespace + "." + resolvedSymbol.name : resolvedSymbol.name; if (!dependencies[filepath]) { dependencies[filepath] = { id: filepath, isExternal: false, symbols: {}, isNamespace: true, }; } dependencies[filepath].symbols[id] = id; } // Add unresolved symbols as external dependencies // Commented because redundant : if a symbol is unresolved, // then the external dependency is imported through a namespace. // Not removed in case my analysis is inaccurate. // for (const unresolvedSymbol of invocations.unresolved) { // dependencies[unresolvedSymbol] = { // id: unresolvedSymbol, // isExternal: true, // symbols: {}, // }; // } return dependencies; } /** * Formats external usings into a record of CSharpDependency. * @param resolvedimports - The resolved imports to format. * @returns A record of dependency IDs to their corresponding CSharpDependency. */ private formatExternalUsings( resolvedimports: ResolvedImports, ): Record { const dependencies: Record = {}; for (const unresolvedSymbol of resolvedimports.external) { dependencies[unresolvedSymbol.name] = { id: unresolvedSymbol.name, isExternal: true, symbols: {}, isNamespace: true, }; } return dependencies; } /** * Formats a file into a CSharpFile object. * @param filepath - The path of the file to format. * @returns The formatted CSharpFile object. */ public formatFile(filepath: string): CSharpFile | undefined { const file = this.invResolver.nsMapper.getFile(filepath); if (!file) { return undefined; } const fileSymbols = this.nsMapper.getExportsForFile(filepath); const fileDependencies = this.invResolver.getInvocationsFromFile(filepath); const formattedFile: CSharpFile = { id: file.path, filepath: file.path, rootNode: file.rootNode, lineCount: file.rootNode.endPosition.row, characterCount: file.rootNode.endIndex - file.rootNode.startIndex, dependencies: this.formatDependencies(fileDependencies), symbols: this.formatSymbols(fileSymbols), }; // Add global usings to dependencies const globalUsings = this.formatExternalUsings( this.usingResolver.getGlobalUsings(filepath), ); for (const key in globalUsings) { if (!formattedFile.dependencies[key]) { formattedFile.dependencies[key] = globalUsings[key]; } } // Add local usings to dependencies const localUsings = this.formatExternalUsings( this.usingResolver.resolveUsingDirectives(filepath), ); for (const key in localUsings) { if (!formattedFile.dependencies[key]) { formattedFile.dependencies[key] = localUsings[key]; } } // If an internal dependency is a symbol of an imported namespace, // then add said symbol to the symbol list of that namespace for (const key in formattedFile.dependencies) { const dep = formattedFile.dependencies[key]; if (!dep.isExternal && !dep.isNamespace) { const namespaceParts = dep.id.split("."); if (namespaceParts.length > 1) { const parentNamespace = namespaceParts.slice(0, -1).join("."); const parentDep = formattedFile.dependencies[parentNamespace]; if (parentDep && !parentDep.isExternal) { parentDep.symbols[dep.id] = dep.id; delete formattedFile.dependencies[key]; } } } } return formattedFile; } } ================================================ FILE: src/languagePlugins/csharp/extensionResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpExtensionResolver } from "./index.ts"; import { CSharpNamespaceMapper } from "../namespaceMapper/index.ts"; import { getCSharpFilesMap } from "../testFiles/index.ts"; describe("CSharpExtensionResolver", () => { const files = getCSharpFilesMap(); const nsMapper = new CSharpNamespaceMapper(files); const extensionResolver = new CSharpExtensionResolver(nsMapper); const extensions = extensionResolver.getExtensions(); test("should resolve extension methods in the project", () => { expect(Object.keys(extensions).length).toBe(2); expect(extensions[""]).toBeDefined(); expect(extensions[""].length).toBe(2); expect(extensions[""][0].name).toBeDefined(); expect(extensions["MyApp.BeefBurger"]).toBeDefined(); expect(extensions["MyApp.BeefBurger"].length).toBe(1); expect(extensions["MyApp.BeefBurger"][0].name).toBeDefined(); expect(extensions["MyApp.BeefBurger"][0].name).toBe("Melt"); }); }); ================================================ FILE: src/languagePlugins/csharp/extensionResolver/index.ts ================================================ import type { CSharpNamespaceMapper, NamespaceNode, SymbolNode, } from "../namespaceMapper/index.ts"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; import Parser from "tree-sitter"; const extensionMethodQuery = new Parser.Query( csharpParser.getLanguage(), ` ((method_declaration parameters : (parameter_list (parameter (modifier) @mod) )) (#eq? @mod "this")) @method `, ); const typeParameterQuery = new Parser.Query( csharpParser.getLanguage(), ` (type_parameter name: (_) @cls) `, ); /** * Interface representing an extension method in C#. */ export interface ExtensionMethod { /** * The syntax node of the extension method. */ node: Parser.SyntaxNode; /** * The symbol associated with the extension method. */ symbol: SymbolNode; /** * The name of the extension method. */ name: string; /** * The type of the extension method. */ type: string; /** * The type that is being extended */ extendedType: string; /** * The type parameters of the extension method. */ typeParameters?: string[]; } /** * Record for all extensions in the project. * Key : name of a namespace that has extensions. * Value : the extensions of said namespace. */ export type ExtensionMethodMap = Record; export class CSharpExtensionResolver { private namespaceMapper: CSharpNamespaceMapper; private extensions: ExtensionMethodMap = {}; constructor(namespaceMapper: CSharpNamespaceMapper) { this.namespaceMapper = namespaceMapper; this.resolveExtensionMethodsInNamespaceTree(); } /** * Returns the extensions found in the project. * @returns A map of extension methods found in the project. */ getExtensions(): ExtensionMethodMap { return this.extensions; } /** * Resolves extension methods in a symbol. * @param symbol - the symbol to analyse. * @returns A map of extension methods found in the file. */ private resolveExtensionMethods(symbol: SymbolNode): ExtensionMethod[] { const extensions: ExtensionMethod[] = []; const extensionMethods = extensionMethodQuery.captures(symbol.node); for (const ext of extensionMethods) { if (ext.name === "mod") continue; const methodNode = ext.node; let methodName = methodNode.childForFieldName("name")?.text; if (!methodName) continue; const typeParameters = methodNode.childForFieldName("type_parameters"); const typeParameterNames: string[] = []; if (typeParameters) { const typeParams = typeParameterQuery.captures(typeParameters); for (const param of typeParams) { typeParameterNames.push(param.node.text); } } if (methodName.includes("<")) { const index = methodName.indexOf("<"); methodName = methodName.substring(0, index); } const methodType = methodNode.childForFieldName("returns")?.text || "void"; const parameters = methodNode.childrenForFieldName("parameters"); let extendedType = "void"; parameters.forEach((param) => { if (param.text.startsWith("this ")) { extendedType = param.childForFieldName("type")?.text || "void"; } }); extensions.push({ node: methodNode, symbol: symbol, name: methodName, type: methodType, extendedType: extendedType, typeParameters: typeParameterNames.length > 0 ? typeParameterNames : undefined, }); } return extensions; } /** * Resolves extension methods in a namespace. * @param namespace - The namespace to analyze. * @returns A map of extension methods found in the namespace. */ private resolveExtensionMethodsInNamespace( namespace: NamespaceNode, ): ExtensionMethod[] { const extensions: ExtensionMethod[] = []; for (const symbol of namespace.exports) { const extMethods = this.resolveExtensionMethods(symbol); if (extMethods.length > 0) { extensions.push(...extMethods); } } return extensions; } /** * Resolves extension methods in the namespace tree. * @returns A map of extension methods found in the project. */ private resolveExtensionMethodsInNamespaceTree() { this.extensions = {}; const resolveExtensionsRecursively = (namespace: NamespaceNode) => { const extMethods = this.resolveExtensionMethodsInNamespace(namespace); if (extMethods.length > 0) { this.extensions[this.namespaceMapper.getFullNSName(namespace)] = extMethods; } for (const childNamespace of namespace.childrenNamespaces) { resolveExtensionsRecursively(childNamespace); } }; resolveExtensionsRecursively(this.namespaceMapper.nsTree); } } ================================================ FILE: src/languagePlugins/csharp/extractor/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpExtractor } from "./index.ts"; import { csharpFilesFolder, getCSharpFilesMap, getCsprojFilesMap, } from "../testFiles/index.ts"; import { generateCSharpDependencyManifest } from "../../../manifest/dependencyManifest/csharp/index.ts"; import { join } from "@std/path"; describe("CSharpExtractor", () => { const parsedfiles = getCSharpFilesMap(); const csprojFiles = getCsprojFilesMap(); const files = new Map(); for (const [filePath, { path, rootNode }] of parsedfiles) { files.set(filePath, { path, content: rootNode.text }); } for (const [filePath, { path, content }] of csprojFiles) { files.set(filePath, { path, content }); } const manifest = generateCSharpDependencyManifest(files); const extractor = new CSharpExtractor(files, manifest); const programpath = join(csharpFilesFolder, "Program.cs"); const twonamespacesonefilepath = join( csharpFilesFolder, "2Namespaces1File.cs", ); const seminamespacedpath = join(csharpFilesFolder, "SemiNamespaced.cs"); test("should extract symbols correctly", () => { expect( extractor.extractSymbolFromFile(programpath, "Program")?.length, ).toBe(10); const salad = extractor.extractSymbolFromFile( twonamespacesonefilepath, "ChickenBurger.Salad", ); expect(salad?.length).toBe(1); const saladfile = salad?.[0]; expect(saladfile?.name).toBe("Salad"); expect(saladfile?.namespace).toBe("ChickenBurger"); expect(saladfile?.subproject.name).toBe("TestFiles"); expect(saladfile?.symbol).toMatchObject({ name: "Salad", type: "class", namespace: "ChickenBurger", }); expect( extractor.extractSymbolFromFile( twonamespacesonefilepath, "MyApp.BeefBurger.Steak", )?.length, ).toBe(2); expect( extractor.extractSymbolFromFile(seminamespacedpath, "HeadCrab")?.length, ).toBe(2); }); test("should extract global using directives", () => { const project = extractor.projectMapper.findSubprojectForFile( join(csharpFilesFolder, "Program.cs"), ); if (!project) { throw new Error("Project not found"); } const usingDirectives = extractor.generateGlobalUsings(project); expect(usingDirectives.startsWith("global using System.IO;")).toBe(true); const subproject = extractor.projectMapper.findSubprojectForFile( join(csharpFilesFolder, "Subfolder/GlobalUsings.cs"), ); if (!subproject) { throw new Error("Subproject not found"); } const subprojectUsingDirectives = extractor.generateGlobalUsings( subproject, ); expect(subprojectUsingDirectives.includes("global using System;")).toBe( true, ); expect( subprojectUsingDirectives.includes("global using MyApp.Models;"), ).toBe(true); }); test("should manage nested classes", () => { const extracted = extractor.extractSymbolFromFile(programpath, "Program"); const symbols = extracted?.map((file) => file.symbol.namespace !== "" ? file.symbol.namespace + "." + file.symbol.name : file.symbol.name ); expect(symbols).not.toContain("OuterNamespace.OuterInnerClass"); expect(symbols).toContain("OuterNamespace.OuterClass"); }); }); ================================================ FILE: src/languagePlugins/csharp/extractor/index.ts ================================================ import type Parser from "tree-sitter"; import { CSharpProjectMapper, type DotNetProject, } from "../projectMapper/index.ts"; import { CSharpNamespaceMapper, type SymbolNode, } from "../namespaceMapper/index.ts"; import { CSharpUsingResolver, type UsingDirective, } from "../usingResolver/index.ts"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; /** * Represents an extracted file containing a symbol. */ export interface ExtractedFile { /** The subproject to which the file belongs */ subproject: DotNetProject; /** The namespace of the symbol */ namespace: string; /** The symbol node */ symbol: SymbolNode; /** The using directives in the file */ imports: UsingDirective[]; /** The name of the file */ name: string; } export class CSharpExtractor { private manifest: DependencyManifest; public projectMapper: CSharpProjectMapper; private nsMapper: CSharpNamespaceMapper; public usingResolver: CSharpUsingResolver; constructor( files: Map, manifest: DependencyManifest, ) { this.manifest = manifest; const csprojFiles = new Map(); const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const [filePath, file] of files) { if (filePath.endsWith(".csproj")) { csprojFiles.set(filePath, file); } else if (filePath.endsWith(".cs")) { parsedFiles.set(filePath, { path: filePath, rootNode: csharpParser.parse(file.content).rootNode, }); } } this.projectMapper = new CSharpProjectMapper(csprojFiles); this.nsMapper = new CSharpNamespaceMapper(parsedFiles); this.usingResolver = new CSharpUsingResolver( this.nsMapper, this.projectMapper, ); for (const [filePath] of parsedFiles) { this.usingResolver.resolveUsingDirectives(filePath); } } /** * Finds all dependencies of a given symbol. * @param symbol - The symbol for which to find dependencies. * @returns An array of symbols. */ private findDependencies(symbol: SymbolNode): SymbolNode[] { const dependencies: SymbolNode[] = []; const symbolfullname = symbol.namespace !== "" ? symbol.namespace + "." + symbol.name : symbol.name; const symbolDependencies = this.manifest[symbol.filepath] ?.symbols[symbolfullname].dependencies; if (symbolDependencies) { for (const dependency of Object.values(symbolDependencies)) { for (const depsymbol of Object.values(dependency.symbols)) { const depsymbolnode = this.nsMapper.findClassInTree( this.nsMapper.nsTree, depsymbol, ); if (depsymbolnode) { dependencies.push(depsymbolnode); } } } } return dependencies; } /** * Finds all dependencies of a given symbol, and the dependencies of those dependencies. * @param symbol - The symbol for which to find dependencies. * @returns An array of symbols. */ private findAllDependencies( symbol: SymbolNode, visited: Set = new Set(), ): SymbolNode[] { const allDependencies: Set = new Set(); if (visited.has(symbol)) { return Array.from(allDependencies); } visited.add(symbol); const dependencies = this.findDependencies(symbol); for (const dependency of dependencies) { allDependencies.add(dependency); for (const dep of this.findAllDependencies(dependency, visited)) { allDependencies.add(dep); } } return Array.from(allDependencies); } /** * Saves the extracted file containing a symbol into the filesystem. * @param file - The extracted file to save. */ public getContent(file: ExtractedFile): string { const usingDirectives = file.imports .map((directive) => directive.node.text) .join("\n"); const namespaceDirective = file.namespace !== "" ? `namespace ${file.namespace};` : ""; const content = `${usingDirectives}\n${namespaceDirective}\n${file.symbol.node.text}\n`; return content; } /** * Extracts a given symbol and its dependencies and returns them as files. * @param symbol - The symbol to extract. * @returns An array of extracted files. */ public extractSymbol(symbol: SymbolNode): ExtractedFile[] { const subproject = this.projectMapper.findSubprojectForFile( symbol.filepath, ); if (!subproject) { throw new Error(`Subproject not found for file: ${symbol.filepath}`); } const extractedFiles: ExtractedFile[] = []; const visitedSymbols = new Set(); const addExtractedFile = (symbol: SymbolNode) => { // If the symbol is nested, we export the parent. while (symbol.parent) { symbol = symbol.parent; } if (!visitedSymbols.has(symbol.name)) { visitedSymbols.add(symbol.name); const subproject = this.projectMapper.findSubprojectForFile( symbol.filepath, ); if (subproject) { const extractedFile: ExtractedFile = { subproject, namespace: symbol.namespace, symbol, imports: this.usingResolver.parseUsingDirectives(symbol.filepath), name: symbol.name, }; extractedFiles.push(extractedFile); } } }; addExtractedFile(symbol); const dependencies = this.findAllDependencies(symbol); for (const dependency of dependencies) { addExtractedFile(dependency); } return extractedFiles; } /** * Extracts a symbol from a file by its name. * @param filePath - The path to file that contains the symbol to extract * @param symbolName - The name of the symbol to extract * @returns A list of extracted files or undefined if the symbol is not found */ public extractSymbolFromFile( filePath: string, symbolName: string, ): ExtractedFile[] | undefined { const fileExports = this.nsMapper.getFileExports(filePath); const symbol = fileExports.find( (symbol) => symbol.name === symbolName || symbol.namespace + "." + symbol.name === symbolName, ); if (symbol) { return this.extractSymbol(symbol); } return undefined; } public generateGlobalUsings(subproject: DotNetProject): string { let content = ""; const directives = subproject.globalUsings.directives; for (const directive of directives) { content += `${directive.node.text}\n`; } return content; } } ================================================ FILE: src/languagePlugins/csharp/invocationResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type { File } from "../namespaceResolver/index.ts"; import { CSharpNamespaceMapper, type SymbolNode, } from "../namespaceMapper/index.ts"; import { CSharpNamespaceResolver } from "../namespaceResolver/index.ts"; import { csharpFilesFolder, getCSharpFilesMap, getCsprojFilesMap, } from "../testFiles/index.ts"; import { join } from "@std/path"; import { CSharpInvocationResolver } from "./index.ts"; import type Parser from "tree-sitter"; import { CSharpProjectMapper } from "../projectMapper/index.ts"; describe("InvocationResolver", () => { const parsedfiles: Map = getCSharpFilesMap(); const csprojfiles = getCsprojFilesMap(); const nsMapper = new CSharpNamespaceMapper(parsedfiles); const projectMapper = new CSharpProjectMapper(csprojfiles); const nsResolver = new CSharpNamespaceResolver(); const invResolver: CSharpInvocationResolver = new CSharpInvocationResolver( nsMapper, projectMapper, ); test("Invocation resolution", () => { const usedFiles = invResolver.getInvocationsFromFile( join(csharpFilesFolder, "Program.cs"), ); const resolved = usedFiles.resolvedSymbols.map((s) => s.namespace !== "" ? s.namespace + "." + s.name : s.name ); const unresolved = usedFiles.unresolved; expect(resolved).toContain("MyApp.BeefBurger.Bun"); expect(resolved).toContain("ChickenBurger.Bun"); expect(resolved).toContain("ChickenBurger.Salad"); expect(resolved).toContain("MyNamespace.MyClass"); expect(resolved).toContain("HalfNamespace.Gordon"); expect(resolved).toContain("Freeman"); expect(resolved).toContain("OuterNamespace.InnerNamespace.InnerClass"); expect(resolved).toContain("MyApp.Models.OrderStatus"); expect(resolved).toContain("HeadCrab"); // Used through extension Bite() expect(resolved).toContain("OuterNamespace.OuterInnerClass"); expect(resolved).not.toContain("MyApp.BeefBurger.Salad"); expect(unresolved).toContain("System.Math"); expect(unresolved).not.toContain("string"); expect(unresolved).not.toContain("System"); expect(unresolved).not.toContain("Salad"); }); test("isUsedInFile", () => { const myclass: SymbolNode = { name: "MyClass", type: "class", filepath: join(csharpFilesFolder, "Namespaced.cs"), namespace: "MyNamespace", node: {} as Parser.SyntaxNode, }; const headcrab: SymbolNode = { name: "HeadCrab", type: "class", filepath: join(csharpFilesFolder, "SemiNamespaced.cs"), namespace: "", node: {} as Parser.SyntaxNode, }; const iorder: SymbolNode = { name: "IOrder", type: "interface", filepath: join(csharpFilesFolder, "Models.cs"), namespace: "MyApp.Models", node: {} as Parser.SyntaxNode, }; expect( invResolver.isUsedInFile( join(csharpFilesFolder, "Program.cs"), myclass, ), ).toBe(true); expect( invResolver.isUsedInFile( join(csharpFilesFolder, "Program.cs"), headcrab, ), ).toBe(true); expect( invResolver.isUsedInFile( join(csharpFilesFolder, "Program.cs"), iorder, ), ).toBe(false); }); test("Same-file dependencies", () => { const seminamespaced = nsResolver.getNamespacesFromFile( parsedfiles.get( join(csharpFilesFolder, "SemiNamespaced.cs"), ) as File, ); const headcrabnode = seminamespaced[0].exports.find( (exp) => exp.name === "HeadCrab", )?.node as Parser.SyntaxNode; const hcinvocations = invResolver.getInvocationsFromNode( headcrabnode, join(csharpFilesFolder, "SemiNamespaced.cs"), ); expect(hcinvocations).toMatchObject({ resolvedSymbols: [ { name: "Freeman", type: "class", namespace: "", }, ], unresolved: [], }); }); test("Finds useless using directives", () => { const filepath = join(csharpFilesFolder, "2Namespaces1File.cs"); const usingDirectives = invResolver.usingResolver.parseUsingDirectives( filepath, ); const invocations = invResolver.getInvocationsFromFile(filepath); expect(usingDirectives.length).toBe(2); expect( usingDirectives.filter((d) => invResolver.isUsingUseful(invocations, d)) .length, ).toBe(1); const programpath = join(csharpFilesFolder, "Program.cs"); const programUsingDirectives = invResolver.usingResolver .parseUsingDirectives(programpath); const programInvocations = invResolver.getInvocationsFromFile(programpath); expect(programUsingDirectives.length).toBe(6); expect( programUsingDirectives.filter((d) => invResolver.isUsingUseful(programInvocations, d) ).length, ).toBe(6); const usagepath = join(csharpFilesFolder, "Usage.cs"); const usageUsingDirectives = invResolver.usingResolver.parseUsingDirectives( usagepath, ); const usageInvocations = invResolver.getInvocationsFromFile(usagepath); expect(usageUsingDirectives.length).toBe(6); expect( usageUsingDirectives.filter((d) => invResolver.isUsingUseful(usageInvocations, d) ).length, ).toBe(4); const globalusingpath = join( csharpFilesFolder, "Subfolder/GlobalUsings.cs", ); const globalusingDirectives = invResolver.usingResolver .parseUsingDirectives(globalusingpath); const globalusingInvocations = invResolver.getInvocationsFromFile( globalusingpath, ); expect(globalusingDirectives.length).toBe(2); expect( globalusingDirectives.filter((d) => invResolver.isUsingUseful(globalusingInvocations, d) ) .length, ).toBe(1); // Every external import is considered useful no matter what // Even if there is no invocation. // It's also not dangerous to remove a global using directive // because they are regrouped in GlobalUsings.cs. }); }); ================================================ FILE: src/languagePlugins/csharp/invocationResolver/index.ts ================================================ import Parser from "tree-sitter"; import type { File } from "../namespaceResolver/index.ts"; import type { CSharpNamespaceMapper, NamespaceNode, SymbolNode, } from "../namespaceMapper/index.ts"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; import { CSharpUsingResolver, ExternalSymbol, type ResolvedImports, type UsingDirective, } from "../usingResolver/index.ts"; import type { CSharpProjectMapper } from "../projectMapper/index.ts"; import { CSharpExtensionResolver, type ExtensionMethod, type ExtensionMethodMap, } from "../extensionResolver/index.ts"; /** * Query to identify variable names in the file */ const variablesQuery = new Parser.Query( csharpParser.getLanguage(), ` (variable_declarator name: (identifier) @varname ) (parameter name: (identifier) @varname ) `, ); /** * Query to identify classes that are called in the file * for object and variable creation */ const calledClassesQuery = new Parser.Query( csharpParser.getLanguage(), ` (object_creation_expression type: (identifier) @cls) (object_creation_expression type: (qualified_name) @cls) ((object_creation_expression type: (generic_name) @cls)) (variable_declaration type: (identifier) @cls) (variable_declaration type: (qualified_name) @cls) (variable_declaration type: (generic_name) @cls) (parameter type: (identifier) @cls) (parameter type: (qualified_name) @cls) (parameter type: (generic_name) @cls) (type_argument_list (identifier) @cls) (type_argument_list (qualified_name) @cls) (type_argument_list (generic_name) @cls) (base_list (identifier) @cls) (base_list (qualified_name) @cls) (base_list (generic_name) @cls) (property_declaration type: (qualified_name) @cls) (property_declaration type: (identifier) @cls) (property_declaration type: (generic_name) @cls) (typeof_expression type: (_) @cls) (method_declaration returns: (qualified_name) @cls) (method_declaration returns: (identifier) @cls) (method_declaration returns: (generic_name) @cls) (array_type type: (identifier) @cls) (array_type type: (qualified_name) @cls) (array_type type: (generic_name) @cls) (nullable_type type: (identifier) @cls) (nullable_type type: (qualified_name) @cls) (nullable_type type: (generic_name) @cls) `, ); /** * Query to identify member accesses in the file * for function or constant calls */ const memberAccessQuery = new Parser.Query( csharpParser.getLanguage(), ` (_ (member_access_expression ))@cls `, ); /** * Query to identify attribute uses in the file */ const attributeQuery = new Parser.Query( csharpParser.getLanguage(), ` (attribute name: (_) @cls) `, ); /** * Query to identify invocation expressions in the file * exclusively for function calls */ const invocationQuery = new Parser.Query( csharpParser.getLanguage(), ` (invocation_expression function: (member_access_expression name: (_) @cls )) `, ); /** * Interface representing the invocations in a file */ export interface Invocations { /** List of resolved symbols */ resolvedSymbols: SymbolNode[]; /** List of unresolved symbols (usually external imports) */ unresolved: string[]; } export class CSharpInvocationResolver { parser: Parser = csharpParser; public nsMapper: CSharpNamespaceMapper; public usingResolver: CSharpUsingResolver; private extensions: ExtensionMethodMap = {}; private resolvedImports: ResolvedImports; private cache: Map = new Map(); constructor( nsMapper: CSharpNamespaceMapper, projectmapper: CSharpProjectMapper, ) { this.nsMapper = nsMapper; this.usingResolver = new CSharpUsingResolver(nsMapper, projectmapper); this.resolvedImports = { internal: [], external: [], }; this.extensions = new CSharpExtensionResolver(nsMapper).getExtensions(); } /** * Retrieves variable names from the given syntax node. * @param node - The syntax node to extract variable names from. * @returns An array of variable names. */ #getVariables(node: Parser.SyntaxNode): string[] { return variablesQuery.captures(node).map((ctc) => ctc.node.text); } /** * Resolves a symbol (class or namespace) from the given classname. * @param classname - The name of the class to resolve. * @param namespaceTree - The namespace tree to search within. * @returns The resolved symbol node or null if not found. */ private resolveSymbol( classname: string, namespaceTree: NamespaceNode, filepath: string, ): SymbolNode | null { // Remove any generic type information from the classname // Classes in the type argument list are managed on their own. const cleanClassname = classname.split("<")[0]; let cutClassname = cleanClassname; // Try to find the class in the resolved imports let ucls = null; while (!ucls) { ucls = this.usingResolver.findClassInImports( this.resolvedImports, cleanClassname, filepath, ); if (!cutClassname.includes(".")) { break; } cutClassname = cutClassname.split(".").slice(0, -1).join("."); } if (ucls) { return ucls; } // Try to find the class in the namespace tree let cls = null; cutClassname = cleanClassname; while (!cls) { cls = this.nsMapper.findClassInTree(namespaceTree, cutClassname); if (!cutClassname.includes(".")) { break; } cutClassname = cutClassname.split(".").slice(0, -1).join("."); } if (cls) { return cls; } return null; } /** * Gets the classes that are called for variable declarations and object creations. * This does not manage static calls such as System.Math.Abs(-1). * @param node - The syntax node to analyze. * @param namespaceTree - The namespace tree to search within. * @returns An object containing resolved and unresolved symbols. */ #getCalledClasses( node: Parser.SyntaxNode, namespaceTree: NamespaceNode, filepath: string, ): Invocations { const invocations: Invocations = { resolvedSymbols: [], unresolved: [], }; // Query to capture object creation expressions and variable declarations const catches = calledClassesQuery.captures(node); // Process each captured class name catches.forEach((ctc) => { const classname = ctc.node.text; const resolvedSymbol = this.resolveSymbol( classname, namespaceTree, filepath, ); if (resolvedSymbol) { invocations.resolvedSymbols.push(resolvedSymbol); } else { // If class not found, mark as unresolved invocations.unresolved.push(classname); } }); return invocations; } /** * Resolves member accesses within the given syntax node. * @param node - The syntax node to analyze. * @param namespaceTree - The namespace tree to search within. * @returns An object containing resolved and unresolved symbols. */ #resolveMemberAccesses( node: Parser.SyntaxNode, namespaceTree: NamespaceNode, filepath: string, ): Invocations { // Get variable names to filter out variable-based invocations const variablenames = this.#getVariables(node); const invocations: Invocations = { resolvedSymbols: [], unresolved: [], }; // Query to capture invocation expressions const catches = memberAccessQuery.captures(node); // Process each captured access expression catches.forEach((ctc) => { // Remove intermediate members (e.g., System.Mario in System.Mario.Bros) if (ctc.node.type === "member_access_expression") return; // Get the root member access expression const mae = ctc.node.children.filter( (child) => child.type === "member_access_expression", ); let func = mae.map((m) => m.text); // Among all the matches, even the functions dont have parentheses // That means that nodes with parentheses in it are intermediate members // They get through the net because their type is invocation_expression. func = func.filter((f) => !f.includes("(")); func.forEach((f) => { // The query gives us a full invocation, // but we only want a class or namespace name for the called class. const funcParts = f.split("."); const classname = funcParts.slice(0, -1).join("."); // If the function is called from a variable, then we ignore it. // (Because the dependency will already be managed by the variable creation) if (variablenames.includes(classname)) { return; } const resolvedSymbol = this.resolveSymbol( classname, namespaceTree, filepath, ); if (resolvedSymbol) { invocations.resolvedSymbols.push(resolvedSymbol); } else { // If class not found, mark as unresolved invocations.unresolved.push(classname); } }); }); return invocations; } /** * Resolves extension uses within the given syntax node. * @param node - The syntax node to analyze. * @param namespaceTree - The namespace tree to search within. * @returns An object containing resolved classes the extensions come from. */ #resolveExtensionUses( node: Parser.SyntaxNode, filepath: string, ): Invocations { const invocations: Invocations = { resolvedSymbols: [], unresolved: [], }; const catches = invocationQuery.captures(node); catches.forEach((ctc) => { let method = ctc.node.text; if (ctc.node.type === "generic_name") { const index = method.indexOf("<"); method = method.substring(0, index); } const extMethods = this.#findExtension(method, filepath); for (const extMethod of extMethods) { // TODO : check if there is the correct amount of type arguments invocations.resolvedSymbols.push(extMethod.symbol); } }); return invocations; } #resolveAttributeUses( node: Parser.SyntaxNode, filepath: string, ): Invocations { const invocations: Invocations = { resolvedSymbols: [], unresolved: [], }; const catches = attributeQuery.captures(node); catches.forEach((ctc) => { let method = ctc.node.text; if (ctc.node.type === "generic_name") { const index = method.indexOf("<"); method = method.substring(0, index); } const resolvedSymbol = this.resolveSymbol( method, this.nsMapper.nsTree, filepath, ); if (resolvedSymbol) { invocations.resolvedSymbols.push(resolvedSymbol); } else { // If class not found, try again by adding "Attribute" to the name const resolvedSymbol = this.resolveSymbol( method + "Attribute", this.nsMapper.nsTree, filepath, ); if (resolvedSymbol) { invocations.resolvedSymbols.push(resolvedSymbol); } else { // If class not found, mark as unresolved invocations.unresolved.push(ctc.node.text); } } }); return invocations; } /** * Finds an extension among the available extension methods. * The available extension methods are only in used namespaces. * @param ext - The extension method to find. * @returns The resolved symbol node or null if not found. */ #findExtension(ext: string, filepath: string): ExtensionMethod[] { const methods: ExtensionMethod[] = []; const usedNamespaces = this.usingResolver.resolveUsingDirectives(filepath).internal; // Check if the extension method is in the extensions map for (const ns of usedNamespaces) { if ( ns.namespace && this.extensions[this.nsMapper.getFullNSName(ns.namespace)] ) { const extensions = this.extensions[this.nsMapper.getFullNSName(ns.namespace)]; const extMethods = extensions.filter((method) => method.name === ext); if (extMethods.length > 0) { methods.push(...extMethods); } } } return methods; } /** * Gets the invocations from a file. * @param filepath - The path of the file to analyze. * @returns An object containing resolved and unresolved symbols. */ getInvocationsFromFile(filepath: string): Invocations { if (this.cache.has(filepath)) { return this.cache.get(filepath) as Invocations; } const file: File | undefined = this.nsMapper.getFile(filepath); if (!file) { return { resolvedSymbols: [], unresolved: [], }; } const invocations = this.getInvocationsFromNode(file.rootNode, filepath); this.cache.set(filepath, invocations); return invocations; } /** * Gets the classes used in a file. * @param node - The syntax node to analyze. * @param filepath - The path of the file being analyzed. * @returns An object containing resolved and unresolved symbols. */ getInvocationsFromNode( node: Parser.SyntaxNode, filepath: string, ): Invocations { this.resolvedImports = this.usingResolver.resolveUsingDirectives(filepath); const invocations: Invocations = { resolvedSymbols: [], unresolved: [], }; // Get classes called in variable declarations and object creations const calledClasses = this.#getCalledClasses( node, this.nsMapper.nsTree, filepath, ); // Resolve member accesses expressions const memberAccesses = this.#resolveMemberAccesses( node, this.nsMapper.nsTree, filepath, ); // Resolve extension uses const extensions = this.#resolveExtensionUses(node, filepath); // Resolve attribute uses const attributes = this.#resolveAttributeUses(node, filepath); // Combine results from both methods, ensuring uniqueness with Set invocations.resolvedSymbols = [ ...new Set([ ...calledClasses.resolvedSymbols, ...memberAccesses.resolvedSymbols, ...extensions.resolvedSymbols, ...attributes.resolvedSymbols, ]), ]; invocations.unresolved = [ ...new Set([ ...calledClasses.unresolved, ...memberAccesses.unresolved, ...extensions.unresolved, ...attributes.unresolved, ]), ]; return invocations; } /** * Checks if a symbol is used in a file. * @param filepath - The path of the file to check. * @param symbol - The symbol to check for. * @returns True if the symbol is used in the file, false otherwise. */ public isUsedInFile(filepath: string, symbol: SymbolNode): boolean { const invocations = this.getInvocationsFromFile(filepath); return invocations.resolvedSymbols.some((inv) => inv.name === symbol.name); } /** * Checks if a using directive is useful in a file. * @param invocations - The invocations in the file. * @param using - The using directive to check for. * @returns True if the using directive is useful, false otherwise. */ public isUsingUseful( invocations: Invocations, usingD: UsingDirective, ): boolean { const usedNamespace = this.usingResolver.resolveUsingDirective(usingD); if (usedNamespace instanceof ExternalSymbol) return true; return invocations.resolvedSymbols.some( (inv) => (usedNamespace.namespace && inv.namespace === this.nsMapper.getFullNSName(usedNamespace.namespace)) || (usedNamespace.symbol && inv.name === usedNamespace.symbol.name) || (usedNamespace.symbol && inv.parent && inv.parent.name === usedNamespace.symbol.name), ); } } ================================================ FILE: src/languagePlugins/csharp/metricsAnalyzer/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpMetricsAnalyzer } from "./index.ts"; import { csharpFilesFolder, getCSharpFilesMap } from "../testFiles/index.ts"; import { join } from "@std/path"; describe("CSharpMetricsAnalyzer", () => { const analyzer = new CSharpMetricsAnalyzer(); const files = getCSharpFilesMap(); const analyzeFile = (filePath: string) => { const absolutePath = join(csharpFilesFolder, filePath); const file = files.get(absolutePath); if (!file) { throw new Error(`File not found: ${absolutePath}`); } return analyzer.analyzeNode(file.rootNode); }; test("Analyzes 2Namespaces1File.cs", () => { const metrics = analyzeFile("2Namespaces1File.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 0, // No control flow statements in this file linesCount: 42, // Total lines in the file codeLinesCount: 38, // Excluding blank lines and comments characterCount: expect.any(Number), // Total characters in the file codeCharacterCount: expect.any(Number), // Characters excluding comments and whitespace }); }); test("Analyzes Models.cs", () => { const metrics = analyzeFile("Models.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 0, // No control flow statements in this file linesCount: 23, // Total lines in the file codeLinesCount: 22, // Excluding blank lines and comments characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); test("Analyzes Namespaced.cs", () => { const metrics = analyzeFile("Namespaced.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 4, linesCount: 18, codeLinesCount: 17, characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); test("Analyzes Nested.cs", () => { const metrics = analyzeFile("Nested.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 1, linesCount: 32, codeLinesCount: 31, characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); test("Analyzes OtherFileSameNamespace.cs", () => { const metrics = analyzeFile("OtherFileSameNamespace.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 0, // No control flow statements linesCount: 3, codeLinesCount: 2, characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); test("Analyzes Program.cs", () => { const metrics = analyzeFile("Program.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 0, // No control flow statements in the main method linesCount: 35, codeLinesCount: 27, characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); test("Analyzes SemiNamespaced.cs", () => { const metrics = analyzeFile("SemiNamespaced.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 0, linesCount: 32, codeLinesCount: 29, characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); test("Analyzes Usage.cs", () => { const metrics = analyzeFile("Usage.cs"); expect(metrics).toMatchObject({ cyclomaticComplexity: 2, linesCount: 25, codeLinesCount: 24, characterCount: expect.any(Number), codeCharacterCount: expect.any(Number), }); }); }); ================================================ FILE: src/languagePlugins/csharp/metricsAnalyzer/index.ts ================================================ import Parser from "tree-sitter"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; /** * Interface for code volumes */ interface CodeCounts { /** Number of lines of code */ lines: number; /** Number of characters of code */ characters: number; } interface CommentSpan { start: { row: number; column: number }; end: { row: number; column: number }; } /** * Represents complexity metrics for a C# symbol */ export interface CSharpComplexityMetrics { /** Cyclomatic complexity (McCabe complexity) */ cyclomaticComplexity: number; /** Code lines (not including whitespace or comments) */ codeLinesCount: number; /** Total lines (including whitespace and comments) */ linesCount: number; /** Characters of actual code (excluding comments and excessive whitespace) */ codeCharacterCount: number; /** Total characters in the entire symbol */ characterCount: number; } // Tree-sitter query to find complexity-related nodes const complexityQuery = new Parser.Query( csharpParser.getLanguage(), ` (if_statement) @complexity (while_statement) @complexity (for_statement) @complexity (do_statement) @complexity (switch_section) @complexity (conditional_expression) @complexity (try_statement) @complexity (catch_clause) @complexity (finally_clause) @complexity `, ); const commentQuery = new Parser.Query( csharpParser.getLanguage(), ` (comment) @comment `, ); export class CSharpMetricsAnalyzer { /** * Calculates metrics for a C# symbol. * @param node - The AST node to analyze * @returns The complexity metrics for the node */ public analyzeNode(node: Parser.SyntaxNode): CSharpComplexityMetrics { const complexityCount = this.getComplexityCount(node); const linesCount = node.endPosition.row - node.startPosition.row + 1; const codeCounts = this.getCodeCounts(node); const codeLinesCount = codeCounts.lines; const characterCount = node.endIndex - node.startIndex; const codeCharacterCount = codeCounts.characters; return { cyclomaticComplexity: complexityCount, linesCount, codeLinesCount, characterCount, codeCharacterCount, }; } private getComplexityCount(node: Parser.SyntaxNode): number { const complexityMatches = complexityQuery.captures(node); return complexityMatches.length; } /** * Finds comments in the given node and returns their spans. * @param node - The AST node to analyze * @returns An object containing pure comment lines and comment spans */ private findComments( node: Parser.SyntaxNode, lines: string[], ): { pureCommentLines: Set; commentSpans: CommentSpan[]; } { const pureCommentLines = new Set(); const commentSpans: CommentSpan[] = []; const commentCaptures = commentQuery.captures(node); for (const capture of commentCaptures) { const commentNode = capture.node; // Record the comment span for character counting commentSpans.push({ start: { row: commentNode.startPosition.row, column: commentNode.startPosition.column, }, end: { row: commentNode.endPosition.row, column: commentNode.endPosition.column, }, }); // Check if the comment starts at the beginning of the line (ignoring whitespace) const lineIdx = commentNode.startPosition.row - node.startPosition.row; if (lineIdx >= 0 && lineIdx < lines.length) { const lineText = lines[lineIdx]; const textBeforeComment = lineText.substring( 0, commentNode.startPosition.column, ); // If there's only whitespace before the comment, it's a pure comment line if (textBeforeComment.trim().length === 0) { for ( let line = commentNode.startPosition.row; line <= commentNode.endPosition.row; line++ ) { pureCommentLines.add(line); } } } } return { pureCommentLines, commentSpans }; } /** * Finds all empty lines in a node * * @param node The syntax node to analyze * @param lines The lines of text in the node * @returns Set of line numbers that are empty */ private findEmptyLines( node: Parser.SyntaxNode, lines: string[], ): Set { const emptyLines = new Set(); for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; if (lines[i].trim().length === 0) { emptyLines.add(lineIndex); } } return emptyLines; } private getCodeCounts(node: Parser.SyntaxNode): CodeCounts { const lines = node.text.split(/\r?\n/); const linesCount = lines.length; // Find comments and their spans const { pureCommentLines, commentSpans } = this.findComments(node, lines); // Find empty lines const emptyLines = this.findEmptyLines(node, lines); // Calculate code lines const nonCodeLines = new Set([...pureCommentLines, ...emptyLines]); const codeLinesCount = linesCount - nonCodeLines.size; let codeCharCount = 0; // Process each line individually for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; const line = lines[i]; // Skip empty lines and pure comment lines if (emptyLines.has(lineIndex) || pureCommentLines.has(lineIndex)) { continue; } // Process line for code characters let lineText = line; // Remove comment content from the line if present for (const span of commentSpans) { if (span.start.row === lineIndex) { // Comment starts on this line lineText = lineText.substring(0, span.start.column); } } // Count normalized code characters (trim excessive whitespace) const normalizedText = lineText.trim().replace(/\s+/g, " "); codeCharCount += normalizedText.length; } return { lines: codeLinesCount, characters: codeCharCount, }; } } ================================================ FILE: src/languagePlugins/csharp/namespaceMapper/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpNamespaceMapper } from "./index.ts"; import { csharpFilesFolder, getCSharpFilesMap } from "../testFiles/index.ts"; import { join } from "@std/path"; import type { File } from "../namespaceResolver/index.ts"; describe("NamespaceMapper", () => { const files: Map = getCSharpFilesMap(); const nsMapper = new CSharpNamespaceMapper(files); test("should build a namespace tree", () => { const nsTree = nsMapper.buildNamespaceTree(); // Check root namespace expect(nsTree.name).toBe(""); expect(nsTree.exports).toHaveLength(3); expect(nsTree.childrenNamespaces).toHaveLength(6); // Check exports in root namespace expect(nsTree.exports).toEqual( expect.arrayContaining([ expect.objectContaining({ name: "Freeman", filepath: join(csharpFilesFolder, "SemiNamespaced.cs"), }), expect.objectContaining({ name: "HeadCrab", filepath: join(csharpFilesFolder, "SemiNamespaced.cs"), }), expect.objectContaining({ name: "Usage", filepath: join(csharpFilesFolder, "Usage.cs"), }), ]), ); // Check ChickenBurger namespace const chickenBurgerNs = nsTree.childrenNamespaces.find( (ns) => ns.name === "ChickenBurger", ); expect(chickenBurgerNs).toBeDefined(); if (!chickenBurgerNs) return; expect(chickenBurgerNs.exports).toHaveLength(3); expect(chickenBurgerNs.childrenNamespaces).toHaveLength(0); // Check MyApp namespace const myAppNs = nsTree.childrenNamespaces.find((ns) => ns.name === "MyApp"); expect(myAppNs).toBeDefined(); if (!myAppNs) return; expect(myAppNs.exports).toHaveLength(0); expect(myAppNs.childrenNamespaces).toHaveLength(2); // Check Models namespace under MyApp const modelsNs = myAppNs.childrenNamespaces.find( (ns) => ns.name === "Models", ); expect(modelsNs).toBeDefined(); if (!modelsNs) return; expect(modelsNs.exports).toHaveLength(5); expect(modelsNs.childrenNamespaces).toHaveLength(0); // Check BeefBurger namespace under MyApp const beefBurgerNs = myAppNs.childrenNamespaces.find( (ns) => ns.name === "BeefBurger", ); expect(beefBurgerNs).toBeDefined(); if (!beefBurgerNs) return; expect(beefBurgerNs.exports).toHaveLength(4); expect(beefBurgerNs.childrenNamespaces).toHaveLength(0); // Check MyNamespace namespace const myNamespaceNs = nsTree.childrenNamespaces.find( (ns) => ns.name === "MyNamespace", ); expect(myNamespaceNs).toBeDefined(); if (!myNamespaceNs) return; expect(myNamespaceNs.exports).toHaveLength(1); expect(myNamespaceNs.childrenNamespaces).toHaveLength(0); // Check OuterNamespace namespace const outerNamespaceNs = nsTree.childrenNamespaces.find( (ns) => ns.name === "OuterNamespace", ); expect(outerNamespaceNs).toBeDefined(); if (!outerNamespaceNs) return; expect(outerNamespaceNs.exports).toHaveLength(2); expect(outerNamespaceNs.childrenNamespaces).toHaveLength(1); // Check InnerNamespace under OuterNamespace const innerNamespaceNs = outerNamespaceNs.childrenNamespaces.find( (ns) => ns.name === "InnerNamespace", ); expect(innerNamespaceNs).toBeDefined(); if (!innerNamespaceNs) return; expect(innerNamespaceNs.exports).toHaveLength(1); expect(innerNamespaceNs.childrenNamespaces).toHaveLength(0); // Check Tests namespace const testsNs = nsTree.childrenNamespaces.find((ns) => ns.name === "Tests"); expect(testsNs).toBeDefined(); if (!testsNs) return; expect(testsNs.exports).toHaveLength(1); expect(testsNs.childrenNamespaces).toHaveLength(0); // Check HalfNamespace namespace const halfNamespaceNs = nsTree.childrenNamespaces.find( (ns) => ns.name === "HalfNamespace", ); expect(halfNamespaceNs).toBeDefined(); if (!halfNamespaceNs) return; expect(halfNamespaceNs.exports).toHaveLength(1); expect(halfNamespaceNs.childrenNamespaces).toHaveLength(0); }); test("Finds classes accurately in the tree", () => { const nsTree = nsMapper.buildNamespaceTree(); const order = nsMapper.findClassInTree(nsTree, "Order"); expect(order).toMatchObject({ name: "Order", filepath: join(csharpFilesFolder, "Models.cs"), }); const innerClass = nsMapper.findClassInTree(nsTree, "InnerClass"); expect(innerClass).toMatchObject({ name: "InnerClass", filepath: join(csharpFilesFolder, "Nested.cs"), }); const chickenbun = nsMapper.findClassInTree(nsTree, "ChickenBurger.Bun"); expect(chickenbun).toMatchObject({ name: "Bun", filepath: join(csharpFilesFolder, "2Namespaces1File.cs"), }); }); test("Finds namespaces accurately in the tree", () => { const nsTree = nsMapper.buildNamespaceTree(); const myapp = nsMapper.findNamespaceInTree(nsTree, "MyApp"); expect(myapp).toBeDefined(); if (!myapp) return; expect(myapp.childrenNamespaces.length).toBe(2); const halfnamespace = nsMapper.findNamespaceInTree(nsTree, "HalfNamespace"); expect(halfnamespace).toMatchObject({ name: "HalfNamespace", exports: [{ name: "Gordon" }], childrenNamespaces: [], }); const models = nsMapper.findNamespaceInTree(nsTree, "MyApp.Models"); expect(models).toMatchObject({ name: "Models", exports: expect.any(Array), childrenNamespaces: expect.any(Array), }); const innernamespace = nsMapper.findNamespaceInTree( nsTree, "OuterNamespace.InnerNamespace", ); expect(innernamespace).toMatchObject({ name: "InnerNamespace", exports: [{ name: "InnerClass" }], childrenNamespaces: [], }); }); }); ================================================ FILE: src/languagePlugins/csharp/namespaceMapper/index.ts ================================================ import type Parser from "tree-sitter"; import { CSharpNamespaceResolver, type SymbolType, } from "../namespaceResolver/index.ts"; /** * Interface representing a namespace node in the namespace tree. */ export interface NamespaceNode { /** The name of the namespace */ name: string; /** List of classes and types exported by the namespace */ exports: SymbolNode[]; /** List of child namespaces */ childrenNamespaces: NamespaceNode[]; /** Parent namespace */ parentNamespace?: NamespaceNode; } /** * Interface representing a symbol node in the namespace tree. */ export interface SymbolNode { /** The name of the symbol */ name: string; /** The type of the symbol (class, interface, etc.) */ type: SymbolType; /** Kept for ambiguity resolution */ namespace: string; /** The file path where the symbol is defined */ filepath: string; /** The syntax node corresponding to the symbol */ node: Parser.SyntaxNode; /** The parent of the symbol if it is nested */ parent?: SymbolNode; } const DEBUG_NAMESPACE = "namespace"; const DEBUG_SYMBOL = "symbol"; type DebugType = typeof DEBUG_NAMESPACE | typeof DEBUG_SYMBOL; /** * Interface representing a debug node in the namespace tree. */ export interface DebugNode { /** The name of the debug node */ name: string; /** The type of the debug node */ type: DebugType; /** The children of the debug node */ children: DebugNode[]; } export class CSharpNamespaceMapper { files: Map; #nsResolver: CSharpNamespaceResolver; nsTree: NamespaceNode; #exportsCache: Map = new Map(); fileExports: Map = new Map(); constructor( files: Map, ) { this.files = files; this.#nsResolver = new CSharpNamespaceResolver(); this.nsTree = this.buildNamespaceTree(); } /** * Gets a file object from the files map. * @param key - The key of the file. * @returns The file object. */ getFile(key: string) { return this.files.get(key); } /** * Gets the exports for a given filepath. * @param filepath - The path of the file to get exports for. * @returns An array of exported symbols. */ getFileExports(filepath: string): SymbolNode[] { return this.fileExports.get(filepath) ?? []; } /** * Builds the fileExports map from the namespace tree. * @param tree - The root of the namespace tree. */ #buildFileExports(tree: NamespaceNode) { tree.exports.forEach((symbol) => { if (!this.fileExports.has(symbol.filepath)) { this.fileExports.set(symbol.filepath, []); } this.fileExports.get(symbol.filepath)?.push(symbol); }); tree.childrenNamespaces.forEach((ns) => { this.#buildFileExports(ns); }); } /** * Adds a namespace to the final tree. * @param namespace - The namespace node to add. * @param tree - The root of the namespace tree. */ #addNamespaceToTree(namespace: NamespaceNode, tree: NamespaceNode) { // Deconstruct the namespace's name, so that A.B // becomes B, child of A. const parts = namespace.name.split("."); let current = tree; let previous = tree; // For each part of the namespace, we check if it's // already in the tree. If it is, we go to the next // part. If it isn't, we add it to the tree. if (namespace.name !== "") { parts.forEach((part) => { let child = current.childrenNamespaces.find((ns) => ns.name === part); if (!child) { child = { name: part, exports: [], childrenNamespaces: [], parentNamespace: current, }; current.childrenNamespaces.push(child); } previous = current; current = child; }); } // Once we're done with the parts, we add the classes // and children namespaces to the current namespace. current.exports.push(...namespace.exports); namespace.childrenNamespaces.forEach((ns) => { this.#addNamespaceToTree(ns, current); }); // We also set the parent namespace for each child namespace. current.parentNamespace = previous; } /** * Assigns namespaces to classes, used for ambiguity resolution. * @param tree - The root of the namespace tree. * @param parentNamespace - The parent namespace name. */ #assignNamespacesToClasses(tree: NamespaceNode, parentNamespace = "") { const fullNamespace = parentNamespace ? `${parentNamespace}.${tree.name}` : tree.name; tree.exports.forEach((cls) => { cls.namespace = fullNamespace; }); tree.childrenNamespaces.forEach((ns) => { this.#assignNamespacesToClasses(ns, fullNamespace); }); } #assignParentNamespaces(tree: NamespaceNode) { tree.childrenNamespaces.forEach((ns) => { ns.parentNamespace = tree; this.#assignParentNamespaces(ns); }); } /** * Builds a tree of namespaces from the parsed files. * @returns The root of the namespace tree. */ buildNamespaceTree(): NamespaceNode { const namespaceTree: NamespaceNode = { name: "", exports: [], childrenNamespaces: [], }; // Parse all files. this.files.forEach((file) => { const namespaces = this.#nsResolver .getNamespacesFromFile(file) .map((ns) => ns as NamespaceNode); namespaces.forEach((namespace) => { this.#assignParentNamespaces(namespace); this.#addNamespaceToTree(namespace, namespaceTree); }); }); // Assign namespaces to classes. this.#assignNamespacesToClasses(namespaceTree); // Build the file exports map. this.#buildFileExports(namespaceTree); return namespaceTree; } /** * Converts a namespace or symbol node to a debug node. * Used for serialisation. * @param node - The node to convert. * @returns The converted node. */ #convertNodeToDebug(node: NamespaceNode | SymbolNode): DebugNode { if ("childrenNamespaces" in node) { return { name: node.name, type: DEBUG_NAMESPACE, children: [ ...node.childrenNamespaces.map((child: NamespaceNode) => this.#convertNodeToDebug(child) ), ...node.exports.map((symbol: SymbolNode) => this.#convertNodeToDebug(symbol) ), ], }; } else { return { name: node.name, type: DEBUG_SYMBOL, children: [], }; } } /** * Saves the namespace tree to a file for debugging purposes. * @param filepath - The path to the file where the debug tree will be saved. * @returns The debug tree. */ saveDebugTree(filepath: string): DebugNode { const debugTree: DebugNode = this.#convertNodeToDebug(this.nsTree); Deno.writeTextFileSync(filepath, JSON.stringify(debugTree, null, 2)); return debugTree; } /** * Finds a namespace in the namespace tree. * @param tree - The root of the namespace tree. * @param namespaceName - The name of the namespace to find. * @returns The namespace node if found, otherwise null. */ findNamespaceInTree( tree: NamespaceNode, namespaceName: string, ): NamespaceNode | null { if (namespaceName === "") { return tree; } if (namespaceName.includes(".")) { const parts = namespaceName.split("."); const simpleNamespaceName = parts[0]; const rest = parts.slice(1).join("."); const namespace = tree.childrenNamespaces.find( (ns) => ns.name === simpleNamespaceName, ); if (namespace) { return this.findNamespaceInTree(namespace, rest); } } return ( tree.childrenNamespaces.find((ns) => ns.name === namespaceName) ?? null ); } /** * Gets the full namespace name of a given namespace node. * @param namespace - The namespace node to get the full name for. * @returns The full namespace name. */ getFullNSName(namespace: NamespaceNode): string { if (namespace.name === "") { return ""; } if (!namespace.parentNamespace || namespace.parentNamespace.name === "") { return namespace.name; } return `${this.getFullNSName(namespace.parentNamespace)}.${namespace.name}`; } /** * Finds a class in the namespace tree. * @param tree - The root of the namespace tree. * @param className - The name of the class to find. * @returns The symbol node if found, otherwise null. */ findClassInTree(tree: NamespaceNode, className: string): SymbolNode | null { // Management of qualified names if (className.includes(".")) { const parts = className.split("."); const simpleClassName = parts[parts.length - 1]; const namespaceParts = parts.slice(0, -1).reverse(); // Find all classes with the same name const matchingClasses: SymbolNode[] = []; const searchClasses = (namespace: NamespaceNode) => { namespace.exports.forEach((cls) => { if (cls.name === simpleClassName) { matchingClasses.push(cls); } }); namespace.childrenNamespaces.forEach((childNamespace) => { searchClasses(childNamespace); }); }; searchClasses(tree); // Filter classes by walking through the namespace parts backwards for (const cls of matchingClasses) { const currentNamespace = cls.namespace.split(".").reverse(); let matches = true; for (let i = 0; i < namespaceParts.length; i++) { if (namespaceParts[i] !== currentNamespace[i]) { matches = false; break; } } if (matches) { return cls; } } return null; } // Find the class in the current node's classes. if (tree.exports.some((cls) => cls.name === className)) { return tree.exports.find((cls) => cls.name === className) ?? null; } // Recursively search in children namespaces. for (const namespace of tree.childrenNamespaces) { const found = this.findClassInTree(namespace, className); if (found) { return found; } } return null; } /** * Gets all the exports for a file from the namespace tree. * @param filepath - The path of the file to get exports for. * @returns An array of exported symbols. */ getExportsForFile(filepath: string): SymbolNode[] { if (this.#exportsCache.has(filepath)) { return this.#exportsCache.get(filepath) ?? []; } const exports: SymbolNode[] = []; const searchClassesInNamespace = (namespace: NamespaceNode) => { namespace.exports.forEach((symbol) => { if (symbol.filepath === filepath) { exports.push(symbol); } }); namespace.childrenNamespaces.forEach((childNamespace) => { searchClassesInNamespace(childNamespace); }); }; searchClassesInNamespace(this.nsTree); this.#exportsCache.set(filepath, exports); return exports; } } ================================================ FILE: src/languagePlugins/csharp/namespaceResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpNamespaceResolver, type File } from "./index.ts"; import { join } from "@std/path"; import { csharpFilesFolder, getCSharpFilesMap } from "../testFiles/index.ts"; describe("NamespaceResolver", () => { const files: Map = getCSharpFilesMap(); const nsResolver: CSharpNamespaceResolver = new CSharpNamespaceResolver(); test("2Namespaces1File.cs", () => { const file = files.get( join(csharpFilesFolder, "2Namespaces1File.cs"), ) as File; const namespaces = nsResolver.getNamespacesFromFile(file); expect(namespaces).toMatchObject([ { name: "", exports: [], childrenNamespaces: [ { name: "MyApp.BeefBurger", exports: [{ name: "Steak" }, { name: "Cheese" }, { name: "Bun" }], childrenNamespaces: [], }, { name: "ChickenBurger", exports: [{ name: "Chicken" }, { name: "Salad" }, { name: "Bun" }], childrenNamespaces: [], }, ], }, ]); }); test("Models.cs", () => { const file = files.get(join(csharpFilesFolder, "Models.cs")) as File; const namespaces = nsResolver.getNamespacesFromFile(file); expect(namespaces).toMatchObject([ { name: "MyApp.Models", exports: [ { name: "User", type: "class" }, { name: "Order", type: "struct" }, { name: "OrderStatus", type: "enum" }, { name: "IOrder", type: "interface" }, { name: "OrderDelegate", type: "delegate" }, ], childrenNamespaces: [], }, ]); }); test("Namespaced.cs", () => { const file = files.get(join(csharpFilesFolder, "Namespaced.cs")) as File; const namespaces = nsResolver.getNamespacesFromFile(file); expect(namespaces).toMatchObject([ { name: "", exports: [], childrenNamespaces: [ { name: "MyNamespace", exports: [{ name: "MyClass" }], childrenNamespaces: [], }, ], }, ]); }); test("Nested.cs", () => { const file = files.get(join(csharpFilesFolder, "Nested.cs")) as File; const namespaces = nsResolver.getNamespacesFromFile(file); expect(namespaces).toMatchObject([ { name: "", exports: [], childrenNamespaces: [ { name: "OuterNamespace", exports: [ { name: "OuterClass" }, { name: "OuterInnerClass", parent: { name: "OuterClass" }, }, ], childrenNamespaces: [ { name: "InnerNamespace", exports: [{ name: "InnerClass" }], childrenNamespaces: [], }, ], }, ], }, ]); }); test("SemiNamespaced.cs", () => { const file = files.get( join(csharpFilesFolder, "SemiNamespaced.cs"), ) as File; const namespaces = nsResolver.getNamespacesFromFile(file); expect(namespaces).toMatchObject([ { name: "", exports: [{ name: "Freeman" }, { name: "HeadCrab" }], childrenNamespaces: [ { name: "HalfNamespace", exports: [{ name: "Gordon" }], childrenNamespaces: [], }, ], }, ]); }); }); ================================================ FILE: src/languagePlugins/csharp/namespaceResolver/index.ts ================================================ import Parser from "tree-sitter"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; // Constants representing different types of symbols in C# export const CSHARP_CLASS_TYPE = "class"; export const CSHARP_STRUCT_TYPE = "struct"; export const CSHARP_ENUM_TYPE = "enum"; export const CSHARP_INTERFACE_TYPE = "interface"; export const CSHARP_RECORD_TYPE = "record"; export const CSHARP_DELEGATE_TYPE = "delegate"; /** Type alias for the different symbol types */ export type SymbolType = | typeof CSHARP_CLASS_TYPE | typeof CSHARP_STRUCT_TYPE | typeof CSHARP_ENUM_TYPE | typeof CSHARP_INTERFACE_TYPE | typeof CSHARP_RECORD_TYPE | typeof CSHARP_DELEGATE_TYPE; const fscopednamespacedeclquery = new Parser.Query( csharpParser.getLanguage(), ` (file_scoped_namespace_declaration name: (qualified_name) @id ) `, ); /** * Interface representing a file */ export interface File { /** The path of the file */ path: string; /** The root node of the file */ rootNode: Parser.SyntaxNode; } /** * Interface representing an exported symbol */ export interface ExportedSymbol { /** The name of the symbol */ name: string; /** The type of the symbol (i.e. class, struct, enum, etc.) */ type: SymbolType; /** The syntax node corresponding to the symbol */ node: Parser.SyntaxNode; /** The syntax node corresponding to the identifier */ identifierNode: Parser.SyntaxNode; /** The namespace of the symbol */ namespace?: string; /** The file path where the symbol is defined */ filepath: string; /** The parent symbol if this is a nested class */ parent?: ExportedSymbol; } /** * Interface representing a namespace */ export interface Namespace { /** The name of the namespace */ name: string; /** The syntax node corresponding to the namespace */ node: Parser.SyntaxNode; /** Optional because the root namespace doesn't have an identifier */ identifierNode?: Parser.SyntaxNode; /** List of classes and types exported by the namespace */ exports: ExportedSymbol[]; /** List of child namespaces */ childrenNamespaces: Namespace[]; } export class CSharpNamespaceResolver { parser: Parser = csharpParser; #currentFile: string; #cache: Map = new Map(); constructor() { this.#currentFile = ""; } /** * Parses namespaces from a file along with the exported classes * @param file The file to parse * @returns An array of namespaces found in the file */ getNamespacesFromFile(file: File): Namespace[] { // Check if the file is already in the cache const cacheValue = this.#cache.get(file.path); if (cacheValue) { return cacheValue; } // Set the current file, so that we can keep track of it in the recursive functions this.#currentFile = file.path; // Different behavior if the file has a file-scoped namespace const fileScopedNamespace = this.#getFileScopedNamespaceDeclaration( file.rootNode, ); let namespaces: Namespace[]; if (fileScopedNamespace) { // Get the namespaces from the file-scoped namespace namespaces = [ { name: fileScopedNamespace, node: file.rootNode, exports: this.#getExportsFromNode(file.rootNode), childrenNamespaces: [], }, ]; // Cache the namespaces this.#cache.set(file.path, namespaces); return namespaces; } else { // Get the namespaces from the root node namespaces = [ { name: "", node: file.rootNode, exports: this.#getExportsFromNode(file.rootNode), childrenNamespaces: this.#getNamespacesFromNode(file.rootNode), }, ]; } // Cache the namespaces this.#cache.set(file.path, namespaces); return namespaces; } /** * Gets the file-scoped namespace declaration from a node * @param node The syntax node to search for a file-scoped namespace declaration * @returns The name of the file-scoped namespace, if any */ #getFileScopedNamespaceDeclaration( node: Parser.SyntaxNode, ): string | undefined { return fscopednamespacedeclquery .captures(node) .map((capture) => capture.node.text)[0]; } /** * Recursively parses namespaces from a node * @param node The syntax node to parse for namespaces * @returns An array of namespaces found in the node */ #getNamespacesFromNode(node: Parser.SyntaxNode): Namespace[] { return node.children .filter((child) => child.type === "namespace_declaration") .map((child) => ({ name: this.#getIdentifierNode(child).text, node: child, identifierNode: this.#getIdentifierNode(child), exports: this.#getExportsFromNode(this.#getDeclarationList(child)), childrenNamespaces: this.#getNamespacesFromNode( this.#getDeclarationList(child), ), })); } /** * Gets the declaration list from a node * i.e. where the classes, namespaces and methods are declared * @param node The syntax node to search for a declaration list * @returns The declaration list node */ #getDeclarationList(node: Parser.SyntaxNode): Parser.SyntaxNode { const result = node.children.find( (child) => child.type === "declaration_list", ); if (!result) { throw new Error("Declaration list node not found"); } return result; } /** * Gets the name from a node * @param node The syntax node to search for an identifier * @returns The identifier node */ #getIdentifierNode(node: Parser.SyntaxNode): Parser.SyntaxNode { const result = node.childForFieldName("name"); if (!result) { throw new Error("Identifier node not found"); } return result; } /** * Gets the classes, structs and enums from a node * @param node The syntax node to search for exported symbols * @param parent The parent symbol if this is a nested class * @returns An array of exported symbols found in the node */ #getExportsFromNode( node: Parser.SyntaxNode, parent?: ExportedSymbol, ): ExportedSymbol[] { const exports: ExportedSymbol[] = []; node.children.forEach((child) => { if ( child.type === "class_declaration" || child.type === "struct_declaration" || child.type === "enum_declaration" || child.type === "interface_declaration" || child.type === "record_declaration" || child.type === "delegate_declaration" ) { let name = this.#getIdentifierNode(child).text; // Handle generic types // Kind of dirty, needs to be corrected if (name.includes("<")) { const index = name.indexOf("<"); name = name.substring(0, index); } const symbol: ExportedSymbol = { name: name, type: child.type.replace("_declaration", "") as SymbolType, node: child, identifierNode: this.#getIdentifierNode(child), filepath: this.#currentFile, parent, }; exports.push(symbol); // Recursively get nested classes if ( child.children.some( (grandChild) => grandChild.type === "declaration_list", ) ) { exports.push( ...this.#getExportsFromNode( this.#getDeclarationList(child), symbol, ), ); } } }); return exports; } /** * Recursively gets all the exported classes from a list of namespaces * @param namespaces The list of namespaces to search for exported symbols * @returns An array of all exported symbols found in the namespaces */ getExportsFromNamespaces(namespaces: Namespace[]): ExportedSymbol[] { let classes: ExportedSymbol[] = []; namespaces.forEach((ns) => { classes = classes.concat(ns.exports); classes = classes.concat( this.getExportsFromNamespaces(ns.childrenNamespaces), ); }); return classes; } } ================================================ FILE: src/languagePlugins/csharp/projectMapper/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpProjectMapper } from "./index.ts"; import { csharpFilesFolder, getCSharpFilesMap, getCsprojFilesMap, } from "../testFiles/index.ts"; import { CSharpUsingResolver } from "../usingResolver/index.ts"; import { CSharpNamespaceMapper } from "../namespaceMapper/index.ts"; import { join } from "@std/path"; describe("CSharpProjectMapper", () => { const csprojfiles = getCsprojFilesMap(); const parsedfiles = getCSharpFilesMap(); const projectMapper = new CSharpProjectMapper(csprojfiles); test("Project mapper definition", () => { expect(projectMapper.rootFolder).toBeDefined(); expect(projectMapper.subprojects).toBeDefined(); expect(projectMapper.subprojects.length).toBe(2); }); const nsmapper = new CSharpNamespaceMapper(parsedfiles); const usingResolver = new CSharpUsingResolver(nsmapper, projectMapper); const usagecsFile = join(csharpFilesFolder, "Usage.cs"); const globalusingcsFile = join( csharpFilesFolder, "Subfolder/GlobalUsings.cs", ); test("Global using resolution", () => { usingResolver.resolveUsingDirectives(usagecsFile); usingResolver.resolveUsingDirectives(globalusingcsFile); expect(usingResolver.getGlobalUsings(usagecsFile).internal.length).toBe(0); expect(usingResolver.getGlobalUsings(usagecsFile).external.length).toBe(1); expect( usingResolver.getGlobalUsings(globalusingcsFile).internal.length, ).toBe(1); expect( usingResolver.getGlobalUsings(globalusingcsFile).external.length, ).toBe(1); }); }); ================================================ FILE: src/languagePlugins/csharp/projectMapper/index.ts ================================================ import { basename, dirname, join } from "@std/path"; import type { ExternalSymbol, InternalSymbol, ResolvedImports, UsingDirective, } from "../usingResolver/index.ts"; /** * Represents a .NET project. */ export interface DotNetProject { /** * The root folder of the project. */ rootFolder: string; /** * The name of the project. */ name: string; /** * The path to the .csproj file of the project. */ csprojPath: string; /** * The content of the .csproj file of the project */ csprojContent: string; /** * The global usings resolved for the project. */ globalUsings: GlobalUsings; } /** * Interface for a subproject's global usings */ export interface GlobalUsings { /** * The internal symbols used in the project. */ internal: InternalSymbol[]; /** * The external symbols used in the project. */ external: ExternalSymbol[]; /** * The using directives that define the global usings. */ directives: UsingDirective[]; } export class CSharpProjectMapper { rootFolder: string; subprojects: DotNetProject[]; constructor(csprojFiles: Map) { this.rootFolder = this.#getRootFolder( Array.from(csprojFiles.values()).map((csproj) => csproj.path), ); this.subprojects = this.#makeSubprojects(csprojFiles); } /** * Recursively finds all .csproj files in the given directory and its subdirectories. * @param dir - The directory to search in. * @returns An array of dotnet projects (path to project and csproj file). */ #makeSubprojects( csprojFiles: Map, ): DotNetProject[] { const subprojects: DotNetProject[] = []; for (const [csprojPath, csprojContent] of csprojFiles) { const subproject: DotNetProject = { rootFolder: dirname(csprojPath).replace(/\\/g, "/"), // Ensure UNIX format name: basename(csprojPath, ".csproj").replace(/\\/g, "/"), // Ensure UNIX format csprojPath, csprojContent: csprojContent.content, globalUsings: { internal: [], external: [], directives: [] }, }; subprojects.push(subproject); } return subprojects; } /** * Gets the root folder of the project based on the provided file paths. * @param filepaths - An array of file paths. * @returns The root folder path. */ #getRootFolder(filepaths: string[]): string { if (filepaths.length === 0) { throw new Error( "No .csproj files found. Make sure to include .csproj files along with .cs files in .napirc and that such files exist in your project.", ); } const splitPaths = filepaths.map((filepath) => filepath.split("/")); const commonPath = splitPaths[0].slice(); for (let i = 0; i < commonPath.length; i++) { for (let j = 1; j < splitPaths.length; j++) { if (splitPaths[j][i] !== commonPath[i]) { commonPath.splice(i); let rootFolder = join("", ...commonPath).replace(/\\/g, "/"); if (filepaths[0].startsWith("/")) { rootFolder = "/" + rootFolder; } return rootFolder; } } } let rootFolder = join("", ...commonPath).replace(/\\/g, "/"); if (filepaths[0].startsWith("/")) { rootFolder = "/" + rootFolder; } return rootFolder; } /** * Finds the subproject that contains the given file. * @param filePath - The path to the file. * @returns The subproject that contains the file, or null if not found. */ findSubprojectForFile(filePath: string): DotNetProject | null { let mostPreciseProject: DotNetProject | null = null; // Check what subprojects contains the file // We assume that the most precise project is the one with the longest rootFolder // (In case there are nested projects) for (const project of this.subprojects) { if ( filePath.startsWith(project.rootFolder) || project.rootFolder === "." ) { if ( mostPreciseProject === null || project.rootFolder.length > mostPreciseProject.rootFolder.length ) { mostPreciseProject = project; } } } return mostPreciseProject; } /** * Updates the global usings for the subprojects * @param globalUsings - The global usings to set for the subprojects */ updateGlobalUsings(globalUsings: GlobalUsings, subproject: DotNetProject) { globalUsings.internal.forEach((symbol) => { subproject.globalUsings.internal.push(symbol); }); globalUsings.external.forEach((symbol) => { subproject.globalUsings.external.push(symbol); }); globalUsings.directives.forEach((directive) => { subproject.globalUsings.directives.push(directive); }); } /** * Gets the global usings for the given file. * @param filepath - The path to the file. * @returns The global usings for the file. */ getGlobalUsings(filepath: string): ResolvedImports { const subproject = this.findSubprojectForFile(filepath); if (subproject) { return subproject.globalUsings; } return { internal: [], external: [] }; } } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/2Namespaces1File.cs ================================================ using HalfNamespace; using MyApp.Models; // Useless using directive namespace MyApp.BeefBurger { public class Steak { private Gordon gordon; public Steak(Gordon gordon) { this.gordon = gordon; } public void Cook() { Console.WriteLine("Gordon, we need to cook."); } } public static class Cheese { public static void Melt(this Steak steak) { Console.WriteLine("Cheese melted."); } } public class Bun { } } namespace ChickenBurger { public class Chicken { } public class Salad { public T Item { get; set; } public void Add(T item) { Item = item; } } public class Bun { } } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Models.cs ================================================ namespace MyApp.Models; public class User { public string Name { get; set; } private string Password { get; set; } string Email { get; set; } } public struct Order { public int OrderId; public string Description; } public enum OrderStatus { Pending, Completed } public interface IOrder { void Process(); } public delegate void OrderDelegate(int orderId); ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Namespaced.cs ================================================ namespace MyNamespace { public class MyClass { public void MyMethod() { Console.WriteLine("MyMethod"); switch (5) { case 1: case 2: case 3: case 4: } } } } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Nested.cs ================================================ namespace OuterNamespace { public class OuterClass { public void OuterMethod() { Console.WriteLine("OuterMethod"); if (true) { Console.WriteLine("This is an if statement"); } } public class OuterInnerClass { public void OuterInnerMethod() { Console.WriteLine("OuterInnerMethod"); } } } namespace InnerNamespace { public class InnerClass { public void InnerMethod() { Console.WriteLine("InnerMethod"); } } } } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/OtherFileSameNamespace.cs ================================================ namespace MyApp.BeefBurger; public class Salad { } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Program.cs ================================================ using MyNamespace; using HalfNamespace; using static OuterNamespace.OuterClass; using OuterNamespace.InnerNamespace; using MyApp.Models; using MyApp.BeefBurger; namespace Tests { class Program { static void Main(string[] args) { // Test for ambiguity resolution Bun beefBun = new Bun(); ChickenBurger.Bun chickenBun = new ChickenBurger.Bun(); ChickenBurger.Salad salad = new ChickenBurger.Salad(); // Regular usage of imported namespaces MyClass myClass = new MyClass(); Gordon gordon = new Gordon(); gordon.Crowbar(); // Class that is in no namespace Freeman freeman = new Freeman(); freeman.Bite(); // Nested classes OuterInnerClass outerInnerClass = new OuterInnerClass(); InnerClass innerClass = new InnerClass(); // Enum OrderStatus orderStatus = OrderStatus.Pending; // Static class System.Math.Abs(-1).Equals(1).ToString(); } } } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/SemiNamespaced.cs ================================================ namespace HalfNamespace { public class Gordon { public void Crowbar() { Console.WriteLine("MyMethod"); } } } public class Freeman { public int Health { get; set; } public void Shotgun() { Console.WriteLine("MyMethod"); } } static class HeadCrab { public static void Bite(this Freeman freeman) { freeman.Health -= 10; } public static void Heal(this Freeman freeman) { freeman.Health += 10; } } ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Subfolder/GlobalUsings.cs ================================================ global using System; global using MyApp.Models; ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Subfolder/TestFiles.Subfolder.csproj ================================================ net9.0 enable enable ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/TestFiles.csproj ================================================ net9.0 enable enable ================================================ FILE: src/languagePlugins/csharp/testFiles/csharpFiles/Usage.cs ================================================ global using System.IO; using System; using System.Collections.Generic; using static System.Math; using Guy = MyApp.Models.User; using Valve = HalfNamespace; class Usage { public void ReadFile() { using (var reader = new System.IO.StreamReader("file.txt")) { try { string content = reader.ReadToEnd(); Console.WriteLine(content); } catch (Exception ex) { Console.WriteLine($"Error reading file: {ex.Message}"); } } } } ================================================ FILE: src/languagePlugins/csharp/testFiles/index.ts ================================================ import { extname, join } from "@std/path"; import type Parser from "tree-sitter"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; export const csharpFilesFolder = join( import.meta.dirname as string, "csharpFiles", ); const csharpFilesMap = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); const csprojFiles = new Map(); /** * Recursively finds all C# files in the given directory and its subdirectories. * @param dir - The directory to search in. */ function findCSharpFiles(dir: string) { const files = Deno.readDirSync(dir); files.forEach((file) => { const fullPath = join(dir, file.name); const stat = Deno.statSync(fullPath); if (stat.isDirectory) { if ( !fullPath.includes(".extracted") && !fullPath.includes("bin") && !fullPath.includes("obj") ) { findCSharpFiles(fullPath); } } else if (extname(fullPath) === ".cs") { const content = Deno.readTextFileSync(fullPath); const tree = csharpParser.parse(content); csharpFilesMap.set(fullPath, { path: fullPath, rootNode: tree.rootNode }); } }); } /** * Recursively finds all .csproj files in the given directory and its subdirectories. * @param dir - The directory to search in. */ function findCsprojFiles(dir: string): void { const files = Deno.readDirSync(dir); files.forEach((file) => { const fullPath = join(dir, file.name); const stat = Deno.statSync(fullPath); if (stat.isDirectory) { if ( !fullPath.includes(".extracted") && !fullPath.includes("bin") && !fullPath.includes("obj") ) { findCsprojFiles(fullPath); } } else if (extname(fullPath) === ".csproj") { const content = Deno.readTextFileSync(fullPath); csprojFiles.set(fullPath, { path: fullPath, content: content }); } }); } /** * Retrieves the map of C# files and their corresponding syntax trees. * @returns A map where the keys are file paths and the values are objects containing the file path and its syntax tree. */ export function getCSharpFilesMap() { findCSharpFiles(csharpFilesFolder); return csharpFilesMap; } /** * Retrieves the map of .csproj files and their content. * @returns A map where the keys are file paths and the values are the content of the .csproj files. */ export function getCsprojFilesMap() { findCsprojFiles(csharpFilesFolder); return csprojFiles; } ================================================ FILE: src/languagePlugins/csharp/usingResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { CSharpUsingResolver, ExternalSymbol, GLOBAL_USING, InternalSymbol, LOCAL_USING, USING_ALIAS, USING_CURRENT, USING_STATIC, } from "./index.ts"; import { CSharpNamespaceMapper } from "../namespaceMapper/index.ts"; import { csharpFilesFolder, getCSharpFilesMap, getCsprojFilesMap, } from "../testFiles/index.ts"; import { join } from "@std/path"; import type { File } from "../namespaceResolver/index.ts"; import { CSharpProjectMapper } from "../projectMapper/index.ts"; describe("UsingResolver", () => { const parsedfiles: Map = getCSharpFilesMap(); const csprojfiles = getCsprojFilesMap(); const nsmapper = new CSharpNamespaceMapper(parsedfiles); const projectMapper = new CSharpProjectMapper(csprojfiles); const resolver = new CSharpUsingResolver(nsmapper, projectMapper); test("Directive simple parsing", () => { const usingDirectives = resolver.parseUsingDirectives( join(csharpFilesFolder, "Usage.cs"), ); expect(usingDirectives).toMatchObject([ { type: GLOBAL_USING, id: "System.IO", }, { type: LOCAL_USING, id: "System", }, { type: LOCAL_USING, id: "System.Collections.Generic", }, { type: USING_STATIC, id: "System.Math", }, { type: USING_ALIAS, id: "MyApp.Models.User", alias: "Guy", }, { type: USING_ALIAS, id: "HalfNamespace", alias: "Valve", }, ]); }); test("Directive resolving", () => { const resolved = resolver.resolveUsingDirectives( join(csharpFilesFolder, "Usage.cs"), ); expect(resolved).toMatchObject({ internal: [ { usingtype: USING_ALIAS, alias: "Guy", symbol: { name: "User", type: "class", namespace: "MyApp.Models", }, }, { usingtype: USING_ALIAS, alias: "Valve", namespace: { name: "HalfNamespace", exports: expect.any(Array), childrenNamespaces: expect.any(Array), }, }, { usingtype: USING_CURRENT, namespace: { name: "", exports: expect.any(Array), childrenNamespaces: expect.any(Array), }, }, ], external: [ { usingtype: GLOBAL_USING, name: "System.IO", }, { usingtype: LOCAL_USING, name: "System", }, { usingtype: LOCAL_USING, name: "System.Collections.Generic", }, { usingtype: USING_STATIC, name: "System.Math", }, ], }); }); test("Class resolution", () => { const filepath = join(csharpFilesFolder, "Usage.cs"); const imports = resolver.resolveUsingDirectives(filepath); const user = resolver.findClassInImports(imports, "User", filepath); expect(user).toMatchObject({ name: "User", type: "class", namespace: "MyApp.Models", }); const gordon = resolver.findClassInImports(imports, "Gordon", filepath); expect(gordon).toMatchObject({ name: "Gordon", type: "class", namespace: "HalfNamespace", }); const guy = resolver.findClassInImports(imports, "Guy", filepath); expect(guy).toMatchObject({ name: "User", type: "class", namespace: "MyApp.Models", }); }); test("Current namespace resolution", () => { const filepath = join(csharpFilesFolder, "Models.cs"); const imports = resolver.resolveUsingDirectives(filepath).internal; expect(imports).toMatchObject([ { usingtype: USING_CURRENT, namespace: { name: "Models", exports: expect.any(Array), childrenNamespaces: expect.any(Array), }, }, { usingtype: USING_CURRENT, namespace: { name: "", exports: expect.any(Array), childrenNamespaces: expect.any(Array), }, }, ]); }); test("instanceof functions correctly", () => { const filepath = join(csharpFilesFolder, "Usage.cs"); const directives = resolver.parseUsingDirectives(filepath); expect( directives.filter( (d) => resolver.resolveUsingDirective(d) instanceof ExternalSymbol, ).length, ).toBe(4); expect( directives.filter( (d) => resolver.resolveUsingDirective(d) instanceof InternalSymbol, ).length, ).toBe(2); const programpath = join(csharpFilesFolder, "Program.cs"); const progDirectives = resolver.parseUsingDirectives(programpath); expect( progDirectives.filter( (d) => resolver.resolveUsingDirective(d) instanceof ExternalSymbol, ).length, ).toBe(0); expect( progDirectives.filter( (d) => resolver.resolveUsingDirective(d) instanceof InternalSymbol, ).length, ).toBe(6); }); }); ================================================ FILE: src/languagePlugins/csharp/usingResolver/index.ts ================================================ import Parser from "tree-sitter"; import type { CSharpNamespaceMapper, NamespaceNode, SymbolNode, } from "../namespaceMapper/index.ts"; import type { CSharpProjectMapper, GlobalUsings, } from "../projectMapper/index.ts"; import { csharpParser } from "../../../helpers/treeSitter/parsers.ts"; const namespaceDeclarationQuery = new Parser.Query( csharpParser.getLanguage(), ` (file_scoped_namespace_declaration name : (_) @name) (namespace_declaration name: (_) @name) `, ); // Constants representing different types of 'using' directives in C# export const GLOBAL_USING = "global"; export const LOCAL_USING = "local"; export const USING_STATIC = "static"; export const USING_ALIAS = "alias"; // Not for using directives, but for the namespace which is currently being worked on. export const USING_CURRENT = "current"; /** Type alias for the different 'using' directive types */ export type UsingType = | typeof GLOBAL_USING | typeof LOCAL_USING | typeof USING_STATIC | typeof USING_ALIAS | typeof USING_CURRENT; /** * Interface representing a 'using' directive in the code */ export interface UsingDirective { /** The syntax node corresponding to the 'using' directive */ node: Parser.SyntaxNode; /** The type of 'using' directive */ type: UsingType; /** The filepath it is imported in */ filepath: string; /** The identifier or qualified name being imported */ id: string; /** Optional alias for the imported identifier */ alias?: string; } /** * Interface representing an internal symbol resolved from a 'using' directive */ export class InternalSymbol { /** The type of 'using' directive */ usingtype: UsingType; /** The filepath it is imported in */ filepath: string; /** Optional alias for the symbol */ alias?: string; /** The symbol node if it is a class or type */ symbol?: SymbolNode; /** The namespace node if it is a namespace */ namespace?: NamespaceNode; constructor( usingtype: UsingType, filepath: string, alias?: string, symbol?: SymbolNode, namespace?: NamespaceNode, ) { this.usingtype = usingtype; this.filepath = filepath; this.alias = alias; this.symbol = symbol; this.namespace = namespace; } } /** * Interface representing an external symbol resolved from a 'using' directive */ export class ExternalSymbol { /** The type of 'using' directive */ usingtype: UsingType; /** The filepath it is imported in */ filepath: string; /** Optional alias for the symbol */ alias?: string; /** The name of the external symbol */ name: string; constructor( usingtype: UsingType, filepath: string, alias?: string, name?: string, ) { this.usingtype = usingtype; this.filepath = filepath; this.alias = alias; this.name = name ?? ""; } } /** * Interface representing the resolved imports from a file */ export interface ResolvedImports { /** List of internal symbols */ internal: InternalSymbol[]; /** List of external symbols */ external: ExternalSymbol[]; } /** * Class responsible for resolving 'using' directives in C# files */ export class CSharpUsingResolver { /** Mapper for namespaces and symbols */ private nsMapper: CSharpNamespaceMapper; /** List of parsed 'using' directives */ private usingDirectives: UsingDirective[] = []; private cachedImports: Map = new Map< string, ResolvedImports >(); private cachedDirectives: Map = new Map< string, UsingDirective[] >(); public projectmapper: CSharpProjectMapper; private cachedExternalDeps: Set = new Set(); constructor( nsMapper: CSharpNamespaceMapper, projectmapper: CSharpProjectMapper, ) { this.nsMapper = nsMapper; this.projectmapper = projectmapper; } /** * Parses the file and returns all using directives. * @param filepath - The path to the file to parse. * @returns An array of UsingDirective objects. */ public parseUsingDirectives(filepath: string): UsingDirective[] { if (this.cachedDirectives.has(filepath)) { return this.cachedDirectives.get(filepath) as UsingDirective[]; } const file = this.nsMapper.getFile(filepath); if (!file) { return []; } const usingNodes = file.rootNode.descendantsOfType("using_directive"); this.usingDirectives = usingNodes.map((node) => { const type = this.getUsingType(node); // The imported namespace or class is an identifier or a qualified name. // Only the alias has the field name "name". // The imported namespace isn't named in the tree, so we have to pull // this kind of black magic to find it. const importNode = node.children.find( (child) => (child.type === "identifier" || child.type === "qualified_name") && child !== node.childForFieldName("name"), ); let id = importNode ? importNode.text : ""; // Remove the :: prefix from the id if it exists id = id.includes("::") ? (id.split("::").pop() as string) : id; const aliasNode = node.childForFieldName("name"); const alias = aliasNode ? aliasNode.text : undefined; return { node, type, filepath, id, alias }; }); // Cache the using directives for the file this.cachedDirectives.set(filepath, this.usingDirectives); return this.usingDirectives; } /** * Determines the type of 'using' directive based on its text content. * @param node - The syntax node representing the 'using' directive. * @returns The type of 'using' directive. */ private getUsingType(node: Parser.SyntaxNode): UsingType { // There is probably a cleaner way to do this. if (node.text.includes("using static")) { return USING_STATIC; } if (node.text.includes("=")) { return USING_ALIAS; } if (node.text.includes("global using")) { return GLOBAL_USING; } return LOCAL_USING; } /** * Resolves a single 'using' directive to either an internal or external symbol. * @param directive - The 'using' directive to resolve. * @returns An InternalSymbol or ExternalSymbol object. */ public resolveUsingDirective( directive: UsingDirective, ): InternalSymbol | ExternalSymbol { const { type, filepath, id, alias } = directive; // Check if the using directive is a known external dependency if (this.cachedExternalDeps.has(id)) { return new ExternalSymbol(type, filepath, alias, id); } const symbol = this.nsMapper.findClassInTree(this.nsMapper.nsTree, id); if (symbol) { return new InternalSymbol(type, filepath, alias, symbol); } const namespace = this.nsMapper.findNamespaceInTree( this.nsMapper.nsTree, id, ); if (namespace) { return new InternalSymbol(type, filepath, alias, undefined, namespace); } // If the directive is not found in the namespace tree, treat it as an external symbol // and cache it this.cachedExternalDeps.add(id); return new ExternalSymbol(type, filepath, alias, id); } private getCurrentNamespaces(filepath: string): string[] { const file = this.nsMapper.getFile(filepath); if (!file) { return []; } const currentNamespaces: string[] = []; const namespaces = namespaceDeclarationQuery.captures(file.rootNode); for (const nsName of namespaces) { currentNamespaces.push(nsName.node.text); } return currentNamespaces; } /** * Resolves all 'using' directives in a file and categorizes them into internal and external symbols. * @param filepath - The path to the file to resolve. * @returns A ResolvedImports object containing internal and external symbols. */ public resolveUsingDirectives(filepath: string): ResolvedImports { const globalUsings: GlobalUsings = { internal: [], external: [], directives: [], }; if (this.cachedImports.has(filepath)) { return this.cachedImports.get(filepath) as ResolvedImports; } const internal: InternalSymbol[] = []; const external: ExternalSymbol[] = []; this.parseUsingDirectives(filepath).forEach((directive) => { const resolved = this.resolveUsingDirective(directive); if ("symbol" in resolved || "namespace" in resolved) { internal.push(resolved); if (directive.type === GLOBAL_USING) { globalUsings.internal.push(resolved); globalUsings.directives.push(directive); } } else { external.push(resolved as ExternalSymbol); if (directive.type === GLOBAL_USING) { globalUsings.external.push(resolved as ExternalSymbol); globalUsings.directives.push(directive); } } }); const currentNamespaces = this.getCurrentNamespaces(filepath).concat(""); // Add the current namespaces to the internal symbols for (const ns of currentNamespaces) { const namespace = this.nsMapper.findNamespaceInTree( this.nsMapper.nsTree, ns, ); if (namespace) { internal.push({ usingtype: USING_CURRENT, filepath, namespace, }); } } const resolvedimports = { internal, external }; // Update the global usings for the project const subproject = this.projectmapper.findSubprojectForFile(filepath); if (subproject) { this.projectmapper.updateGlobalUsings(globalUsings, subproject); } this.cachedImports.set(filepath, resolvedimports); return resolvedimports; } /** * Gets the global usings for a file. * @param filepath - The path to the file to analyse. * @returns A ResolvedImports object containing internal and external symbols imported through global using directives. */ public getGlobalUsings(filepath: string): ResolvedImports { return this.projectmapper.getGlobalUsings(filepath); } /** * Finds a class in the resolved imports. * @param imports - The resolved imports to search. * @param className - The name of the class to find. * @returns The SymbolNode of the class if found, otherwise null. */ public findClassInImports( imports: ResolvedImports, className: string, filepath: string, ): SymbolNode | null { // Handle qualified class names with aliases const parts = className.split("."); for (let i = 0; i < parts.length; i++) { const aliasMatch = imports.internal.find( (symbol) => symbol.alias === parts[i], ); if (aliasMatch && aliasMatch.symbol) { parts[i] = aliasMatch.symbol.name; } } const reconstructedClassName = parts.join("."); // Check if the class is directly imported const found = imports.internal.find( (symbol) => "symbol" in symbol && (symbol.symbol?.name === reconstructedClassName || symbol.alias === reconstructedClassName), ); if (found) { return found.symbol ?? null; } // Check if the class is imported through a namespace for (const symbol of imports.internal) { if ("namespace" in symbol && symbol.namespace) { const nsFound = this.nsMapper.findClassInTree( symbol.namespace, reconstructedClassName, ); if (nsFound) { return nsFound; } } } // Also check in global usings for (const symbol of this.getGlobalUsings(filepath).internal) { if ("namespace" in symbol && symbol.namespace) { const nsFound = this.nsMapper.findClassInTree( symbol.namespace, reconstructedClassName, ); if (nsFound) { return nsFound; } } } return null; } } ================================================ FILE: src/languagePlugins/java/dependencyFormatting/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { getJavaFilesContentMap } from "../testFiles/index.ts"; import { JavaDependencyFormatter } from "./index.ts"; import { PEBBLE, STEAK, WORMKILLER } from "../testFiles/constants.ts"; describe("Java dependency formatter", () => { const files = getJavaFilesContentMap(); const formatter = new JavaDependencyFormatter(files); test("formats Wormkiller.java", () => { const killer = formatter.formatFile(WORMKILLER); expect(killer).toBeDefined(); expect(killer.lineCount >= 11).toBe(true); expect(killer.characterCount > 250).toBe(true); expect(killer.dependencies[STEAK]).toBeDefined(); expect(killer.dependencies[PEBBLE]).toBeDefined(); expect(killer.dependencies[STEAK].symbols["Steak"]).toBeDefined(); expect(killer.dependencies[PEBBLE].symbols["Pebble"]).toBeDefined(); expect(killer.filePath).toBe(WORMKILLER); expect(killer.id).toBe(WORMKILLER); expect(killer.symbols["Wormkiller"]).toBeDefined(); expect(killer.symbols["Wormkiller"].dependencies[STEAK]).toBeDefined(); expect(killer.symbols["Wormkiller"].dependencies[PEBBLE]).toBeDefined(); expect(killer.symbols["Wormkiller"].type).toBe("class"); }); }); ================================================ FILE: src/languagePlugins/java/dependencyFormatting/index.ts ================================================ import type Parser from "tree-sitter"; import { javaParser } from "../../../helpers/treeSitter/parsers.ts"; import { JavaImportResolver } from "../importResolver/index.ts"; import { JavaInvocationResolver } from "../invocationResolver/index.ts"; import { JavaPackageMapper } from "../packageMapper/index.ts"; import type { JavaDependency, JavaDepFile, JavaDepSymbol } from "./types.ts"; import type { Invocations } from "../invocationResolver/types.ts"; import type { ExportedSymbol } from "../packageResolver/types.ts"; /** * Class responsible for formatting Java dependencies by analyzing parsed files, * resolving imports, invocations, and symbols. */ export class JavaDependencyFormatter { mapper: JavaPackageMapper; importResolver: JavaImportResolver; invocationResolver: JavaInvocationResolver; /** * Constructs a JavaDependencyFormatter instance. * * @param files - A map of file identifiers to their path and content. */ constructor(files: Map) { const parsedFiles: Map< string, { path: string; rootNode: Parser.SyntaxNode } > = new Map(); for (const [k, f] of files) { try { const rootNode = javaParser.parse(f.content, undefined, { bufferSize: f.content.length + 10, }).rootNode; parsedFiles.set(k, { path: f.path, rootNode: rootNode, }); } catch (e) { console.error(`Failed to parse ${f.path}, skipping`); console.error(e); } } this.mapper = new JavaPackageMapper(parsedFiles); this.importResolver = new JavaImportResolver(this.mapper); this.invocationResolver = new JavaInvocationResolver(this.importResolver); } /** * Formats the dependencies of a file based on its invocations. * * @param fileDependencies - The invocations of the file to process. * @returns A record of Java dependencies. */ #formatDependencies( fileDependencies: Invocations, ): Record { const dependencies: Record = {}; const resolved = fileDependencies.resolved; for (const [, node] of resolved) { const filepath = node.file.path; // Getting the file's symbol to get the oldest ancestor in case of nested symbol const id = node.file.symbol.name; if (!dependencies[filepath]) { dependencies[filepath] = { id: filepath, isExternal: false, symbols: {}, }; } dependencies[filepath].symbols[id] = id; } return dependencies; } /** * Formats standard library imports into dependency records. * * @param stdimports - An array of unresolved standard imports. * @returns A record of Java dependencies for standard imports. */ #formatStandardImports(stdimports: string[]): Record { const dependencies: Record = {}; for (const id of stdimports) { if (!dependencies[id]) { dependencies[id] = { id: id, isExternal: true, symbols: {}, }; } } return dependencies; } /** * Formats a symbol exported by a file into a dependency symbol record. * * @param fileSymbol - The exported symbol to format. * @returns A record containing the formatted dependency symbol. */ #formatSymbol(fileSymbol: ExportedSymbol): Record { // Record despite files only having 1 symbol since it's needed for manifest const symbol: Record = {}; const id = fileSymbol.name; const dependencies = this.invocationResolver.getInvocations( this.mapper.files.get(fileSymbol.filepath)!, ); symbol[id] = { id: id, type: fileSymbol.type, lineCount: fileSymbol.node.endPosition.row - fileSymbol.node.startPosition.row, characterCount: fileSymbol.node.endIndex - fileSymbol.node.startIndex, node: fileSymbol.node, dependents: {}, dependencies: this.#formatDependencies(dependencies), }; return symbol; } /** * Formats a file into a JavaDepFile object, including its dependencies and symbols. * * @param filepath - The path of the file to format. * @returns A formatted JavaDepFile object. */ formatFile(filepath: string): JavaDepFile { const file = this.mapper.files.get(filepath)!; const imports = this.importResolver.imports.get(filepath)!; const stdDependencies = this.#formatStandardImports(imports.unresolved); const invocations = this.invocationResolver.invocations.get(filepath)!; const invokedDependencies = this.#formatDependencies(invocations); const allDependencies = { ...invokedDependencies, ...stdDependencies, }; const formattedFile: JavaDepFile = { id: filepath, filePath: file.path, rootNode: file.rootNode, lineCount: file.rootNode.endPosition.row, characterCount: file.rootNode.endIndex, dependencies: allDependencies, symbols: this.#formatSymbol(file.symbol), }; return formattedFile; } } ================================================ FILE: src/languagePlugins/java/dependencyFormatting/types.ts ================================================ import type { SymbolType } from "../packageResolver/types.ts"; import type Parser from "tree-sitter"; /** * Represents a dependency in a Java file */ export interface JavaDependency { id: string; isExternal: boolean; symbols: Record; } /** * Represents a dependent in a Java file */ export interface JavaDependent { id: string; symbols: Record; } /** * Represents a symbol in a Java file */ export interface JavaDepSymbol { id: string; type: SymbolType; lineCount: number; characterCount: number; node: Parser.SyntaxNode; dependents: Record; dependencies: Record; } /** * Represents a Java file with its dependencies and symbols. */ export interface JavaDepFile { id: string; filePath: string; rootNode: Parser.SyntaxNode; lineCount: number; characterCount: number; dependencies: Record; symbols: Record; } ================================================ FILE: src/languagePlugins/java/extractor/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { getJavaFilesContentMap } from "../testFiles/index.ts"; import { generateJavaDependencyManifest } from "../../../manifest/dependencyManifest/java/index.ts"; import { JavaExtractor } from "./index.ts"; import { WORMKILLER } from "../testFiles/constants.ts"; describe("Java extractor", () => { const files = getJavaFilesContentMap(); const manifest = generateJavaDependencyManifest(files); const extractor = new JavaExtractor(files, manifest); test("extracts wormkiller", () => { const symbolsMap: Map }> = new Map(); symbolsMap.set(WORMKILLER, { filePath: WORMKILLER, symbols: new Set("Wormkiller"), }); const extracted = extractor.extractSymbols(symbolsMap); expect(extracted.size).toBe(4); }); }); ================================================ FILE: src/languagePlugins/java/extractor/index.ts ================================================ import type Parser from "tree-sitter"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; import { JavaInvocationResolver } from "../invocationResolver/index.ts"; import { javaParser } from "../../../helpers/treeSitter/parsers.ts"; import { JavaPackageMapper } from "../packageMapper/index.ts"; import { JavaImportResolver } from "../importResolver/index.ts"; import { ExportedFile } from "./types.ts"; /** * Responsible for extracting and managing Java symbols * from a set of files, using dependency resolution and package mapping. */ export class JavaExtractor { manifest: DependencyManifest; resolver: JavaInvocationResolver; /** * Constructs a new instance of `JavaExtractor`. * * @param files - A map of file paths to their respective content and metadata. * @param manifest - The dependency manifest containing information about project dependencies. */ constructor( files: Map, manifest: DependencyManifest, ) { this.manifest = manifest; const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const [filepath, file] of files) { parsedFiles.set(filepath, { path: file.path, rootNode: javaParser.parse(file.content).rootNode, }); } const mapper = new JavaPackageMapper(parsedFiles); const importresolver = new JavaImportResolver(mapper); this.resolver = new JavaInvocationResolver(importresolver); } /** * Extracts symbols from the provided symbol map and returns a map of files * that should be kept, including their paths and content. * * @param symbolsMap - A map where keys are symbol names and values contain file paths * and sets of symbols associated with those files. * @returns A map of file paths to their respective content and metadata for files to keep. */ extractSymbols( symbolsMap: Map< string, { filePath: string; symbols: Set; } >, ): Map { const exportedfiles = Array.from( symbolsMap.keys().map((k) => new ExportedFile(k, this.resolver)), ); const filesToKeep: Map = new Map(); for (const f of exportedfiles) { if (!filesToKeep.has(f.file.path)) { filesToKeep.set(f.file.path, { path: f.file.path, content: f.file.rootNode.text, }); } for (const [k, v] of f.dependencies) { if (!filesToKeep.has(k)) { filesToKeep.set(k, { path: v.path, content: v.rootNode.text, }); } } } return filesToKeep; } } ================================================ FILE: src/languagePlugins/java/extractor/types.ts ================================================ import type { JavaInvocationResolver } from "../invocationResolver/index.ts"; import type { JavaFile } from "../packageResolver/types.ts"; /** * Represents a file that has been exported along with its dependencies. */ export class ExportedFile { /** * A map of dependencies where the key is the file path and the value is the corresponding JavaFile. */ dependencies: Map; /** * The main JavaFile instance associated with this exported file. */ file: JavaFile; /** * Constructs an ExportedFile instance and resolves its dependencies. * * @param filepath - The file path of the Java file to be exported. * @param resolver - An instance of JavaInvocationResolver used to resolve file dependencies. */ constructor(filepath: string, resolver: JavaInvocationResolver) { this.file = resolver.mapper.files.get(filepath)!; this.dependencies = new Map(); const stack = [this.file]; while (stack.length > 0) { const current = stack.pop()!; if (this.dependencies.has(current.path)) { continue; } this.dependencies.set(current.path, current); const currentDep = resolver.getInvocations(current).resolved; stack.push(...currentDep.values().map((v) => v.file)); } } } ================================================ FILE: src/languagePlugins/java/importResolver/index.test.ts ================================================ import { getJavaFilesMap } from "../testFiles/index.ts"; import { BURGER, PEBBLE, WORMKILLER } from "../testFiles/constants.ts"; import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { JavaImportResolver } from "./index.ts"; import { JavaPackageMapper } from "../packageMapper/index.ts"; describe("Java Import Resolver", () => { const files = getJavaFilesMap(); const mapper = new JavaPackageMapper(files); const resolver = new JavaImportResolver(mapper); test("unresolved external dependencies", () => { const burgerimports = resolver.imports.get(BURGER)!; expect(burgerimports.unresolved.length).toBe(2); expect(burgerimports.unresolved).toContain("java.io.File"); expect(burgerimports.unresolved).toContain("java.lang.System"); }); test("resolved internal dependencies", () => { const killerimports = resolver.imports.get(WORMKILLER)!; expect(killerimports.resolved.size).toBe(3); expect(killerimports.unresolved.length).toBe(0); expect(killerimports.resolved.get("Steak")).toBeDefined(); expect(killerimports.resolved.get("Pebble")).toBeDefined(); expect(killerimports.resolved.get("Wormkiller")).toBeDefined(); expect(killerimports.resolved.get("Pebble.Sandworm")).toBeUndefined(); expect(killerimports.resolved.get("Sandworm")).toBeUndefined(); const pebbleimports = resolver.imports.get(PEBBLE)!; expect(pebbleimports.resolved.size).toBe(2); expect(pebbleimports.unresolved.length).toBe(0); expect(pebbleimports.resolved.get("Food")).toBeDefined(); expect(pebbleimports.resolved.get("Pebble")).toBeDefined(); const burgerimports = resolver.imports.get(BURGER)!; expect(burgerimports.resolved.get("Food")).toBeDefined(); expect(burgerimports.resolved.get("Condiment")).toBeDefined(); expect(burgerimports.resolved.get("DoubleBurger")).toBeDefined(); expect(burgerimports.resolved.get("Steak")).toBeDefined(); expect(burgerimports.resolved.get("Pebble")).toBeUndefined(); }); }); ================================================ FILE: src/languagePlugins/java/importResolver/index.ts ================================================ import type { JavaPackageMapper } from "../packageMapper/index.ts"; import type { JavaFile } from "../packageResolver/types.ts"; import type { JavaImports } from "./types.ts"; /** * Resolves and organizes Java imports for a given set of files. */ export class JavaImportResolver { mapper: JavaPackageMapper; imports: Map; /** * Creates an instance of JavaImportResolver. * @param {JavaPackageMapper} mapper - The package mapper containing file and tree information. */ constructor(mapper: JavaPackageMapper) { this.mapper = mapper; this.imports = new Map(); for (const [key, f] of this.mapper.files) { this.imports.set(key, this.getImports(f)); } } /** * Retrieves and categorizes imports for a given Java file. * @param {JavaFile} file - The Java file for which imports need to be resolved. * @returns {JavaImports} An object containing resolved and unresolved imports. */ getImports(file: JavaFile): JavaImports { const imports: JavaImports = { resolved: new Map(), unresolved: [], }; const importstrings = file.imports; for (const impstr of importstrings) { const foundimport = this.mapper.tree.getImport(impstr); if (!foundimport) { imports.unresolved.push(impstr); } else { for (const [key, dep] of foundimport) { imports.resolved.set(key, dep); } } } const currentPackageImports = this.mapper.tree.getImport( file.package + ".*", ); if (currentPackageImports) { for (const [key, dep] of currentPackageImports) { imports.resolved.set(key, dep); } } return imports; } } ================================================ FILE: src/languagePlugins/java/importResolver/types.ts ================================================ import type { ConcreteNode } from "../packageMapper/types.ts"; /** * Represents a collection of Java imports, categorized into resolved and unresolved imports. */ export interface JavaImports { /** * A map of resolved imports where the key is the import name and the value is the corresponding ConcreteNode. */ resolved: Map; /** * An array of unresolved import names. */ unresolved: string[]; } ================================================ FILE: src/languagePlugins/java/invocationResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { getJavaFilesMap } from "../testFiles/index.ts"; import { JavaInvocationResolver } from "./index.ts"; import { JavaPackageMapper } from "../packageMapper/index.ts"; import { JavaImportResolver } from "../importResolver/index.ts"; import { APP, WORMKILLER } from "../testFiles/constants.ts"; describe("Java Invocation Resolver", () => { const files = getJavaFilesMap(); const mapper = new JavaPackageMapper(files); const impResolver = new JavaImportResolver(mapper); const resolver = new JavaInvocationResolver(impResolver); const invocations = resolver.invocations; test("resolves Wormkiller.java", () => { const killerinv = invocations.get(WORMKILLER)!; expect(killerinv.unresolved.has("Sandworm")).toBe(true); expect(killerinv.unresolved.has("Pebble.Sandworm")).toBe(true); const resolved = Array.from(killerinv.resolved.keys()); expect(resolved).toContain("Steak"); expect(resolved).toContain("Pebble"); }); test("resolves App.java", () => { const appinv = invocations.get(APP)!; console.log(impResolver.imports.get(APP)!); expect(appinv.unresolved.has("System.out")).toBe(false); expect(appinv.unresolved.has("System")).toBe(true); const resolved = Array.from(appinv.resolved.keys()); expect(resolved).toContain("restaurantCount"); }); }); ================================================ FILE: src/languagePlugins/java/invocationResolver/index.ts ================================================ import type { JavaImportResolver } from "../importResolver/index.ts"; import type { JavaImports } from "../importResolver/types.ts"; import type { JavaPackageMapper } from "../packageMapper/index.ts"; import { ConcreteNode } from "../packageMapper/types.ts"; import type { JavaFile } from "../packageResolver/types.ts"; import { JAVA_INVOCATION_QUERY, JAVA_VARIABLES_QUERY } from "./queries.ts"; import type { Invocations } from "./types.ts"; /** * Resolves Java method invocations and maps them to their corresponding imports or project nodes. */ export class JavaInvocationResolver { mapper: JavaPackageMapper; imports: Map; invocations: Map; /** * Constructs a new JavaInvocationResolver. * * @param resolver - An instance of JavaImportResolver containing the package mapper and imports. */ constructor(resolver: JavaImportResolver) { this.mapper = resolver.mapper; this.imports = resolver.imports; this.invocations = new Map(); for (const [key, f] of this.mapper.files) { this.invocations.set(key, this.getInvocations(f)); } } /** * Extracts and resolves method invocations from a given Java file. * * @param file - The Java file to analyze for method invocations. * @returns An object containing resolved and unresolved method invocations. */ getInvocations(file: JavaFile): Invocations { const variables = new Set( JAVA_VARIABLES_QUERY.captures(file.symbol.node).map((c) => c.node.text), ); const captures = JAVA_INVOCATION_QUERY.captures(file.symbol.node).map((c) => c.node.text ); const resolvedImports = this.imports.get(file.path)!.resolved; const resolved = new Map(); const unresolved = new Set(); for (const c of captures) { // Check variables if (variables.has(c)) { continue; } // Check imports if (resolvedImports.has(c)) { resolved.set(c, resolvedImports.get(c)!); } else { // Check project const cnode = this.mapper.tree.getNode(c); if (cnode && cnode instanceof ConcreteNode) { resolved.set(c, cnode); } else { unresolved.add(c); } } } return { resolved, unresolved }; } } ================================================ FILE: src/languagePlugins/java/invocationResolver/queries.ts ================================================ import Parser from "tree-sitter"; import { javaParser } from "../../../helpers/treeSitter/parsers.ts"; /** * A Tree-sitter query to match Java method or type invocations. * This query captures scoped type identifiers, type identifiers, and identifiers. */ export const JAVA_INVOCATION_QUERY = new Parser.Query( javaParser.getLanguage(), ` [ (scoped_type_identifier) (type_identifier) (identifier) ] @any `, ); /** * A Tree-sitter query to match Java variable declarations and formal parameters. * This query captures variable declarators and formal parameter names. */ export const JAVA_VARIABLES_QUERY = new Parser.Query( javaParser.getLanguage(), ` (variable_declarator name: (_) @var) (formal_parameter name: (_) @var) `, ); ================================================ FILE: src/languagePlugins/java/invocationResolver/types.ts ================================================ import type { ConcreteNode } from "../packageMapper/types.ts"; /** * Represents a collection of invocations. */ export interface Invocations { /** * A map of resolved invocations, where the key is a string identifier * and the value is a `ConcreteNode` object. */ resolved: Map; /** * A set of unresolved invocation identifiers. */ unresolved: Set; } ================================================ FILE: src/languagePlugins/java/metrics/index.ts ================================================ import { JAVA_COMMENT_QUERY, JAVA_COMPLEXITY_QUERY } from "./queries.ts"; import type { CodeCounts, CommentSpan, JavaComplexityMetrics, } from "./types.ts"; import type Parser from "tree-sitter"; // This class has no tests because it is copy-pasted from C. // If the C metrics work, then the Java ones do. export class JavaMetricsAnalyzer { /** * Calculates metrics for a Java symbol. * @param node - The syntax node to analyze. * @returns An object containing the complexity metrics. */ public analyzeNode(node: Parser.SyntaxNode): JavaComplexityMetrics { if (node.type === "preproc_function_def") { const value = node.childForFieldName("value"); if (value) { node = value; } } const complexityCount = this.getComplexityCount(node); const linesCount = node.endPosition.row - node.startPosition.row + 1; const codeCounts = this.getCodeCounts(node); const codeLinesCount = codeCounts.lines; const characterCount = node.endIndex - node.startIndex; const codeCharacterCount = codeCounts.characters; return { cyclomaticComplexity: complexityCount, linesCount, codeLinesCount, characterCount, codeCharacterCount, }; } private getComplexityCount(node: Parser.SyntaxNode): number { const complexityMatches = JAVA_COMPLEXITY_QUERY.captures(node); return complexityMatches.length; } /** * Finds comments in the given node and returns their spans. * @param node - The AST node to analyze * @returns An object containing pure comment lines and comment spans */ private findComments( node: Parser.SyntaxNode, lines: string[], ): { pureCommentLines: Set; commentSpans: CommentSpan[]; } { const pureCommentLines = new Set(); const commentSpans: CommentSpan[] = []; const commentCaptures = JAVA_COMMENT_QUERY.captures(node); for (const capture of commentCaptures) { const commentNode = capture.node; // Record the comment span for character counting commentSpans.push({ start: { row: commentNode.startPosition.row, column: commentNode.startPosition.column, }, end: { row: commentNode.endPosition.row, column: commentNode.endPosition.column, }, }); // Check if the comment starts at the beginning of the line (ignoring whitespace) const lineIdx = commentNode.startPosition.row - node.startPosition.row; if (lineIdx >= 0 && lineIdx < lines.length) { const lineText = lines[lineIdx]; const textBeforeComment = lineText.substring( 0, commentNode.startPosition.column, ); // If there's only whitespace before the comment, it's a pure comment line if (textBeforeComment.trim().length === 0) { for ( let line = commentNode.startPosition.row; line <= commentNode.endPosition.row; line++ ) { pureCommentLines.add(line); } } } } return { pureCommentLines, commentSpans }; } /** * Finds all empty lines in a node * * @param node The syntax node to analyze * @param lines The lines of text in the node * @returns Set of line numbers that are empty */ private findEmptyLines( node: Parser.SyntaxNode, lines: string[], ): Set { const emptyLines = new Set(); for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; if (lines[i].trim().length === 0) { emptyLines.add(lineIndex); } } return emptyLines; } private getCodeCounts(node: Parser.SyntaxNode): CodeCounts { const lines = node.text.split(/\r?\n/); const linesCount = lines.length; // Find comments and their spans const { pureCommentLines, commentSpans } = this.findComments(node, lines); // Find empty lines const emptyLines = this.findEmptyLines(node, lines); // Calculate code lines const nonCodeLines = new Set([...pureCommentLines, ...emptyLines]); const codeLinesCount = linesCount - nonCodeLines.size; let codeCharCount = 0; // Process each line individually for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; const line = lines[i]; // Skip empty lines and pure comment lines if (emptyLines.has(lineIndex) || pureCommentLines.has(lineIndex)) { continue; } // Process line for code characters let lineText = line; // Remove comment content from the line if present for (const span of commentSpans) { if (span.start.row === lineIndex) { // Comment starts on this line lineText = lineText.substring(0, span.start.column); } } // Count normalized code characters (trim excessive whitespace) const normalizedText = lineText.trim().replace(/\s+/g, " "); codeCharCount += normalizedText.length; } return { lines: codeLinesCount, characters: codeCharCount, }; } } ================================================ FILE: src/languagePlugins/java/metrics/queries.ts ================================================ import Parser from "tree-sitter"; import { javaParser } from "../../../helpers/treeSitter/parsers.ts"; // Tree-sitter query to find complexity-related nodes export const JAVA_COMPLEXITY_QUERY = new Parser.Query( javaParser.getLanguage(), ` (if_statement) @complexity (while_statement) @complexity (for_statement) @complexity (do_statement) @complexity (switch_block_statement_group) @complexity (ternary_expression) @complexity `, ); export const JAVA_COMMENT_QUERY = new Parser.Query( javaParser.getLanguage(), ` (block_comment) @comment (line_comment) @comment `, ); ================================================ FILE: src/languagePlugins/java/metrics/types.ts ================================================ /** * Interface for code volumes */ export interface CodeCounts { /** Number of lines of code */ lines: number; /** Number of characters of code */ characters: number; } export interface CommentSpan { start: { row: number; column: number }; end: { row: number; column: number }; } /** * Represents complexity metrics for a C symbol */ export interface JavaComplexityMetrics { /** Cyclomatic complexity (McCabe complexity) */ cyclomaticComplexity: number; /** Code lines (not including whitespace or comments) */ codeLinesCount: number; /** Total lines (including whitespace and comments) */ linesCount: number; /** Characters of actual code (excluding comments and excessive whitespace) */ codeCharacterCount: number; /** Total characters in the entire symbol */ characterCount: number; } ================================================ FILE: src/languagePlugins/java/packageMapper/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { JavaPackageMapper } from "./index.ts"; import { getJavaFilesMap } from "../testFiles/index.ts"; describe("Java Package Mapper", () => { const files = getJavaFilesMap(); const mapper = new JavaPackageMapper(files); const tree = mapper.tree; test("maps the project correctly", () => { expect(Array.from(tree.children.keys())).toContain("io"); expect(Array.from(tree.children.get("io")!.children.keys())).toContain( "nanoapi", ); expect( Array.from( tree.children.get("io")!.children.get("nanoapi")!.children.keys(), ), ) .toContain("testfiles"); expect( Array.from(tree.children.get("io")!.children.get("nanoapi")!.children.get( "testfiles", )!.children.keys()), ).toContain("food"); const food = tree.children.get("io")!.children.get("nanoapi")!.children.get( "testfiles", )!.children.get("food")!; const classes = Array.from(food.children.keys()); expect(classes).toContain("Burger"); expect(classes).toContain("Condiment"); expect(classes).toContain("DoubleBurger"); expect(classes).toContain("Food"); expect(classes).toContain("Steak"); expect(classes).toContain("goron"); const goron = food.children.get("goron")!; expect(goron.children.get("Pebble")).toBeDefined(); expect(goron.children.get("Pebble")!.children.get("Sandworm")) .toBeDefined(); }); test("finds nodes", () => { expect(tree.getNode("")).toBeDefined(); expect(tree.getNode("io")).toBeDefined(); expect(tree.getNode("io.nanoapi")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.App")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Burger")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Burger.advertisement")) .toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Burger.restaurantCount")) .toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Condiment")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.DoubleBurger")) .toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Food")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Steak")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.Steak.Tapeworm")) .toBeUndefined(); // Private nested classes aren't registered expect(tree.getNode("io.nanoapi.testfiles.food.Screws")).toBeUndefined(); expect(tree.getNode("io.nanoapi.testfiles.food.goron")).toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.goron.Pebble")) .toBeDefined(); expect(tree.getNode("io.nanoapi.testfiles.food.goron.Pebble.Sandworm")) .toBeDefined(); }); test("resolves imports", () => { const ioimport = tree.getImport("io.*"); expect(ioimport).toBeDefined(); if (!ioimport) { throw Error("dummy error, not supposed to happen"); } expect(ioimport.size).toBe(0); const foodimport = tree.getImport("io.nanoapi.testfiles.food.*")!; expect(foodimport.size >= 5).toBe(true); expect(foodimport.get("goron")).toBeUndefined(); expect(foodimport.get("Pebble")).toBeUndefined(); const burgerimport = tree.getImport("io.nanoapi.testfiles.food.Burger")!; expect(burgerimport.size).toBe(1); const burgerstarimport = tree.getImport( "io.nanoapi.testfiles.food.Burger.*", ); expect(burgerstarimport).toBeDefined(); expect(burgerstarimport!.size).toBe(2); const advertisementimport = tree.getImport( "io.nanoapi.testfiles.food.Burger.advertisement", )!; expect(advertisementimport).toBeDefined(); expect(advertisementimport.size).toBe(1); expect(tree.getImport("io.nanoapi.testfiles")).toBeUndefined(); }); }); ================================================ FILE: src/languagePlugins/java/packageMapper/index.ts ================================================ import { JavaTree } from "./types.ts"; import type Parser from "tree-sitter"; import { JavaPackageResolver } from "../packageResolver/index.ts"; import type { JavaFile } from "../packageResolver/types.ts"; /** * Maps Java files to their respective packages and builds a tree representation. */ export class JavaPackageMapper { /** A tree structure representing the Java package hierarchy. */ tree: JavaTree; /** A map of file paths to their resolved JavaFile representations. */ files: Map; /** * Constructs a new JavaPackageMapper instance. * * @param files - A map where each key is a file path and each value is an object containing * the file path and its root syntax node. */ constructor( files: Map, ) { this.files = new Map(); this.tree = new JavaTree(); const resolver = new JavaPackageResolver(); for (const f of files.values()) { const resolvedFile = resolver.resolveFile(f); if (!resolvedFile) { continue; } this.tree.addFile(resolvedFile); this.files.set(f.path, resolvedFile); } } } ================================================ FILE: src/languagePlugins/java/packageMapper/types.ts ================================================ import type { ExportedSymbol, JavaFile } from "../packageResolver/types.ts"; /** * Represents a node in a Java tree structure. */ export interface JavaNode { /** The name of the node. */ name: string; /** The children of the node, stored as a map. */ children: Map; } /** * A basic implementation of the JavaNode interface. */ export class AbstractNode implements JavaNode { /** The name of the node. */ name: string; /** The children of the node, stored as a map. */ children: Map; /** * Creates an instance of AbstractNode. * @param name - The name of the node. */ constructor(name: string) { this.name = name; this.children = new Map(); } } /** * An abstract class representing a concrete node in the Java tree structure. */ export abstract class ConcreteNode implements JavaNode { /** The name of the node. */ name: string; /** The children of the node, stored as a map of NestedSymbol. */ children: Map; /** The declaration associated with this node. */ declaration: ExportedSymbol; /** The file associated with this node. */ file: JavaFile; /** * Creates an instance of ConcreteNode. * @param declaration - The exported symbol declaration for this node. * @param file - The Java file associated with this node. */ constructor(declaration: ExportedSymbol, file: JavaFile) { this.declaration = declaration; this.file = file; this.name = declaration.name; this.children = new Map(); } } /** * Represents the root of a Java tree structure. */ export class JavaTree extends AbstractNode { /** * Creates an instance of JavaTree. */ constructor() { super(""); } /** * Adds a Java file to the tree structure. * @param file - The Java file to add. */ addFile(file: JavaFile) { const packageparts = file.package.split("."); let current = this.children; for (const p of packageparts) { if (!current.has(p)) { current.set(p, new AbstractNode(p)); } current = current.get(p)!.children; } current.set(file.symbol.name, new FileNode(file)); } /** * Retrieves a node from the tree by its name. * @param name - The fully qualified name of the node. * @returns The JavaNode if found, otherwise undefined. */ getNode(name: string): JavaNode | undefined { if (name === "") { return this; } else { const packageparts = name.split(".").reverse(); let current = this.children.get(packageparts.pop()!); if (!current) { return undefined; } while (packageparts.length > 0) { if (!current) { return undefined; } current = current.children.get(packageparts.pop()!); } return current; } } /** * Retrieves a map of importable nodes by their name. * @param name - The name of the import, optionally ending with `*` for wildcard imports. * @returns A map of ConcreteNode objects if found, otherwise undefined. */ getImport(name: string): Map | undefined { if (name.endsWith("*")) { const node = this.getNode(name.substring(0, name.length - 2)); if (node) { const map = new Map(); for (const v of node.children.values()) { if (v instanceof ConcreteNode) { map.set(v.name, v); } } return map; } } else { const node = this.getNode(name); if (node && node instanceof ConcreteNode) { const map = new Map(); map.set(node.name, node); return map; } } return undefined; } } /** * Represents a file node in the Java tree structure. */ export class FileNode extends ConcreteNode { /** * Creates an instance of FileNode. * @param file - The Java file associated with this node. */ constructor(file: JavaFile) { super(file.symbol, file); for (const c of this.declaration.children) { if (!c.modifiers.includes("private")) { this.children.set(c.name, new NestedSymbol(c, file)); } } } } /** * Represents a nested symbol within a Java file. */ export class NestedSymbol extends ConcreteNode { /** * Creates an instance of NestedSymbol. * @param symbol - The exported symbol associated with this node. * @param file - The Java file associated with this node. */ constructor(symbol: ExportedSymbol, file: JavaFile) { super(symbol, file); for (const c of this.declaration.children) { if (!c.modifiers.includes("private")) { this.children.set(c.name, new NestedSymbol(c, file)); } } } } ================================================ FILE: src/languagePlugins/java/packageResolver/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { getJavaFilesMap } from "../testFiles/index.ts"; import { BURGER, CONDIMENT, DOUBLEBURGER, FOOD, STEAK, } from "../testFiles/constants.ts"; import { JavaPackageResolver } from "./index.ts"; import type { JavaClass } from "./types.ts"; describe("Java Package Resolver", () => { const files = getJavaFilesMap(); const condiment = files.get(CONDIMENT)!; const food = files.get(FOOD)!; const steak = files.get(STEAK)!; const burger = files.get(BURGER)!; const doubleburger = files.get(DOUBLEBURGER)!; const packageResolver = new JavaPackageResolver(); test("parses Food.java", () => { const result = packageResolver.resolveFile(food)!; expect(result).toBeDefined(); expect(result.path).toStrictEqual(FOOD); expect(result.package).toBe("io.nanoapi.testfiles.food"); expect(result.imports.length).toBe(0); const symbol = result.symbol as JavaClass; expect(symbol).toBeDefined(); expect(symbol.name).toBe("Food"); expect(symbol.type).toBe("interface"); expect(symbol.modifiers).toContain("public"); expect(symbol.modifiers.length).toBe(1); expect(symbol.typeParamCount).toBe(0); expect(symbol.superclass).toBeUndefined(); expect(symbol.interfaces.length).toBe(0); expect(symbol.children.length).toBe(0); }); test("parses Condiment.java", () => { const result = packageResolver.resolveFile(condiment)!; expect(result).toBeDefined(); expect(result.path).toStrictEqual(CONDIMENT); expect(result.package).toBe("io.nanoapi.testfiles.food"); expect(result.imports.length).toBe(0); const symbol = result.symbol as JavaClass; expect(symbol).toBeDefined(); expect(symbol.name).toBe("Condiment"); expect(symbol.type).toBe("enum"); expect(symbol.modifiers).toContain("public"); expect(symbol.modifiers.length).toBe(1); expect(symbol.typeParamCount).toBe(0); expect(symbol.superclass).toBeUndefined(); expect(symbol.interfaces.length).toBe(0); expect(symbol.children.length).toBe(0); }); test("parses Steak.java", () => { const result = packageResolver.resolveFile(steak)!; expect(result).toBeDefined(); expect(result.path).toStrictEqual(STEAK); expect(result.package).toBe("io.nanoapi.testfiles.food"); expect(result.imports.length).toBe(0); const symbol = result.symbol as JavaClass; expect(symbol).toBeDefined(); expect(symbol.name).toBe("Steak"); expect(symbol.type).toBe("class"); expect(symbol.modifiers).toContain("public"); expect(symbol.modifiers.length).toBe(1); expect(symbol.typeParamCount).toBe(0); expect(symbol.superclass).toBeUndefined(); expect(symbol.interfaces.length).toBe(1); expect(symbol.interfaces[0]).toBe("Food"); expect(symbol.children.length).toBe(1); const child = symbol.children[0] as JavaClass; expect(child).toBeDefined(); expect(child.name).toBe("Tapeworm"); expect(child.type).toBe("class"); expect(child.modifiers).toContain("private"); expect(child.modifiers).toContain("static"); expect(child.modifiers.length).toBe(2); expect(child.typeParamCount).toBe(0); expect(child.superclass).toBeUndefined(); expect(child.interfaces.length).toBe(0); expect(child.children.length).toBe(0); }); test("parses Burger.java", () => { const result = packageResolver.resolveFile(burger)!; expect(result).toBeDefined(); expect(result.path).toStrictEqual(BURGER); expect(result.package).toBe("io.nanoapi.testfiles.food"); expect(result.imports.length).toBe(2); const symbol = result.symbol as JavaClass; expect(symbol).toBeDefined(); expect(symbol.name).toBe("Burger"); expect(symbol.type).toBe("class"); expect(symbol.typeParamCount).toBe(1); expect(symbol.superclass).toBeUndefined(); expect(symbol.interfaces.length).toBe(1); expect(symbol.children.length).toBe(2); expect(symbol.children.filter((c) => c.name === "restaurantCount")) .toHaveLength(1); expect(symbol.children.filter((c) => c.type === "field")).toHaveLength(1); expect(symbol.children.filter((c) => c.name === "advertisement")) .toHaveLength(1); expect(symbol.children.filter((c) => c.type === "method")).toHaveLength(1); }); test("parses DoubleBurger.java", () => { const result = packageResolver.resolveFile(doubleburger)!; expect(result).toBeDefined(); expect(result.path).toStrictEqual(DOUBLEBURGER); expect(result.package).toBe("io.nanoapi.testfiles.food"); const symbol = result.symbol as JavaClass; expect(symbol).toBeDefined(); expect(symbol.name).toBe("DoubleBurger"); expect(symbol.type).toBe("class"); expect(symbol.typeParamCount).toBe(2); expect(symbol.superclass).toBeDefined(); expect(symbol.superclass).toBe("Burger"); expect(symbol.interfaces.length).toBe(0); }); }); ================================================ FILE: src/languagePlugins/java/packageResolver/index.ts ================================================ import type Parser from "tree-sitter"; import { JavaClass, type JavaFile, type SymbolType } from "./types.ts"; import { JAVA_PROGRAM_QUERY } from "./queries.ts"; import { javaParser } from "../../../helpers/treeSitter/parsers.ts"; /** * Resolves Java package and import information from a parsed Java file. */ export class JavaPackageResolver { /** * The Tree-sitter parser instance for Java. */ parser: Parser = javaParser; /** * Resolves the package, imports, and main declaration of a Java file. * * @param file - An object containing the file path and the root syntax node of the parsed Java file. * @returns A JavaFile object containing the file's path, root node, package name, imports, and main symbol declaration. * @throws Will throw an error if no declaration is found in the file. */ resolveFile(file: { path: string; rootNode: Parser.SyntaxNode; }): JavaFile | undefined { const captures = JAVA_PROGRAM_QUERY.captures(file.rootNode); const packagename = captures.find((c) => c.name === "package")?.node.text ?? ""; const imports = captures.filter((c) => c.name === "import").map((c) => // Unholy string manipulation because wildcard imports break tree-sitter queries. c.node.text.split(" ").findLast((s) => s.length > 0 && s !== ";")! .replace(";", "") ); const declaration = captures.find((c) => !["package", "import"].includes(c.name) ); if (!declaration) { return undefined; } const symbol = new JavaClass( declaration.node, declaration.name as SymbolType, file.path, ); return { path: file.path, rootNode: file.rootNode, symbol, package: packagename, imports, }; } } ================================================ FILE: src/languagePlugins/java/packageResolver/queries.ts ================================================ import Parser from "tree-sitter"; import { javaParser } from "../../../helpers/treeSitter/parsers.ts"; /** * A Tree-sitter query to extract high-level program elements from Java source code. * This query captures: * - Package declarations * - Import statements * - Class declarations * - Interface declarations * - Enum declarations * - Record declarations * - Annotation type declarations */ export const JAVA_PROGRAM_QUERY = new Parser.Query( javaParser.getLanguage(), `(program [ (package_declaration (_) @package) (import_declaration) @import (class_declaration) @class (interface_declaration) @interface (enum_declaration) @enum (record_declaration) @record (annotation_type_declaration) @annotation ])`, ); /** * A Tree-sitter query to extract public static members from Java source code. * This query captures: * - Public static field declarations * - Public static method declarations */ export const JAVA_STATIC_MEMBERS_QUERY = new Parser.Query( javaParser.getLanguage(), ` (field_declaration (modifiers "public" "static")) @field (method_declaration (modifiers "public" "static")) @method `, ); ================================================ FILE: src/languagePlugins/java/packageResolver/types.ts ================================================ import type Parser from "tree-sitter"; import { JAVA_STATIC_MEMBERS_QUERY } from "./queries.ts"; /** * Represents the "public" modifier in Java. */ export const JAVA_PUBLIC_MODIFIER = "public"; /** * Represents the "private" modifier in Java. */ export const JAVA_PRIVATE_MODIFIER = "private"; /** * Represents the "protected" modifier in Java. */ export const JAVA_PROTECTED_MODIFIER = "protected"; /** * Represents the "abstract" modifier in Java. */ export const JAVA_ABSTRACT_MODIFIER = "abstract"; /** * Represents the "final" modifier in Java. */ export const JAVA_FINAL_MODIFIER = "final"; /** * Represents the "static" modifier in Java. */ export const JAVA_STATIC_MODIFIER = "static"; /** * Represents the "strictfp" modifier in Java. */ export const JAVA_STRICTFP_MODIFIER = "strictfp"; /** * Represents the "sealed" modifier in Java. */ export const JAVA_SEALED_MODIFIER = "sealed"; /** * Represents the "non-sealed" modifier in Java. */ export const JAVA_NONSEALED_MODIFIER = "non-sealed"; export type Modifier = | typeof JAVA_PUBLIC_MODIFIER | typeof JAVA_PRIVATE_MODIFIER | typeof JAVA_PROTECTED_MODIFIER | typeof JAVA_ABSTRACT_MODIFIER | typeof JAVA_FINAL_MODIFIER | typeof JAVA_STATIC_MODIFIER | typeof JAVA_STRICTFP_MODIFIER | typeof JAVA_SEALED_MODIFIER | typeof JAVA_NONSEALED_MODIFIER; /** * Represents the "class" type in Java. */ export const JAVA_CLASS_TYPE = "class"; /** * Represents the "interface" type in Java. */ export const JAVA_INTERFACE_TYPE = "interface"; /** * Represents the "enum" type in Java. */ export const JAVA_ENUM_TYPE = "enum"; /** * Represents the "record" type in Java. */ export const JAVA_RECORD_TYPE = "record"; /** * Represents the "annotation" type in Java. */ export const JAVA_ANNOTATION_TYPE = "annotation"; /** * Represents the "field" type in Java. */ export const JAVA_FIELD_TYPE = "field"; /** * Represents the "method" type in Java. */ export const JAVA_METHOD_TYPE = "method"; export type SymbolType = | typeof JAVA_CLASS_TYPE | typeof JAVA_INTERFACE_TYPE | typeof JAVA_ENUM_TYPE | typeof JAVA_RECORD_TYPE | typeof JAVA_ANNOTATION_TYPE | typeof JAVA_FIELD_TYPE | typeof JAVA_METHOD_TYPE; /** * Represents a Java file with its metadata and structure. */ export interface JavaFile { /** The file path of the Java file. */ path: string; /** The root syntax node of the file. */ rootNode: Parser.SyntaxNode; /** The main exported symbol in the file. */ symbol: ExportedSymbol; /** The package name of the file. */ package: string; /** The list of imports in the file. */ imports: string[]; } /** * Represents an exported symbol in a Java file. */ export interface ExportedSymbol { /** The name of the symbol. */ name: string; /** The type of the symbol (e.g., class, method). */ type: SymbolType; /** The modifiers applied to the symbol. */ modifiers: Modifier[]; /** The child symbols of this symbol. */ children: ExportedSymbol[]; /** The file path where the symbol is located. */ filepath: string; /** The syntax node representing the symbol. */ node: Parser.SyntaxNode; /** The syntax node representing the identifier of the symbol. */ idNode: Parser.SyntaxNode; } /** * Represents a Java class or similar construct (e.g., interface, enum). */ export class JavaClass implements ExportedSymbol { /** The name of the class. */ name: string; /** The type of the class (e.g., class, interface). */ type: SymbolType; /** The modifiers applied to the class. */ modifiers: Modifier[]; /** The child symbols of the class. */ children: ExportedSymbol[]; /** The file path where the class is located. */ filepath: string; /** The syntax node representing the class. */ node: Parser.SyntaxNode; /** The syntax node representing the identifier of the class. */ idNode: Parser.SyntaxNode; /** The number of type parameters the class has. */ typeParamCount: number; /** The name of the superclass, if any. */ superclass?: string; /** The list of interfaces implemented by the class. */ interfaces: string[]; /** * Constructs a new JavaClass instance. * @param node - The syntax node representing the class. * @param type - The type of the class (e.g., class, interface). * @param filepath - The file path where the class is located. */ constructor(node: Parser.SyntaxNode, type: SymbolType, filepath: string) { const modifiersNode = node.children.find((c) => c.type === "modifiers"); this.modifiers = []; if (modifiersNode) { this.modifiers.push( ...modifiersNode.children.map((c) => c.text as Modifier), ); } this.idNode = node.childForFieldName("name")!; this.name = this.idNode.text; const typeParams = node.childForFieldName("type_parameters"); this.typeParamCount = 0; if (typeParams) { this.typeParamCount = typeParams.namedChildren.length; } this.superclass = undefined; const superclassNode = node.childForFieldName("superclass"); if (superclassNode) { this.superclass = superclassNode.namedChildren[0].text; } const interfacesNode = node.childForFieldName("interfaces"); this.interfaces = []; if (interfacesNode) { this.interfaces.push( ...interfacesNode.namedChildren[0].namedChildren.map((c) => c.text), ); } this.children = []; const bodyNode = node.childForFieldName("body"); if (bodyNode) { this.children.push( ...bodyNode.children.filter((c) => [ "class_declaration", "interface_declaration", "enum_declaration", "record_declaration", "annotation_type_declaration", ].includes(c.type) ).map((c) => new JavaClass(c, c.type.split("_")[0] as SymbolType, filepath) ), ); this.children.push( ...JAVA_STATIC_MEMBERS_QUERY.captures(bodyNode).map((c) => new JavaMember(c, filepath) ), ); } this.type = type; this.node = node; this.filepath = filepath; } } /** * Represents a member of a Java class (e.g., method, field). */ export class JavaMember implements ExportedSymbol { /** The name of the member. */ name: string; /** The type of the member (e.g., method, field). */ type: SymbolType; /** The modifiers applied to the member. */ modifiers: Modifier[]; /** The child symbols of the member. */ children: ExportedSymbol[]; /** The syntax node representing the member. */ node: Parser.SyntaxNode; /** The syntax node representing the identifier of the member. */ idNode: Parser.SyntaxNode; /** The file path where the member is located. */ filepath: string; /** * Constructs a new JavaMember instance. * @param capture - The query capture representing the member. * @param filepath - The file path where the member is located. */ constructor(capture: Parser.QueryCapture, filepath: string) { this.type = capture.name as SymbolType; this.node = capture.node; this.filepath = filepath; const modifiersNode = this.node.children.find((c) => c.type === "modifiers" )!; this.modifiers = modifiersNode.children.map((c) => c.text as Modifier), this.children = []; if (this.type === "method") { this.idNode = this.node.childForFieldName("name")!; this.name = this.idNode.text; } else { const declarator = this.node.childForFieldName("declarator")!; this.idNode = declarator.childForFieldName("name")!; this.name = this.idNode.text; } } } ================================================ FILE: src/languagePlugins/java/testFiles/constants.ts ================================================ import { javaFilesFolder } from "./index.ts"; import { join } from "@std/path"; // Root export const APP = join(javaFilesFolder, "App.java"); // Food package export const CONDIMENT = join(javaFilesFolder, "food/Condiment.java"); export const FOOD = join(javaFilesFolder, "food/Food.java"); export const STEAK = join(javaFilesFolder, "food/Steak.java"); export const BURGER = join(javaFilesFolder, "food/Burger.java"); export const DOUBLEBURGER = join(javaFilesFolder, "food/DoubleBurger.java"); export const PEBBLE = join(javaFilesFolder, "food/goron/Pebble.java"); // Medication package export const WORMKILLER = join(javaFilesFolder, "medication/Wormkiller.java"); ================================================ FILE: src/languagePlugins/java/testFiles/index.ts ================================================ import * as fs from "node:fs"; import { extname, join } from "@std/path"; import type Parser from "tree-sitter"; import { javaLanguage, javaParser, } from "../../../helpers/treeSitter/parsers.ts"; if (!import.meta.dirname) { throw new Error("import.meta.dirname is not defined"); } export const javaFilesFolder = join( import.meta.dirname, "napi-tests/src/main/java/io/nanoapi/testfiles", ); const javaFilesMap = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); /** * Recursively finds all C files in the given directory and its subdirectories. * @param dir - The directory to search in. */ function findJavaFiles(dir: string) { const files = fs.readdirSync(dir); files.forEach((file) => { const fullPath = join(dir, file); const stat = fs.statSync(fullPath); if (stat.isDirectory()) { if ( !fullPath.includes(".extracted/") && !fullPath.includes("bin/") && !fullPath.includes("obj/") ) { findJavaFiles(fullPath); } } else if ( extname(fullPath) === ".java" ) { const content = fs.readFileSync(fullPath, "utf8"); const tree = javaParser.parse(content); javaFilesMap.set(fullPath, { path: fullPath, rootNode: tree.rootNode }); } }); } export function getJavaFilesMap(): Map< string, { path: string; rootNode: Parser.SyntaxNode } > { findJavaFiles(javaFilesFolder); return javaFilesMap; } export function getJavaFilesContentMap(): Map< string, { path: string; content: string } > { findJavaFiles(javaFilesFolder); const contentMap = new Map(); for (const [filePath, file] of javaFilesMap) { contentMap.set(filePath, { path: file.path, content: file.rootNode.text, }); } return contentMap; } export const dummyLocalConfig = { language: javaLanguage, project: { include: [], exclude: [], }, outDir: "./dist", }; ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/.project ================================================ napi-tests org.eclipse.jdt.core.javabuilder org.eclipse.m2e.core.maven2Builder org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature 1749122394405 30 org.eclipse.core.resources.regexFilterMatcher node_modules|\.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/App.java ================================================ package io.nanoapi.testfiles; import static io.nanoapi.testfiles.food.Burger.*; /** * Hello world! */ public class App { public static void main(String[] args) { restaurantCount++; System.out.println("Hello World!"); } } ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/food/Burger.java ================================================ package io.nanoapi.testfiles.food; import java.io.File; import java.lang.System; public class Burger implements Food { public static int restaurantCount = 1; public void eat() { System.out.println("Yummy !"); } public double getPrice() { return 2.0; } public double getCalories() { return 500.0; } public static void advertisement() { System.out.println("Mmmmm Burger King"); } } ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/food/Condiment.java ================================================ package io.nanoapi.testfiles.food; public enum Condiment { SALAD, TOMATO, ONION, } ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/food/DoubleBurger.java ================================================ package io.nanoapi.testfiles.food; public class DoubleBurger extends Burger {} ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/food/Food.java ================================================ package io.nanoapi.testfiles.food; public interface Food { public void eat(); public double getPrice(); public double getCalories(); } ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/food/Steak.java ================================================ package io.nanoapi.testfiles.food; public class Steak implements Food { public void eat() { System.out.println("Yum !"); } public double getPrice() { return 1.5; } public double getCalories() { return 200; } private static class Tapeworm { // Uh oh !! } } ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/food/goron/Pebble.java ================================================ package io.nanoapi.testfiles.food.goron; import io.nanoapi.testfiles.food.Food; public class Pebble implements Food { public void eat() { System.out.println("crunch !"); } public double getPrice() { return 0.01; } public double getCalories() { return 1; } public class Sandworm { // Gorons get tapeworms too ?! } } ================================================ FILE: src/languagePlugins/java/testFiles/napi-tests/src/main/java/io/nanoapi/testfiles/medication/Wormkiller.java ================================================ package io.nanoapi.testfiles.medication; import io.nanoapi.testfiles.food.Steak; import io.nanoapi.testfiles.food.goron.Pebble; public class Wormkiller { public void applyTo(Steak steak) {} public void killWorm(Pebble.Sandworm sandworm) {} } ================================================ FILE: src/languagePlugins/python/dependencyResolver/index.test.ts ================================================ import { beforeEach, describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type Parser from "tree-sitter"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; import { PythonExportExtractor } from "../exportExtractor/index.ts"; import { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PythonUsageResolver } from "../usageResolver/index.ts"; import { PythonImportExtractor } from "../importExtractor/index.ts"; import { PythonItemResolver } from "../itemResolver/index.ts"; import { PythonDependencyResolver } from "../dependencyResolver/index.ts"; import type { FileDependencies } from "./types.ts"; import { PYTHON_VARIABLE_TYPE } from "../exportExtractor/types.ts"; import { PythonMetricsAnalyzer } from "../metricAnalyzer/index.ts"; describe("DependencyResolver", () => { let parser: Parser; let exportExtractor: PythonExportExtractor; let importExtractor: PythonImportExtractor; let usageResolver: PythonUsageResolver; let moduleResolver: PythonModuleResolver; let itemResolver: PythonItemResolver; let metricsAnalyzer: PythonMetricsAnalyzer; let dependencyResolver: PythonDependencyResolver; let files: Map; beforeEach(() => { parser = pythonParser; // Create test files files = new Map([ // Original module with function [ "original.py", { path: "original.py", rootNode: parser.parse(` def original_func(): return "Original function" `).rootNode, }, ], // Re-exporter module [ "reexporter.py", { path: "reexporter.py", rootNode: parser.parse(` from original import original_func `).rootNode, }, ], // Consumer module [ "consumer.py", { path: "consumer.py", rootNode: parser.parse(` from reexporter import original_func def consumer_func(): return original_func() `).rootNode, }, ], // Another module for testing [ "another.py", { path: "another.py", rootNode: parser.parse(` def another_func(): return "Another function" `).rootNode, }, ], // Direct consumer module that imports directly from original [ "direct_consumer.py", { path: "direct_consumer.py", rootNode: parser.parse(` from original import original_func def direct_func(): return original_func() `).rootNode, }, ], // Files for multi-level re-export test [ "level1.py", { path: "level1.py", rootNode: parser.parse(` from original import original_func `).rootNode, }, ], [ "level2.py", { path: "level2.py", rootNode: parser.parse(` from level1 import original_func `).rootNode, }, ], [ "level3_consumer.py", { path: "level3_consumer.py", rootNode: parser.parse(` from level2 import original_func def level3_func(): return original_func() `).rootNode, }, ], // Module with modified variables and different dependencies at each modification [ "modified_variables.py", { path: "modified_variables.py", rootNode: parser.parse(` from original import original_func # Initial declaration with dependency on original_func counter = original_func() from another import another_func # Modification with dependency on another_func counter += len(another_func()) # Another modification with both dependencies counter = counter + len(original_func()) + len(another_func()) `).rootNode, }, ], // Module with multiple function definitions [ "multi_function.py", { path: "multi_function.py", rootNode: parser.parse(` from original import original_func # First definition with dependency on original def multi_func(): return original_func() from another import another_func # Second definition with dependency on another def multi_func(param): return another_func() + param `).rootNode, }, ], // Module with sequential variable dependencies [ "sequential_vars.py", { path: "sequential_vars.py", rootNode: parser.parse(` from original import original_func # First variable depends on original_func var1 = original_func() # Second variable depends on var1 var2 = var1 + " modified" from another import another_func # Third variable depends on another_func and var2 var3 = another_func() + var2 # Modification of var1 now depends on another_func var1 = another_func() `).rootNode, }, ], // Module with complex list and dictionary variables [ "complex_vars.py", { path: "complex_vars.py", rootNode: parser.parse(` from original import original_func from another import another_func # List with dependency on original_func my_list = [original_func()] # Augmented assignment with dependency on another_func my_list += [another_func()] # Dictionary with dependencies on both functions my_dict = { "original": original_func(), "another": another_func() } # Dictionary modification my_dict["third"] = original_func() + another_func() # List reassignment with both dependencies my_list = [original_func(), another_func()] `).rootNode, }, ], ]); exportExtractor = new PythonExportExtractor(parser, files); importExtractor = new PythonImportExtractor(parser, files); moduleResolver = new PythonModuleResolver(new Set(files.keys()), "3.10"); usageResolver = new PythonUsageResolver(parser, exportExtractor); itemResolver = new PythonItemResolver( exportExtractor, importExtractor, moduleResolver, ); metricsAnalyzer = new PythonMetricsAnalyzer(parser); dependencyResolver = new PythonDependencyResolver( files, exportExtractor, importExtractor, itemResolver, usageResolver, moduleResolver, metricsAnalyzer, ); }); describe("Re-exporting modules", () => { test("should track both original and re-exporting modules as dependencies", () => { // Analyze dependencies for the consumer module const dependencies: FileDependencies = dependencyResolver .getFileDependencies("consumer.py"); // We expect the consumer to depend on both original.py and reexporter.py expect(dependencies.dependencies.size).toBe(2); // Check dependency on original.py expect(dependencies.dependencies.has("original.py")).toBeTruthy(); const originalDep = dependencies.dependencies.get("original.py"); expect(originalDep?.isExternal).toBe(false); expect(originalDep?.symbols.has("original_func")).toBeTruthy(); // Check dependency on reexporter.py (as a re-exporting module) expect(dependencies.dependencies.has("reexporter.py")).toBeTruthy(); const reexporterDep = dependencies.dependencies.get("reexporter.py"); expect(reexporterDep?.isExternal).toBe(false); // Re-exporting modules are tracked without symbols expect(reexporterDep?.symbols.size).toBe(0); }); test("should not track reexporter as dependency if it's not used", () => { // Analyze dependencies const dependencies: FileDependencies = dependencyResolver .getFileDependencies( "direct_consumer.py", ); // Should only depend on original.py expect(dependencies.dependencies.size).toBe(1); expect(dependencies.dependencies.has("original.py")).toBeTruthy(); expect(dependencies.dependencies.has("reexporter.py")).toBeFalsy(); }); test("should handle multiple levels of re-exports", () => { // Analyze dependencies const dependencies: FileDependencies = dependencyResolver .getFileDependencies( "level3_consumer.py", ); // Should depend on all three modules in the chain expect(dependencies.dependencies.size).toBe(3); expect(dependencies.dependencies.has("original.py")).toBeTruthy(); expect(dependencies.dependencies.has("level1.py")).toBeTruthy(); expect(dependencies.dependencies.has("level2.py")).toBeTruthy(); // Original should have the symbol const originalDep = dependencies.dependencies.get("original.py"); expect(originalDep?.symbols.has("original_func")).toBeTruthy(); // Re-exporters should have no symbols const level1Dep = dependencies.dependencies.get("level1.py"); const level2Dep = dependencies.dependencies.get("level2.py"); expect(level1Dep?.symbols.size).toBe(0); expect(level2Dep?.symbols.size).toBe(0); }); }); describe("Variable modifications", () => { test("should track dependencies in each variable modification", () => { // Analyze dependencies for the module with modified variables const dependencies: FileDependencies = dependencyResolver .getFileDependencies( "modified_variables.py", ); // Should depend on both original.py and another.py expect(dependencies.dependencies.size).toBe(2); expect(dependencies.dependencies.has("original.py")).toBeTruthy(); expect(dependencies.dependencies.has("another.py")).toBeTruthy(); // Both dependencies should have their functions as used symbols const originalDep = dependencies.dependencies.get("original.py"); const anotherDep = dependencies.dependencies.get("another.py"); expect(originalDep?.symbols.has("original_func")).toBeTruthy(); expect(anotherDep?.symbols.has("another_func")).toBeTruthy(); // Find counter variable in symbols const counterSymbol = dependencies.symbols.find( (s) => s.id === "counter" && s.type === PYTHON_VARIABLE_TYPE, ); expect(counterSymbol).toBeDefined(); // Counter should have dependencies on both modules expect(counterSymbol?.dependencies.size).toBe(2); expect(counterSymbol?.dependencies.has("original.py")).toBeTruthy(); expect(counterSymbol?.dependencies.has("another.py")).toBeTruthy(); // Both dependencies should have their functions as used symbols const counterOriginalDep = counterSymbol?.dependencies.get("original.py"); const counterAnotherDep = counterSymbol?.dependencies.get("another.py"); expect(counterOriginalDep?.symbols.has("original_func")).toBeTruthy(); expect(counterAnotherDep?.symbols.has("another_func")).toBeTruthy(); }); test("should track dependencies in multiple function definitions", () => { // Analyze dependencies for the module with multiple function definitions const dependencies: FileDependencies = dependencyResolver .getFileDependencies( "multi_function.py", ); // Should depend on both original.py and another.py expect(dependencies.dependencies.size).toBe(2); expect(dependencies.dependencies.has("original.py")).toBeTruthy(); expect(dependencies.dependencies.has("another.py")).toBeTruthy(); // Find multi_func function in symbols const multiFuncSymbol = dependencies.symbols.find( (s) => s.id === "multi_func", ); expect(multiFuncSymbol).toBeDefined(); // multi_func should have dependencies on both modules expect(multiFuncSymbol?.dependencies.size).toBe(2); expect(multiFuncSymbol?.dependencies.has("original.py")).toBeTruthy(); expect(multiFuncSymbol?.dependencies.has("another.py")).toBeTruthy(); }); test("should handle sequential variable dependencies", () => { // Analyze dependencies for the module with sequential variable dependencies const dependencies: FileDependencies = dependencyResolver .getFileDependencies( "sequential_vars.py", ); // Module should depend on both original.py and another.py expect(dependencies.dependencies.has("original.py")).toBeTruthy(); expect(dependencies.dependencies.has("another.py")).toBeTruthy(); // Find all three variables in symbols const variables = dependencies.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // Should have three variable symbols: var1, var2, var3 const varIds = variables.map((v) => v.id); expect(varIds).toContain("var1"); expect(varIds).toContain("var2"); expect(varIds).toContain("var3"); // Each variable should have at least some dependencies for (const varSymbol of variables) { expect(varSymbol.dependencies.size).toBeGreaterThan(0); } // At least one variable should depend on another.py const hasAnotherDependency = variables.some((v) => v.dependencies.has("another.py") ); expect(hasAnotherDependency).toBeTruthy(); // At least one variable should depend on original.py const hasOriginalDependency = variables.some((v) => v.dependencies.has("original.py") ); expect(hasOriginalDependency).toBeTruthy(); }); test("should handle complex list and dictionary variables", () => { // Analyze dependencies for the module with complex list and dictionary variables const dependencies: FileDependencies = dependencyResolver .getFileDependencies( "complex_vars.py", ); // Should depend on both original.py and another.py expect(dependencies.dependencies.size).toBe(2); // Find my_list variable in symbols const myListSymbol = dependencies.symbols.find( (s) => s.id === "my_list" && s.type === PYTHON_VARIABLE_TYPE, ); expect(myListSymbol).toBeDefined(); // my_list should depend on both modules expect(myListSymbol?.dependencies.size).toBe(2); expect(myListSymbol?.dependencies.has("original.py")).toBeTruthy(); expect(myListSymbol?.dependencies.has("another.py")).toBeTruthy(); // Find my_dict variable in symbols const myDictSymbol = dependencies.symbols.find( (s) => s.id === "my_dict" && s.type === PYTHON_VARIABLE_TYPE, ); expect(myDictSymbol).toBeDefined(); // my_dict should depend on both modules expect(myDictSymbol?.dependencies.size).toBe(2); expect(myDictSymbol?.dependencies.has("original.py")).toBeTruthy(); expect(myDictSymbol?.dependencies.has("another.py")).toBeTruthy(); }); }); }); ================================================ FILE: src/languagePlugins/python/dependencyResolver/index.ts ================================================ import type Parser from "tree-sitter"; import type { PythonExportExtractor } from "../exportExtractor/index.ts"; import type { PythonUsageResolver } from "../usageResolver/index.ts"; import type { ExternalUsage, InternalUsage } from "../usageResolver/types.ts"; import type { PythonItemResolver } from "../itemResolver/index.ts"; import { PYTHON_INTERNAL_MODULE_TYPE, type ResolvedExternalModule, type ResolvedExternalSymbol, type ResolvedInternalModule, type ResolvedInternalSymbol, } from "../itemResolver/types.ts"; import type { PythonImportExtractor } from "../importExtractor/index.ts"; import type { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PYTHON_NAMESPACE_MODULE_TYPE, type PythonModule, } from "../moduleResolver/types.ts"; import type { FileDependencies, ModuleDependency, SymbolDependency, } from "./types.ts"; import { FROM_IMPORT_STATEMENT_TYPE, type ImportStatement, NORMAL_IMPORT_STATEMENT_TYPE, } from "../importExtractor/types.ts"; import type { PythonMetricsAnalyzer } from "../metricAnalyzer/index.ts"; /** * PythonDependencyResolver analyzes a Python file's AST to build a dependency manifest. * It uses the PythonExportExtractor to determine exported symbols and the PythonUsageResolver * to determine which imports are used and how they are used within the file. * * Dependencies are computed at two levels: * * 1. File-level: Based on the file's root AST node. * 2. Symbol-level: For each exported symbol, by analyzing its AST subtree. * * The resolver distinguishes between internal (project) and external dependencies, * and tracks which symbols from each module are actually used. * * Results are cached to avoid re-computation. */ export class PythonDependencyResolver { private files: Map; private exportExtractor: PythonExportExtractor; private importExtractor: PythonImportExtractor; private itemResolver: PythonItemResolver; private usageResolver: PythonUsageResolver; private moduleResolver: PythonModuleResolver; private metricsAnalyzer: PythonMetricsAnalyzer; private fileDependenciesCache = new Map(); constructor( files: Map, exportExtractor: PythonExportExtractor, importExtractor: PythonImportExtractor, itemResolver: PythonItemResolver, usageResolver: PythonUsageResolver, moduleResolver: PythonModuleResolver, metricsAnalyzer: PythonMetricsAnalyzer, ) { this.files = files; this.exportExtractor = exportExtractor; this.importExtractor = importExtractor; this.itemResolver = itemResolver; this.usageResolver = usageResolver; this.moduleResolver = moduleResolver; this.metricsAnalyzer = metricsAnalyzer; } public getFileDependencies(path: string) { if (this.fileDependenciesCache.has(path)) { return this.fileDependenciesCache.get(path) as FileDependencies; } const file = this.files.get(path); if (!file) { throw new Error(`File not found: ${path}`); } const complexityMetrics = this.metricsAnalyzer.analyzeNodes([ file.rootNode, ]); const fileDependencies: FileDependencies = { filePath: file.path, metrics: { characterCount: complexityMetrics.characterCount, codeCharacterCount: complexityMetrics.codeCharacterCount, linesCount: complexityMetrics.linesCount, codeLineCount: complexityMetrics.codeLinesCount, cyclomaticComplexity: complexityMetrics.cyclomaticComplexity, }, dependencies: new Map(), symbols: [], }; // Get module from file path const fileModule = this.moduleResolver.getModuleFromFilePath(file.path); // Get all imports const importStmts = this.importExtractor.getImportStatements(file.path); // Analyze dependencies for the file-level node const { internalUsageMap, externalUsageMap } = this .analyzeDependenciesForNode( file.rootNode, fileModule, importStmts, ); // Convert usage maps to dependencies fileDependencies.dependencies = new Map([ ...this.convertInternalUsageToDependencies(internalUsageMap), ...this.convertExternalUsageToDependencies(externalUsageMap), ]); // get all symbols const { symbols } = this.exportExtractor.getSymbols(file.path); // For each symbol, resolve its dependencies for (const symbol of symbols) { const complexityMetrics = this.metricsAnalyzer.analyzeNodes(symbol.nodes); const symbolDependencies: SymbolDependency = { id: symbol.id, type: symbol.type, positions: symbol.nodes.map((node) => ({ start: { index: node.startIndex, row: node.startPosition.row, column: node.startPosition.column, }, end: { index: node.endIndex, row: node.endPosition.row, column: node.endPosition.column, }, })), metrics: { characterCount: complexityMetrics.characterCount, codeCharacterCount: complexityMetrics.codeCharacterCount, linesCount: complexityMetrics.linesCount, codeLineCount: complexityMetrics.codeLinesCount, cyclomaticComplexity: complexityMetrics.cyclomaticComplexity, }, dependencies: new Map(), }; // Process each node of the symbol independently, then merge results const symbolInternalUsageMap = new Map(); const symbolExternalUsageMap = new Map(); // For each node of the symbol, analyze its dependencies for (const symbolNode of symbol.nodes) { // Find import statements that are relevant for this specific node const nodeImportStmts = this.filterImportStatementsForNode( symbolNode, importStmts, symbols, ); // Analyze dependencies for the current node const nodeResult = this.analyzeDependenciesForNode( symbolNode, fileModule, nodeImportStmts, ); // Merge the results into the symbol's usage maps this.mergeUsageMaps( nodeResult.internalUsageMap, symbolInternalUsageMap, ); this.mergeUsageMaps( nodeResult.externalUsageMap, symbolExternalUsageMap, ); // Resolve internal usage for other symbols in the file for this node for (const otherSymbol of symbols) { if (otherSymbol.id === symbol.id) { continue; } this.usageResolver.resolveInternalUsageForSymbol( symbolNode, nodeImportStmts.map((stmt) => stmt.node), fileModule, otherSymbol, otherSymbol.identifierNode.text, symbolInternalUsageMap, ); } } // Convert usage maps to dependencies symbolDependencies.dependencies = new Map([ ...this.convertInternalUsageToDependencies(symbolInternalUsageMap), ...this.convertExternalUsageToDependencies(symbolExternalUsageMap), ]); fileDependencies.symbols.push(symbolDependencies); } // Cache the file dependencies this.fileDependenciesCache.set(path, fileDependencies); return fileDependencies; } /** * Filters import statements that are relevant for a specific node of a symbol * @param symbolNode The specific node of a symbol * @param allImportStmts All import statements in the file * @param allSymbols All symbols in the file * @returns Filtered import statements relevant for this node */ private filterImportStatementsForNode( symbolNode: Parser.SyntaxNode, allImportStmts: ImportStatement[], allSymbols: { id: string; nodes: Parser.SyntaxNode[]; identifierNode: Parser.SyntaxNode; type: string; }[], ): ImportStatement[] { return allImportStmts.filter((importStmt) => { // Include only imports that come before this node const isBeforeNode = importStmt.node.endIndex < symbolNode.endIndex; // Exclude imports that are contained within other symbols let isWithinOtherSymbols = false; for (const otherSymbol of allSymbols) { for (const otherNode of otherSymbol.nodes) { // Skip the current node we're analyzing if (otherNode === symbolNode) { continue; } // Check if the import is contained within another symbol's node if ( importStmt.node.startIndex >= otherNode.startIndex && importStmt.node.endIndex <= otherNode.endIndex ) { isWithinOtherSymbols = true; break; } } if (isWithinOtherSymbols) { break; } } return isBeforeNode && !isWithinOtherSymbols; }); } /** * Merges two usage maps together, combining their contents */ private mergeUsageMaps( source: Map, target: Map, ): void { for (const [key, sourceValue] of source.entries()) { if (!target.has(key)) { target.set(key, sourceValue); } else { const targetValue = target.get(key); if (targetValue) { // Handle internal usage maps if ("symbols" in sourceValue && "symbols" in targetValue) { const sourceInternal = sourceValue as InternalUsage; const targetInternal = targetValue as InternalUsage; // Merge symbols for (const [symbolId, symbol] of sourceInternal.symbols.entries()) { targetInternal.symbols.set(symbolId, symbol); } // Merge re-exporting modules if they exist if (sourceInternal.reExportingModules) { if (!targetInternal.reExportingModules) { targetInternal.reExportingModules = new Map(); } for ( const [ modulePath, module, ] of sourceInternal.reExportingModules.entries() ) { targetInternal.reExportingModules.set(modulePath, module); } } } // Handle external usage maps else if ("itemNames" in sourceValue && "itemNames" in targetValue) { const sourceExternal = sourceValue as ExternalUsage; const targetExternal = targetValue as ExternalUsage; // Merge item names for (const itemName of sourceExternal.itemNames) { targetExternal.itemNames.add(itemName); } } } } } } /** * Analyzes dependencies for a given AST node (can be a file or symbol node) * @param node The AST node to analyze * @param contextModule The module context for resolving imports * @param importStmts The import statements to consider * @returns Maps containing internal and external usage information */ private analyzeDependenciesForNode( node: Parser.SyntaxNode, contextModule: PythonModule, // Using any here for simplicity importStmts: ImportStatement[], ): { internalUsageMap: Map; externalUsageMap: Map; } { // Initialize usage result const internalUsageMap = new Map(); const externalUsageMap = new Map(); const nodesToExclude = importStmts.map((importStmt) => importStmt.node); // Process all explicit imports first - both normal imports and from-imports // (excluding wildcards) in the order they appear in the file for (const importStmt of importStmts) { if (importStmt.type === FROM_IMPORT_STATEMENT_TYPE) { // For "from X import Y" statements const member = importStmt.members[0]; if (member.isWildcardImport) { // process wildcard import last continue; } // Try to resolve the source module (X in "from X import Y") // This could be an internal module or an external one const sourceModule = this.moduleResolver.resolveModule( contextModule, member.identifierNode.text, ); for (const item of member.items || []) { // Internal module if (sourceModule) { const resolvedItem = this.itemResolver.resolveItem( sourceModule, item.identifierNode.text, ); if (!resolvedItem) { // Skip items that can't be resolved instead of throwing continue; } if (resolvedItem.type === PYTHON_INTERNAL_MODULE_TYPE) { const internalResolvedModule = resolvedItem as ResolvedInternalModule; if (internalResolvedModule.symbol) { const internalResolvedSymbol = internalResolvedModule as ResolvedInternalSymbol; // Pass the immediate re-exporting module if it's different from the original module const reExportingModule = internalResolvedSymbol.module.path !== sourceModule.path ? sourceModule : undefined; this.usageResolver.resolveInternalUsageForSymbol( node, nodesToExclude, internalResolvedSymbol.module, internalResolvedSymbol.symbol, item.aliasNode?.text || item.identifierNode.text, internalUsageMap, reExportingModule, ); // Add all modules in the re-export chain as dependencies if ( internalResolvedSymbol.reExportChain && internalResolvedSymbol.reExportChain.length > 0 ) { for ( const reExportModule of internalResolvedSymbol.reExportChain ) { if (reExportModule.path !== sourceModule.path) { // Skip the immediate re-exporter (already handled) this.usageResolver.resolveInternalUsageForSymbol( node, nodesToExclude, internalResolvedSymbol.module, // Original module internalResolvedSymbol.symbol, // Original symbol item.aliasNode?.text || item.identifierNode.text, internalUsageMap, reExportModule, // Intermediate re-exporting module ); } } } } else { this.usageResolver.resolveInternalUsageForModule( node, nodesToExclude, internalResolvedModule.module, item.aliasNode?.text || item.identifierNode.text, internalUsageMap, ); } } } else { // external module this.usageResolver.resolveExternalUsageForItem( node, nodesToExclude, { moduleName: member.identifierNode.text, itemName: item.identifierNode.text, }, item.aliasNode?.text || item.identifierNode.text, externalUsageMap, ); } } } if (importStmt.type === NORMAL_IMPORT_STATEMENT_TYPE) { importStmt.members.forEach((member) => { const resolvedItem = this.itemResolver.resolveItem( contextModule, member.aliasNode?.text || member.identifierNode.text, ); if (!resolvedItem) { // Skip items that can't be resolved return; } if (resolvedItem.type === PYTHON_INTERNAL_MODULE_TYPE) { const internalResolvedModule = resolvedItem as ResolvedInternalModule; this.usageResolver.resolveInternalUsageForModule( node, nodesToExclude, internalResolvedModule.module, member.aliasNode?.text || member.identifierNode.text, internalUsageMap, ); } else { const externalResolvedModule = resolvedItem as ResolvedExternalModule; this.usageResolver.resolveExternalUsageForItem( node, nodesToExclude, { moduleName: externalResolvedModule.moduleName, }, member.aliasNode?.text || member.identifierNode.text, externalUsageMap, ); } }); } } // Process wildcard imports last for (const importStmt of importStmts) { if (importStmt.type === FROM_IMPORT_STATEMENT_TYPE) { const member = importStmt.members[0]; if (!member.isWildcardImport) { continue; } const sourceModule = this.moduleResolver.resolveModule( contextModule, member.identifierNode.text, ); // For external modules, we can't resolve items // Only thing we can do is to add the module as a dependency if (!sourceModule) { // External module processing is done when converting usage map to dependencies continue; } else { // internal module, we can resolve items const resolvedItem = this.itemResolver.resolveItem( sourceModule, member.identifierNode.text, ); if (!resolvedItem) { continue; } if (resolvedItem.type === PYTHON_INTERNAL_MODULE_TYPE) { const internalResolvedModule = resolvedItem as ResolvedInternalModule; if (!internalResolvedModule.symbol) { continue; } const internalResolvedSymbol = internalResolvedModule as ResolvedInternalSymbol; this.usageResolver.resolveInternalUsageForSymbol( node, nodesToExclude, internalResolvedSymbol.module, internalResolvedSymbol.symbol, internalResolvedSymbol.symbol.identifierNode.text, internalUsageMap, ); } else { const externalResolvedModule = resolvedItem as ResolvedExternalModule; if (!externalResolvedModule.symbol) { continue; } const externalResolvedSymbol = externalResolvedModule as ResolvedExternalSymbol; this.usageResolver.resolveExternalUsageForItem( node, nodesToExclude, { moduleName: externalResolvedSymbol.moduleName, itemName: externalResolvedSymbol.symbolName, }, externalResolvedSymbol.symbolName, externalUsageMap, ); } } } } return { internalUsageMap, externalUsageMap }; } /** * Converts internal usage map to module dependencies */ private convertInternalUsageToDependencies( internalUsageMap: Map, ): Map { const dependencies = new Map(); for (const [modulePath, usage] of internalUsageMap.entries()) { const dependency: ModuleDependency = { id: modulePath, isExternal: false, isNamespaceModule: usage.module.type === PYTHON_NAMESPACE_MODULE_TYPE, symbols: new Set(), }; // Add all used symbols for (const symbol of usage.symbols.values()) { dependency.symbols.add(symbol.identifierNode.text); } dependencies.set(modulePath, dependency); // Add re-exporting modules as dependencies without symbols if (usage.reExportingModules) { for ( const [ reExportingModulePath, reExportingModule, ] of usage.reExportingModules.entries() ) { if (!dependencies.has(reExportingModulePath)) { dependencies.set(reExportingModulePath, { id: reExportingModulePath, isExternal: false, isNamespaceModule: reExportingModule.type === PYTHON_NAMESPACE_MODULE_TYPE, symbols: new Set(), // Empty set since we don't need specific symbols }); } } } } return dependencies; } /** * Converts external usage map to module dependencies */ private convertExternalUsageToDependencies( externalUsageMap: Map, ): Map { const dependencies = new Map(); for (const [modulePath, usage] of externalUsageMap.entries()) { const dependency: ModuleDependency = { id: modulePath, isExternal: true, isNamespaceModule: false, symbols: new Set(), }; // Add all used items for (const itemName of usage.itemNames) { dependency.symbols.add(itemName); } dependencies.set(modulePath, dependency); } return dependencies; } } ================================================ FILE: src/languagePlugins/python/dependencyResolver/types.ts ================================================ import type { PythonSymbolType } from "../exportExtractor/types.ts"; /** * Represents a dependency on a specific module. * This includes both internal project modules and external libraries. */ export interface ModuleDependency { /** Module identifier (typically the file path) */ id: string; /** True if this is an external dependency (stdlib/third-party) */ isExternal: boolean; /** * True if this module is a namespace module * Usefull to have because namespace modules are not files */ isNamespaceModule: boolean; /** Symbols used from this module, mapping symbol ID to symbol name */ symbols: Set; } /** * Represents dependency information for a specific exported symbol (function, class, variable). * This is used for fine-grained code splitting. */ export interface SymbolDependency { /** Symbol identifier */ id: string; /** Symbol type (class, function, variable) */ type: PythonSymbolType; /** Positions of the symbol in the file */ positions: { start: { index: number; row: number; column: number; }; end: { index: number; row: number; column: number; }; }[]; /** Size metrics for the symbol */ metrics: { /** Total character count in the symbol */ characterCount: number; /** Total character count in the symbol */ codeCharacterCount: number; /** Total line count in the symbol */ linesCount: number; /** Total line count in the symbol */ codeLineCount: number; /** Total cyclomatic complexity of the symbol */ cyclomaticComplexity: number; }; /** Map of modules this symbol depends on */ dependencies: Map; } /** * Represents comprehensive dependency information for a file. */ export interface FileDependencies { /** The path to the analyzed file */ filePath: string; /** File size metrics */ metrics: { /** Total character count in the symbol */ characterCount: number; /** Total character count in the symbol */ codeCharacterCount: number; /** Total line count in the symbol */ linesCount: number; /** Total line count in the symbol */ codeLineCount: number; /** Total cyclomatic complexity of the symbol */ cyclomaticComplexity: number; }; /** Module-level dependencies for the entire file */ dependencies: Map; /** Exported symbols with their individual dependency info */ symbols: SymbolDependency[]; } /** * Complete module dependency graph for an entire project. * Maps file paths to their dependency information. */ export type FileDependencyMap = Map; ================================================ FILE: src/languagePlugins/python/exportExtractor/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type Parser from "tree-sitter"; import { PythonExportExtractor } from "./index.ts"; import { PYTHON_CLASS_TYPE, PYTHON_FUNCTION_TYPE, PYTHON_VARIABLE_TYPE, } from "./types.ts"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; describe("PythonExportExtractor", () => { const parser = pythonParser; test("should extract simple class definitions", () => { const code = ` class MyClass: pass `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("class_test.py", { path: "class_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("class_test.py"); const classSymbols = result.symbols.filter( (s) => s.type === PYTHON_CLASS_TYPE, ); expect(classSymbols.length).toBeGreaterThanOrEqual(1); const myClass = classSymbols.find((s) => s.id === "MyClass"); expect(myClass).toBeDefined(); // Optionally, check that the syntax node's text matches expectations. expect(myClass?.identifierNode.text).toBe("MyClass"); }); test("should extract decorated class definitions", () => { const code = ` @decorator class DecoratedClass: pass `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("decorated_class_test.py", { path: "decorated_class_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("decorated_class_test.py"); const classSymbols = result.symbols.filter( (s) => s.type === PYTHON_CLASS_TYPE, ); expect(classSymbols.length).toBeGreaterThanOrEqual(1); const decClass = classSymbols.find((s) => s.id === "DecoratedClass"); expect(decClass).toBeDefined(); }); test("should extract simple function definitions", () => { const code = ` def my_function(): pass `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("function_test.py", { path: "function_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("function_test.py"); const functionSymbols = result.symbols.filter( (s) => s.type === PYTHON_FUNCTION_TYPE, ); expect(functionSymbols.length).toBeGreaterThanOrEqual(1); const func = functionSymbols.find((s) => s.id === "my_function"); expect(func).toBeDefined(); }); test("should extract decorated function definitions", () => { const code = ` @decorator def decorated_function(): pass `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("decorated_function_test.py", { path: "decorated_function_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("decorated_function_test.py"); const functionSymbols = result.symbols.filter( (s) => s.type === PYTHON_FUNCTION_TYPE, ); expect(functionSymbols.length).toBeGreaterThanOrEqual(1); const decFunc = functionSymbols.find((s) => s.id === "decorated_function"); expect(decFunc).toBeDefined(); }); test("should extract variable assignments and not include __all__", () => { const code = ` x = 10 y, z = 20, 30 __all__ = ["x", "y", "z"] `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("variable_test.py", { path: "variable_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("variable_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // Expect that x, y, z are extracted as variables. const varX = variableSymbols.find((s) => s.id === "x"); const varY = variableSymbols.find((s) => s.id === "y"); const varZ = variableSymbols.find((s) => s.id === "z"); expect(varX).toBeDefined(); expect(varY).toBeDefined(); expect(varZ).toBeDefined(); // __all__ should not be among exported symbols. const exportedIds = result.symbols.map((s) => s.id); expect(exportedIds).not.toContain("__all__"); }); test("should extract __all__ elements correctly", () => { const code = ` __all__ = ["a", "b", "c"] a = 1 b = 2 c = 3 d = 4 `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("all_test.py", { path: "all_test.py", rootNode: tree.rootNode }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("all_test.py"); // publicSymbols should reflect the __all__ definition. expect(result.publicSymbols).toContain("a"); expect(result.publicSymbols).toContain("b"); expect(result.publicSymbols).toContain("c"); // 'd' is not public because it's not in __all__ expect(result.publicSymbols).not.toContain("d"); }); test("should extract multiple function definitions with the same name", () => { const code = ` def my_function(): pass # Some comment in between def my_function(a, b): return a + b `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("multi_function_test.py", { path: "multi_function_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("multi_function_test.py"); const functionSymbols = result.symbols.filter( (s) => s.type === PYTHON_FUNCTION_TYPE, ); // Should find one symbol for my_function with two nodes const func = functionSymbols.find((s) => s.id === "my_function"); expect(func).toBeDefined(); expect(func?.nodes.length).toBe(2); }); test("should extract multiple class definitions with the same name", () => { const code = ` class MyClass: def method1(self): pass # Some comment in between class MyClass: def method2(self): pass `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("multi_class_test.py", { path: "multi_class_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("multi_class_test.py"); const classSymbols = result.symbols.filter( (s) => s.type === PYTHON_CLASS_TYPE, ); // Should find one symbol for MyClass with two nodes const myClass = classSymbols.find((s) => s.id === "MyClass"); expect(myClass).toBeDefined(); expect(myClass?.nodes.length).toBe(2); }); test("should capture variable modifications", () => { const code = ` counter = 0 counter += 1 counter = counter + 10 `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("variable_mod_test.py", { path: "variable_mod_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("variable_mod_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // There should be only one symbol for counter expect(variableSymbols.length).toBe(1); // The one symbol should be counter const counter = variableSymbols[0]; expect(counter.id).toBe("counter"); // After deduplication, we expect 3 nodes: initial assignment + two modifications expect(counter.nodes.length).toBe(3); // Verify that all the expected nodes are included const nodeCaptured = (text: string) => counter.nodes.some((node) => node.text.includes(text)); expect(nodeCaptured("counter = 0")).toBe(true); expect(nodeCaptured("counter += 1")).toBe(true); expect(nodeCaptured("counter = counter + 10")).toBe(true); }); test("should not create new symbols for variable modifications", () => { const code = ` # Only modify a variable without initializing it existing_var += 5 `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("var_mod_only_test.py", { path: "var_mod_only_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("var_mod_only_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // Should not find a symbol for existing_var as it's only modified without initialization const existingVar = variableSymbols.find((s) => s.id === "existing_var"); expect(existingVar).toBeUndefined(); }); test("should handle mixed symbol types and multiple modifications", () => { const code = ` x = 10 def func(): pass x += 5 class MyClass: pass x = 20 def func(a): return a `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("mixed_symbols_test.py", { path: "mixed_symbols_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("mixed_symbols_test.py"); // Variable x should have 3 nodes const x = result.symbols.find( (s) => s.id === "x" && s.type === PYTHON_VARIABLE_TYPE, ); expect(x).toBeDefined(); expect(x?.nodes.length).toBe(3); // Function func should have 2 nodes const func = result.symbols.find( (s) => s.id === "func" && s.type === PYTHON_FUNCTION_TYPE, ); expect(func).toBeDefined(); expect(func?.nodes.length).toBe(2); // Class MyClass should have 1 node const myClass = result.symbols.find( (s) => s.id === "MyClass" && s.type === PYTHON_CLASS_TYPE, ); expect(myClass).toBeDefined(); expect(myClass?.nodes.length).toBe(1); }); test("should capture list variable modifications", () => { const code = ` my_list = [1, 2, 3] my_list.append(4) my_list += [5, 6] my_list.extend([7, 8]) my_list[0] = 0 my_list = my_list + [9, 10] `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("list_mod_test.py", { path: "list_mod_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("list_mod_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // There should be only one symbol for my_list const myList = variableSymbols.find((s) => s.id === "my_list"); expect(myList).toBeDefined(); // Should have nodes for the initial assignment and += operation // Method calls like append and extend are not captured as they're not direct assignments // But reassignment (my_list = my_list + [9, 10]) should be captured expect(myList?.nodes.length).toBeGreaterThanOrEqual(3); // Verify that key operations are included const nodeCaptured = (text: string) => myList?.nodes.some((node) => node.text.includes(text)); expect(nodeCaptured("my_list = [1, 2, 3]")).toBe(true); expect(nodeCaptured("my_list += [5, 6]")).toBe(true); expect(nodeCaptured("my_list = my_list + [9, 10]")).toBe(true); }); test("should capture dictionary variable modifications", () => { const code = ` my_dict = {"a": 1, "b": 2} my_dict["c"] = 3 my_dict.update({"d": 4}) my_dict |= {"e": 5} del my_dict["a"] my_dict = {**my_dict, "f": 6} `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("dict_mod_test.py", { path: "dict_mod_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("dict_mod_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // There should be only one symbol for my_dict const myDict = variableSymbols.find((s) => s.id === "my_dict"); expect(myDict).toBeDefined(); // Should have at least the initial assignment, |= operation, and reassignment // Method calls like update and subscript assignments aren't captured as direct assignments expect(myDict?.nodes.length).toBeGreaterThanOrEqual(3); // Verify that key operations are included const nodeCaptured = (text: string) => myDict?.nodes.some((node) => node.text.includes(text)); expect(nodeCaptured('my_dict = {"a": 1, "b": 2}')).toBe(true); expect(nodeCaptured('my_dict |= {"e": 5}')).toBe(true); expect(nodeCaptured('my_dict = {**my_dict, "f": 6}')).toBe(true); }); test("should capture object attribute modifications", () => { const code = ` class Person: pass person = Person() person.name = "John" person.age = 30 person = Person() person.name = "Jane" `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("object_mod_test.py", { path: "object_mod_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("object_mod_test.py"); // There should be a class and a variable const personClass = result.symbols.find( (s) => s.id === "Person" && s.type === PYTHON_CLASS_TYPE, ); expect(personClass).toBeDefined(); const personVar = result.symbols.find( (s) => s.id === "person" && s.type === PYTHON_VARIABLE_TYPE, ); expect(personVar).toBeDefined(); // Should have at least the two assignments to person // Attribute assignments (person.name, person.age) aren't captured as direct variable assignments expect(personVar?.nodes.length).toBeGreaterThanOrEqual(2); // Verify that direct assignments to the person variable are included const nodeCaptured = (text: string) => personVar?.nodes.some((node) => node.text.includes(text)); expect(nodeCaptured("person = Person()")).toBe(true); }); test("should ignore variable modifications inside functions", () => { const code = ` # Module-level variable counter = 0 def increment(): # Local variable with same name as module-level one counter = 0 counter += 1 return counter # Module-level modification counter += 10 def modify_global(): global counter # This modifies the module-level variable counter += 100 # Another module-level variable total = 50 `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("function_scope_test.py", { path: "function_scope_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("function_scope_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // There should be two variables: counter and total expect(variableSymbols.length).toBe(2); // The counter variable should have 2 nodes (initial assignment and module-level +=) const counter = variableSymbols.find((s) => s.id === "counter"); expect(counter).toBeDefined(); expect(counter?.nodes.length).toBe(2); // Verify that only module-level operations are included, not the ones inside functions const counterNodeTexts = counter?.nodes.map((node) => node.text.trim()); expect(counterNodeTexts).toContain("counter = 0"); expect(counterNodeTexts).toContain("counter += 10"); // The counter += 1 inside increment() should not be captured const hasLocalIncrement = counter?.nodes.some( (node) => node.text.includes("counter += 1") && node.parent?.parent?.type === "function_definition", ); expect(hasLocalIncrement).toBeFalsy(); // The counter += 100 inside modify_global() should not be captured // even though it modifies the global variable const hasGlobalModification = counter?.nodes.some((node) => node.text.includes("counter += 100") ); expect(hasGlobalModification).toBeFalsy(); // The total variable should have 1 node const total = variableSymbols.find((s) => s.id === "total"); expect(total).toBeDefined(); expect(total?.nodes.length).toBe(1); }); test("should ignore variable modifications inside classes", () => { const code = ` # Module-level variable data = [] class DataProcessor: # Class variable with same name data = {} def __init__(self): # Instance variable self.data = [] def process(self): # Local variable data = [] data.append("processed") return data @classmethod def reset(cls): # Modifying class variable cls.data = {} # Module-level modification data.append("module") data = data + ["level"] # Another module-level variable config = {} `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("class_scope_test.py", { path: "class_scope_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("class_scope_test.py"); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); // There should be two variables: data and config expect(variableSymbols.length).toBe(2); // The data variable should have 2 nodes (initial assignment and module-level reassignment) const data = variableSymbols.find((s) => s.id === "data"); expect(data).toBeDefined(); expect(data?.nodes.length).toBeGreaterThanOrEqual(2); // Verify that only module-level operations are included, not the ones inside the class const dataNodeTexts = data?.nodes.map((node) => node.text.trim()); expect(dataNodeTexts).toContain("data = []"); expect(dataNodeTexts).toContain('data = data + ["level"]'); // Class-level data = {} should not be captured const hasClassVariable = data?.nodes.some( (node) => node.text.includes("data = {}") && node.parent?.parent?.type === "class_definition", ); expect(hasClassVariable).toBeFalsy(); // The config variable should have 1 node const config = variableSymbols.find((s) => s.id === "config"); expect(config).toBeDefined(); expect(config?.nodes.length).toBe(1); }); test("should capture only top-level nodes in a complex nested structure", () => { const code = ` # Module-level variable count = 0 class Counter: # Class variable count = 0 def __init__(self, initial=0): # Instance variable self.count = initial def increment(self): # Local method operation count = 0 # Local variable shadowing count += 1 self.count += 1 @classmethod def increment_class(cls): # Class variable operation cls.count += 1 @staticmethod def process(): # Local function operation def nested(): # Nested function variable count = 100 count *= 2 return count return nested() # Another module-level function def operate(): # Local variable count = 5 # Nested function def nested_modifier(): nonlocal count count += 5 # Double-nested function def deep_nested(): # Local to deep_nested count = 1000 return count return deep_nested() nested_modifier() return count # Module-level modifications count += 1 count *= 2 `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("complex_nested_test.py", { path: "complex_nested_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("complex_nested_test.py"); // There should be one class, one function and one variable const classSymbols = result.symbols.filter( (s) => s.type === PYTHON_CLASS_TYPE, ); expect(classSymbols.length).toBe(1); const functionSymbols = result.symbols.filter( (s) => s.type === PYTHON_FUNCTION_TYPE, ); expect(functionSymbols.length).toBe(1); const variableSymbols = result.symbols.filter( (s) => s.type === PYTHON_VARIABLE_TYPE, ); expect(variableSymbols.length).toBe(1); // The count variable should have 3 nodes (initial assignment and two module-level modifications) const count = variableSymbols[0]; expect(count.id).toBe("count"); expect(count.nodes.length).toBe(3); // Verify that only module-level operations are included const countNodeTexts = count.nodes.map((node) => node.text.trim()); expect(countNodeTexts).toContain("count = 0"); expect(countNodeTexts).toContain("count += 1"); expect(countNodeTexts).toContain("count *= 2"); // No inner node should be included const hasNestedNodes = count.nodes.some((node) => { const parentTypes = []; let current = node.parent; while (current) { parentTypes.push(current.type); current = current.parent; } return ( parentTypes.includes("function_definition") || parentTypes.includes("class_definition") ); }); expect(hasNestedNodes).toBeFalsy(); }); test("should capture method calls and attribute assignments for app configuration", () => { const code = ` import os from celery import Celery os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Project.settings") app = Celery("Project") app.config_from_object("django.conf:settings", namespace="CELERY") app.conf.update(app.conf.get("CELERY_CONFIG")) app.autodiscover_tasks() app.conf.task_queue_max_priority = 10 app.conf.task_default_priority = 5 app.conf.beat_schedule = { "task1": { "task": "module.tasks.task1", "schedule": 60, }, "task2": { "task": "module.tasks.task2", "schedule": 300, }, } `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("celery_app_test.py", { path: "celery_app_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("celery_app_test.py"); const appSymbol = result.symbols.find((s) => s.id === "app"); // Verify app symbol exists and is captured correctly expect(appSymbol).toBeDefined(); expect(appSymbol?.type).toBe(PYTHON_VARIABLE_TYPE); // Should have multiple nodes for the app variable expect(appSymbol?.nodes.length).toBeGreaterThanOrEqual(6); // Initial definition + 5 modification statements // Verify that all app operations are included const nodeCaptured = (text: string) => appSymbol?.nodes.some((node) => node.text.includes(text)); // Initial assignment expect(nodeCaptured('app = Celery("Project")')).toBe(true); // Method calls expect(nodeCaptured("app.config_from_object")).toBe(true); expect(nodeCaptured("app.autodiscover_tasks()")).toBe(true); // Attribute assignments expect(nodeCaptured("app.conf.task_queue_max_priority = 10")).toBe(true); expect(nodeCaptured("app.conf.task_default_priority = 5")).toBe(true); expect(nodeCaptured("app.conf.beat_schedule = {")).toBe(true); }); test("should capture complex object initialization and modifications", () => { const code = ` from flask import Flask from flask_cors import CORS from config import Config server = Flask(__name__) server.config.from_object(Config) server.config.update({ "DEBUG": True, "SECRET_KEY": "dev-key", }) server.register_blueprint(api_bp, url_prefix="/api") CORS(server) server.logger.setLevel("INFO") server.config["MAX_CONTENT_LENGTH"] = 16 * 1024 * 1024 server.session_interface = CustomSessionInterface() server.route_map = { "home": "/", "dashboard": "/dashboard", "profile": "/profile", "settings": { "general": "/settings/general", "security": "/settings/security", "notifications": "/settings/notifications", } } @server.before_request def before_request(): pass if __name__ == "__main__": server.run(host="0.0.0.0", port=5000) `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("flask_app.py", { path: "flask_app.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("flask_app.py"); const serverSymbol = result.symbols.find((s) => s.id === "server"); // Verify server symbol exists and is a variable expect(serverSymbol).toBeDefined(); expect(serverSymbol?.type).toBe(PYTHON_VARIABLE_TYPE); // Should have multiple nodes for the server variable expect(serverSymbol?.nodes.length).toBeGreaterThan(5); // Check for specific operations const nodeCaptured = (text: string) => serverSymbol?.nodes.some((node) => node.text.includes(text)); // Initial declaration expect(nodeCaptured("server = Flask(__name__)")).toBe(true); // Method calls expect(nodeCaptured("server.config.from_object")).toBe(true); expect(nodeCaptured("server.config.update")).toBe(true); expect(nodeCaptured("server.register_blueprint")).toBe(true); // Attribute assignments expect(nodeCaptured("server.logger.setLevel")).toBe(true); // Subscript assignments should now be detected expect(nodeCaptured('server.config["MAX_CONTENT_LENGTH"]')).toBe(true); expect(nodeCaptured("server.session_interface =")).toBe(true); expect(nodeCaptured("server.route_map =")).toBe(true); }); test("should capture variable modifications through subscript operations", () => { const code = ` # Direct subscript operations data = {} data["key1"] = "value1" data["key2"] = "value2" # Nested subscript operations config = {"db": {}} config["db"]["host"] = "localhost" config["db"]["port"] = 5432 # Attribute + subscript operations class Storage: def __init__(self): self.items = {} storage = Storage() storage.items["item1"] = {"name": "Item 1", "price": 10} storage.items["item2"] = {"name": "Item 2", "price": 20} # Mixed operations options = {} options["debug"] = True options.update({"verbose": True}) options["logging"] = {"level": "info"} `; const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set("subscript_test.py", { path: "subscript_test.py", rootNode: tree.rootNode, }); const resolver = new PythonExportExtractor(parser, files); const result = resolver.getSymbols("subscript_test.py"); // Check data variable const dataSymbol = result.symbols.find((s) => s.id === "data"); expect(dataSymbol).toBeDefined(); expect(dataSymbol?.type).toBe(PYTHON_VARIABLE_TYPE); expect(dataSymbol?.nodes.length).toBeGreaterThan(1); // Check config variable const configSymbol = result.symbols.find((s) => s.id === "config"); expect(configSymbol).toBeDefined(); expect(configSymbol?.nodes.length).toBeGreaterThanOrEqual(1); // Check storage variable const storageSymbol = result.symbols.find((s) => s.id === "storage"); expect(storageSymbol).toBeDefined(); expect(storageSymbol?.nodes.length).toBeGreaterThanOrEqual(1); // Check options variable const optionsSymbol = result.symbols.find((s) => s.id === "options"); expect(optionsSymbol).toBeDefined(); expect(optionsSymbol?.nodes.length).toBeGreaterThan(1); // Check specific subscript operations were captured const nodeCaptured = ( symbol: { nodes: Parser.SyntaxNode[] } | undefined, text: string, ) => symbol?.nodes.some((node: Parser.SyntaxNode) => node.text.includes(text)); // Verify that subscript-related nodes were captured expect(nodeCaptured(dataSymbol, "data[")).toBe(true); expect(nodeCaptured(storageSymbol, "storage.items")).toBe(true); expect(nodeCaptured(optionsSymbol, "options[")).toBe(true); }); }); ================================================ FILE: src/languagePlugins/python/exportExtractor/index.ts ================================================ import Parser from "tree-sitter"; import { PYTHON_CLASS_TYPE, PYTHON_FUNCTION_TYPE, PYTHON_VARIABLE_TYPE, type PythonSymbol, type PythonSymbolType, } from "./types.ts"; /** * PythonExportExtractor extracts exported symbols from a Python source file using Tree-sitter. * * This class performs the following: * - Analyzes Python source code to detect top-level definitions. * - Supports both plain and decorated class and function definitions. * - Extracts top-level variable assignments (ignoring __all__). * - Additionally, detects the __all__ assignment to extract public symbol names. * - Combines all these patterns in a single Tree-sitter query. * - Uses caching to improve performance by avoiding redundant computation. * - Tracks all nodes for each symbol, including multiple definitions and modifications. */ export class PythonExportExtractor { private files: Map; private parser: Parser; private symbolQuery: Parser.Query; private cache = new Map< string, { symbols: PythonSymbol[]; publicSymbols: undefined | string[] } >(); // Cache results for efficiency constructor( parser: Parser, files: Map, ) { this.parser = parser; this.files = files; // This single query combines multiple patterns: // - Top-level class definitions (plain and decorated) with their name captured as @classIdentifier // - Top-level function definitions (plain and decorated) with their name captured as @functionIdentifier // - Top-level variable assignments (excluding __all__) with their identifier captured as @variableIdentifier // - Top-level variable modifications with their identifier captured as @variableModifier // - Attribute assignments (app.attr = value) // - Nested attribute assignments (app.conf.attr = value) // - Method calls (app.method()) // - Nested method calls (app.conf.update()) // - An assignment for __all__ where the left side is captured as @allIdentifier (ensuring it equals "__all__") // and the public element strings from the list are captured as @publicElement. this.symbolQuery = new Parser.Query( this.parser.getLanguage(), ` (module ([ ; Top-level classes (plain and decorated) (class_definition name: (identifier) @classIdentifier ) @class (decorated_definition definition: (class_definition name: (identifier) @classIdentifier ) ) @class ; Top-level functions (plain and decorated) (function_definition name: (identifier) @functionIdentifier ) @function (decorated_definition definition: (function_definition name: (identifier) @functionIdentifier ) ) @function ; Top-level variables (ignoring __all__) (expression_statement (assignment left: ( (identifier) @variableIdentifier (#not-eq? @variableIdentifier "__all__") ) ) ) @variable (expression_statement (assignment left: (pattern_list ( (identifier) @variableIdentifier (#not-eq? @variableIdentifier "__all__") ) ) ) ) @variable ; Top-level variable modifications (augmented assignments like +=, -=, etc.) (expression_statement (augmented_assignment left: (identifier) @variableModifier ) ) @variableMod ; Top-level variable modifications in regular assignments (reassignments) (expression_statement (assignment left: ( (identifier) @variableModifier (#not-eq? @variableModifier "__all__") ) ) ) @variableMod ; Attribute assignments (app.attr = value) (expression_statement (assignment left: (attribute object: (identifier) @variableModifier ) ) ) @variableMod ; Nested attribute assignments (app.conf.attr = value) (expression_statement (assignment left: (attribute object: (attribute object: (identifier) @variableModifier ) ) ) ) @variableMod ; Method calls (app.method()) (expression_statement (call function: (attribute object: (identifier) @variableModifier ) ) ) @variableMod ; Nested method calls (app.conf.update()) (expression_statement (call function: (attribute object: (attribute object: (identifier) @variableModifier ) ) ) ) @variableMod ; Subscript assignments (obj[key] = value) (expression_statement (assignment left: (subscript value: (identifier) @variableModifier ) ) ) @variableMod ; Nested subscript assignments (obj.attr[key] = value) (expression_statement (assignment left: (subscript value: (attribute object: (identifier) @variableModifier ) ) ) ) @variableMod ; __all__ assignment to capture public symbols (expression_statement (assignment left: (identifier) @allIdentifier (#eq? @allIdentifier "__all__") right: (list (string (string_content) @publicElement) ) ) ) ]) ) `, ); } /** * Retrieves all exported symbols from the specified file. * * This method performs the following: * - Runs the combined Tree-sitter query on the file's AST to extract: * - Top-level classes, functions, and variable definitions. * - Variable modifications at the top level. * - The __all__ assignment to obtain public symbol names. * - Constructs Symbol objects for each definition using: * - The outer node capture (e.g. @class, @function, or @variable). * - Its associated identifier capture (e.g. @classIdentifier, @functionIdentifier, or @variableIdentifier). * - Tracks all nodes for each symbol, including multiple definitions and modifications. * - Caches the result to prevent redundant parsing. * * @param filePath The path of the Python file to analyze. * @returns An object with two properties: * - symbols: Array of extracted Symbol objects. * - publicSymbols: Array of public symbol names from __all__, or undefined if not defined. */ public getSymbols(filePath: string) { const cacheKey = `${filePath}|symbols`; // Return cached result if available const cacheValue = this.cache.get(cacheKey); if (cacheValue) { return cacheValue; } // Ensure the file exists in the provided map const file = this.files.get(filePath); if (!file) { throw new Error(`File not found: ${filePath}`); } // Use a map to collect all nodes for each symbol const symbolsMap = new Map< string, { id: string; type: PythonSymbolType; nodes: Parser.SyntaxNode[]; identifierNode: Parser.SyntaxNode; } >(); let publicSymbols: string[] | undefined = undefined; // Execute the combined query on the AST const matches = this.symbolQuery.matches(file.rootNode); matches.forEach(({ captures }) => { let symbolType: PythonSymbolType | undefined; let symbolNode: Parser.SyntaxNode | undefined; let identifierNode: Parser.SyntaxNode | undefined; let isModification = false; captures.forEach((capture) => { // If the capture name indicates an outer definition, // record its node and determine its type. if ( ["class", "function", "variable", "variableMod"].includes( capture.name, ) ) { symbolNode = capture.node; if (capture.name === "class") { symbolType = PYTHON_CLASS_TYPE; } else if (capture.name === "function") { symbolType = PYTHON_FUNCTION_TYPE; } else if ( capture.name === "variable" || capture.name === "variableMod" ) { symbolType = PYTHON_VARIABLE_TYPE; isModification = capture.name === "variableMod"; } } // If the capture is an identifier for the definition or modification, // store it as the identifier node. if ( [ "classIdentifier", "functionIdentifier", "variableIdentifier", "variableModifier", ].includes(capture.name) ) { identifierNode = capture.node; } // Capture public symbol elements from the __all__ assignment. if (capture.name === "publicElement") { if (!publicSymbols) { publicSymbols = [capture.node.text]; } else { publicSymbols.push(capture.node.text); } } }); // When we have all parts of a symbol, update the symbols map if (symbolType && symbolNode && identifierNode) { const symbolId = identifierNode.text; if (symbolsMap.has(symbolId)) { // If this symbol already exists, add this node to its nodes array const existingSymbol = symbolsMap.get(symbolId); // For classes, functions, and variable modifications if (existingSymbol) { // Check if this node is already in the array (to avoid duplicates) const isDuplicate = existingSymbol.nodes.some( (node) => node.startPosition.row === symbolNode?.startPosition.row && node.startPosition.column === symbolNode?.startPosition.column, ); if (!isDuplicate) { // Only add modifications to existing variable symbols, not creating a new variable symbol if (isModification && symbolType === PYTHON_VARIABLE_TYPE) { existingSymbol.nodes.push(symbolNode); } // For classes and functions with multiple definitions, add each occurrence else if ( !isModification && (symbolType === PYTHON_CLASS_TYPE || symbolType === PYTHON_FUNCTION_TYPE) ) { existingSymbol.nodes.push(symbolNode); } // For non-modification variable assignments, if the symbol already exists, // add the node to track reassignments else if (!isModification && symbolType === PYTHON_VARIABLE_TYPE) { existingSymbol.nodes.push(symbolNode); } } } } else { // If it's a new symbol or not a modification, add it to the map if (!isModification) { symbolsMap.set(symbolId, { id: symbolId, type: symbolType, nodes: [symbolNode], identifierNode, }); } } } }); // Convert the map to an array of symbols const symbols: PythonSymbol[] = Array.from(symbolsMap.values()); const result = { symbols, publicSymbols, }; // Cache the result for future lookups. this.cache.set(cacheKey, result); return result; } } ================================================ FILE: src/languagePlugins/python/exportExtractor/types.ts ================================================ import type Parser from "tree-sitter"; /** * Constants for Python symbol types */ export const PYTHON_CLASS_TYPE = "class"; export const PYTHON_FUNCTION_TYPE = "function"; export const PYTHON_VARIABLE_TYPE = "variable"; /** * Represents the types of symbols that can be exported from a Python module. * * - class: A Python class definition * - function: A Python function definition * - variable: A Python variable assignment at module level */ export type PythonSymbolType = | typeof PYTHON_CLASS_TYPE | typeof PYTHON_FUNCTION_TYPE | typeof PYTHON_VARIABLE_TYPE; /** * Represents an exported symbol in a Python module. * Exported symbols include top-level classes, functions, and variables. * * This interface captures not only the symbol's identifier (name) but also * its AST nodes for detailed analysis. */ export interface PythonSymbol { /** The identifier (name) of the symbol */ id: string; /** The full AST nodes for the symbol definition */ nodes: Parser.SyntaxNode[]; /** The AST node for just the identifier part of the symbol */ identifierNode: Parser.SyntaxNode; /** The type of the symbol (class, function, or variable) */ type: PythonSymbolType; } ================================================ FILE: src/languagePlugins/python/importExtractor/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { PythonImportExtractor } from "./index.ts"; import { FROM_IMPORT_STATEMENT_TYPE, type ImportItem, NORMAL_IMPORT_STATEMENT_TYPE, } from "./types.ts"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; describe("Python Import Extractor", () => { const parser = pythonParser; test("should extract multiple simple normal import statements", () => { const importExtractor = new PythonImportExtractor( parser, new Map([ [ "file.py", { path: "file.py", rootNode: parser.parse(` import os import sys `).rootNode, }, ], ]), ); const importStatements = importExtractor.getImportStatements("file.py"); expect(importStatements).toHaveLength(2); const firstImportStatement = importStatements[0]; expect(firstImportStatement.type).toEqual(NORMAL_IMPORT_STATEMENT_TYPE); expect(firstImportStatement.node.text).toEqual("import os"); expect(firstImportStatement.members).toHaveLength(1); const firstImportStatementMember = firstImportStatement.members[0]; expect(firstImportStatementMember.isWildcardImport).toEqual(false); expect(firstImportStatementMember.items).toBeUndefined(); expect(firstImportStatementMember.node.text).toEqual("os"); expect(firstImportStatementMember.identifierNode.text).toEqual("os"); expect(firstImportStatementMember.aliasNode).toBeUndefined(); const secondImportStatement = importStatements[1]; expect(secondImportStatement.type).toEqual(NORMAL_IMPORT_STATEMENT_TYPE); expect(secondImportStatement.node.text).toEqual("import sys"); expect(secondImportStatement.members).toHaveLength(1); const secondImportStatementMember = secondImportStatement.members[0]; expect(secondImportStatementMember.isWildcardImport).toEqual(false); expect(secondImportStatementMember.items).toBeUndefined(); expect(secondImportStatementMember.node.text).toEqual("sys"); expect(secondImportStatementMember.identifierNode.text).toEqual("sys"); expect(secondImportStatementMember.aliasNode).toBeUndefined(); }); test("should extract aliased normal import statements", () => { const importExtractor = new PythonImportExtractor( parser, new Map([ [ "file.py", { path: "file.py", rootNode: parser.parse("import os as operating_system").rootNode, }, ], ]), ); const importStatements = importExtractor.getImportStatements("file.py"); expect(importStatements).toHaveLength(1); const importStatement = importStatements[0]; expect(importStatement.type).toEqual(NORMAL_IMPORT_STATEMENT_TYPE); expect(importStatement.members).toHaveLength(1); const importStatementMember = importStatement.members[0]; expect(importStatementMember.isWildcardImport).toEqual(false); expect(importStatementMember.items).toBeUndefined(); expect(importStatementMember.node.text).toEqual("os as operating_system"); expect(importStatementMember.identifierNode.text).toEqual("os"); expect(importStatementMember.aliasNode?.text).toEqual("operating_system"); }); test("should extract multilple from import statements", () => { const importExtractor = new PythonImportExtractor( parser, new Map([ [ "file.py", { path: "file.py", rootNode: parser.parse(` from os import path, environ from sys import argv `).rootNode, }, ], ]), ); const importStatements = importExtractor.getImportStatements("file.py"); expect(importStatements).toHaveLength(2); const firstImportStatement = importStatements[0]; expect(firstImportStatement.type).toEqual(FROM_IMPORT_STATEMENT_TYPE); expect(firstImportStatement.node.text).toEqual( "from os import path, environ", ); expect(firstImportStatement.members).toHaveLength(1); const firstImportStatementMember = firstImportStatement.members[0]; expect(firstImportStatementMember.isWildcardImport).toEqual(false); expect(firstImportStatementMember.node.text).toEqual("os"); expect(firstImportStatementMember.identifierNode.text).toEqual("os"); expect(firstImportStatementMember.aliasNode).toBeUndefined(); expect(firstImportStatementMember.items).toHaveLength(2); const firstImportStatementMemberFirstSymbol = ( firstImportStatementMember.items as ImportItem[] )[0]; expect(firstImportStatementMemberFirstSymbol.node.text).toEqual("path"); expect(firstImportStatementMemberFirstSymbol.identifierNode.text).toEqual( "path", ); expect(firstImportStatementMemberFirstSymbol.aliasNode).toBeUndefined(); const firstImportStatementMemberSecondSymbol = ( firstImportStatementMember.items as ImportItem[] )[1]; expect(firstImportStatementMemberSecondSymbol.node.text).toEqual("environ"); expect(firstImportStatementMemberSecondSymbol.identifierNode.text).toEqual( "environ", ); expect(firstImportStatementMemberSecondSymbol.aliasNode).toBeUndefined(); const secondImportStatement = importStatements[1]; expect(secondImportStatement.type).toEqual(FROM_IMPORT_STATEMENT_TYPE); expect(secondImportStatement.node.text).toEqual("from sys import argv"); expect(secondImportStatement.members).toHaveLength(1); const secondImportStatementMember = secondImportStatement.members[0]; expect(secondImportStatementMember.isWildcardImport).toEqual(false); expect(secondImportStatementMember.node.text).toEqual("sys"); expect(secondImportStatementMember.identifierNode.text).toEqual("sys"); expect(secondImportStatementMember.aliasNode).toBeUndefined(); expect(secondImportStatementMember.items).toHaveLength(1); const secondImportStatementMemberSymbol = ( secondImportStatementMember.items as ImportItem[] )[0]; expect(secondImportStatementMemberSymbol.node.text).toEqual("argv"); expect(secondImportStatementMemberSymbol.node.text).toEqual("argv"); expect(secondImportStatementMemberSymbol.aliasNode).toBeUndefined(); }); test("should extract from import statements with aliases", () => { const importExtractor = new PythonImportExtractor( parser, new Map([ [ "file.py", { path: "file.py", rootNode: parser.parse("from os import path as os_path").rootNode, }, ], ]), ); const importStatements = importExtractor.getImportStatements("file.py"); expect(importStatements).toHaveLength(1); const importStatement = importStatements[0]; expect(importStatement.type).toEqual(FROM_IMPORT_STATEMENT_TYPE); expect(importStatement.node.text).toEqual( "from os import path as os_path", ); expect(importStatement.members).toHaveLength(1); const importStatementMember = importStatement.members[0]; expect(importStatementMember.isWildcardImport).toEqual(false); expect(importStatementMember.node.text).toEqual("os"); expect(importStatementMember.identifierNode.text).toEqual("os"); expect(importStatementMember.aliasNode).toBeUndefined(); expect(importStatementMember.items).toHaveLength(1); const importStatementMemberSymbol = ( importStatementMember.items as ImportItem[] )[0]; expect(importStatementMemberSymbol.node.text).toEqual("path as os_path"); expect(importStatementMemberSymbol.identifierNode.text).toEqual("path"); expect(importStatementMemberSymbol.aliasNode?.text).toEqual("os_path"); }); test("should extract from import statements with wildcard imports", () => { const importExtractor = new PythonImportExtractor( parser, new Map([ [ "file.py", { path: "file.py", rootNode: parser.parse("from os import *").rootNode, }, ], ]), ); const importStatements = importExtractor.getImportStatements("file.py"); expect(importStatements).toHaveLength(1); const importStatement = importStatements[0]; expect(importStatement.type).toEqual(FROM_IMPORT_STATEMENT_TYPE); expect(importStatement.node.text).toEqual("from os import *"); expect(importStatement.members).toHaveLength(1); const importStatementMember = importStatement.members[0]; expect(importStatementMember.isWildcardImport).toEqual(true); expect(importStatementMember.items).toBeUndefined(); expect(importStatementMember.node.text).toEqual("os"); expect(importStatementMember.identifierNode.text).toEqual("os"); expect(importStatementMember.aliasNode).toBeUndefined(); }); test("should handle mix of normal and from import statements", () => { const importExtractor = new PythonImportExtractor( parser, new Map([ [ "file.py", { path: "file.py", rootNode: parser.parse(` import os from sys import argv `).rootNode, }, ], ]), ); const importStatements = importExtractor.getImportStatements("file.py"); expect(importStatements).toHaveLength(2); const firstImportStatement = importStatements[0]; expect(firstImportStatement.type).toEqual(NORMAL_IMPORT_STATEMENT_TYPE); expect(firstImportStatement.node.text).toEqual("import os"); expect(firstImportStatement.members).toHaveLength(1); const secondImportStatement = importStatements[1]; expect(secondImportStatement.type).toEqual(FROM_IMPORT_STATEMENT_TYPE); expect(secondImportStatement.node.text).toEqual("from sys import argv"); expect(secondImportStatement.members).toHaveLength(1); }); }); ================================================ FILE: src/languagePlugins/python/importExtractor/index.ts ================================================ import Parser from "tree-sitter"; import { FROM_IMPORT_STATEMENT_TYPE, type ImportMember, type ImportStatement, NORMAL_IMPORT_STATEMENT_TYPE, } from "./types.ts"; /** * PythonImportExtractor parses and extracts Python import statements (normal and from-import). * * It specifically does the following: * - Parses Python files using Tree-sitter. * - Identifies standard import statements (`import module`) and extracts their identifiers and aliases. * - Identifies from-import statements (`from module import X`) and extracts module names, imported items, aliases, and wildcard imports. * - Caches results to optimize performance on subsequent calls. * * Dependencies (assumed provided externally): * - Tree-sitter parser for AST parsing. * - A map of files with their paths and parsed AST root nodes. * * Usage example: * ```typescript * // Get import statements for a Python file * const importStatements = importExtractor.getImportStatements(filePath); * // Process them to understand imports and dependencies * for (const stmt of importStatements) { * if (stmt.type === NORMAL_IMPORT_STATEMENT_TYPE) { * // Handle regular imports * } else { * // Handle from-imports * } * } * ``` */ export class PythonImportExtractor { /** * Map of file paths to their parsed AST root nodes * Used to access file content for analysis */ private files: Map; /** * Tree-sitter parser for Python code * Used to parse import statements */ private parser: Parser; /** * Tree-sitter query to find import statements in Python code * This identifies both normal imports and from-imports */ private importQuery: Parser.Query; /** * Cache for import statements * Maps file paths to their parsed import statements * Used to avoid reprocessing files already analyzed */ private cache = new Map(); /** * Constructs a new PythonImportExtractor. * * @param parser - A Tree-sitter parser instance for Python. * @param files - A map of file paths to objects containing their AST root nodes. */ constructor( parser: Parser, files: Map, ) { this.parser = parser; this.files = files; // Single query to capture all import statements this.importQuery = new Parser.Query( this.parser.getLanguage(), `[ (import_statement) @import (import_from_statement) @import ]`, ); } /** * Extracts all import statements (both normal and from-import) from the specified file. * Uses caching to optimize repeated calls. * * @param filePath - Path to the Python file being analyzed. * @returns An array of resolved ImportStatement objects for the given file. */ public getImportStatements(filePath: string): ImportStatement[] { const cachedValue = this.cache.get(filePath); if (cachedValue) { return cachedValue; } const file = this.files.get(filePath); if (!file) { console.error(`File ${filePath} not found in files map`); return []; } const importStatements: ImportStatement[] = []; // Process all matched import statements this.importQuery.captures(file.rootNode).forEach(({ node }) => { if (node.type === "import_statement") { importStatements.push(this.processNormalImport(node)); } else if (node.type === "import_from_statement") { importStatements.push(this.processFromImport(node)); } }); this.cache.set(filePath, importStatements); return importStatements; } /** * Processes a normal import statement (`import module`) by manually extracting * its components from the AST. * * @param node - The import_statement syntax node * @returns A resolved ImportStatement object */ private processNormalImport(node: Parser.SyntaxNode): ImportStatement { const importStatement: ImportStatement = { node, type: NORMAL_IMPORT_STATEMENT_TYPE, sourceNode: undefined, members: [], }; // Retrieve imported modules and optional aliases const memberNodes = node.childrenForFieldName("name"); memberNodes.forEach((memberNode) => { let identifierNode: Parser.SyntaxNode; let aliasNode: Parser.SyntaxNode | undefined; if (memberNode.type === "aliased_import") { const nameNode = memberNode.childForFieldName("name"); if (!nameNode) { throw new Error("Malformed aliased import: missing name"); } identifierNode = nameNode; aliasNode = memberNode.childForFieldName("alias") || undefined; } else { identifierNode = memberNode; aliasNode = undefined; } importStatement.members.push({ node: memberNode, identifierNode, aliasNode, isWildcardImport: false, items: undefined, }); }); return importStatement; } /** * Processes a from-import statement (`from module import X`) by manually extracting * its components from the AST. * * @param node - The import_from_statement syntax node * @returns A resolved ImportStatement object */ private processFromImport(node: Parser.SyntaxNode): ImportStatement { const sourceNode = node.childForFieldName("module_name"); if (!sourceNode) { throw new Error("Malformed from-import: missing module name"); } const importStatement: ImportStatement = { node, type: FROM_IMPORT_STATEMENT_TYPE, sourceNode, members: [], }; const importMember: ImportMember = { node: sourceNode, identifierNode: sourceNode, aliasNode: undefined, isWildcardImport: false, items: undefined, }; const wildcardNode = node.descendantsOfType("wildcard_import")[0]; if (wildcardNode) { importMember.isWildcardImport = true; } else { const itemNodes = node.childrenForFieldName("name"); importMember.items = itemNodes.map((itemNode) => { let identifierNode: Parser.SyntaxNode; let aliasNode: Parser.SyntaxNode | undefined; if (itemNode.type === "aliased_import") { const nameNode = itemNode.childForFieldName("name"); if (!nameNode) { throw new Error("Malformed aliased import item: missing name"); } identifierNode = nameNode; aliasNode = itemNode.childForFieldName("alias") || undefined; } else { identifierNode = itemNode; aliasNode = undefined; } return { node: itemNode, identifierNode, aliasNode, }; }); } importStatement.members.push(importMember); return importStatement; } } ================================================ FILE: src/languagePlugins/python/importExtractor/types.ts ================================================ import type Parser from "tree-sitter"; /** * Represents an explicitly imported item within an import statement. * This item can be a class, function, module, or any valid Python identifier. * * Example: * - In "from os import path as p", the item would be "path" with alias "p" * - In "from os import path", the item would be "path" without an alias */ export interface ImportItem { /** The syntax node corresponding to the entire imported item (including alias, if present). */ node: Parser.SyntaxNode; /** The syntax node for the item's original identifier (e.g., "path" in "from os import path as p"). */ identifierNode: Parser.SyntaxNode; /** The syntax node for the item's alias, if one is provided (e.g., "p" in "from os import path as p"). */ aliasNode: Parser.SyntaxNode | undefined; } /** * Represents a member imported from a module. A member corresponds to either: * - A module imported entirely (`import module` or `import module as alias`). * - A module or symbol imported from another module (`from module import X` or `from module import *`). * * This is the core structure that represents what's being imported and how. */ export interface ImportMember { /** The syntax node corresponding to the imported member (module or item). */ node: Parser.SyntaxNode; /** The syntax node corresponding to the member's identifier. */ identifierNode: Parser.SyntaxNode; /** The syntax node for the member's alias, if provided (e.g., "alias" in "import module as alias"). */ aliasNode: Parser.SyntaxNode | undefined; /** Indicates if this is a wildcard import (`from module import *`). */ isWildcardImport: boolean; /** * The list of explicitly imported items from this member. * - Undefined if the import is a wildcard import (`from module import *`). * - Undefined if the import is a standard import statement (`import module`). * - Contains ImportItem objects for each imported symbol in a from-import statement. */ items?: ImportItem[]; } /** * Constants representing the two types of Python import statements */ export const NORMAL_IMPORT_STATEMENT_TYPE = "normal"; export const FROM_IMPORT_STATEMENT_TYPE = "from"; /** * Defines the possible types of Python import statements: * - "normal": Standard imports like "import os" or "import os as operating_system" * - "from": From-imports like "from os import path" or "from os import *" */ export type PythonImportStatementType = | typeof NORMAL_IMPORT_STATEMENT_TYPE | typeof FROM_IMPORT_STATEMENT_TYPE; /** * Represents a fully resolved import statement from a Python source file. * It abstracts both normal (`import module`) and from-import (`from module import X`) statements. * * This structure contains all the necessary information to analyze import relationships * and symbol dependencies between Python modules. */ export interface ImportStatement { /** The syntax node representing the entire import statement. */ node: Parser.SyntaxNode; /** The type of import statement: either "normal" or "from". */ type: PythonImportStatementType; /** * The syntax node representing the source module in a from-import statement (`from module import ...`). * - Undefined for standard import statements (`import module`). */ sourceNode: Parser.SyntaxNode | undefined; /** The list of imported members or modules. */ members: ImportMember[]; } ================================================ FILE: src/languagePlugins/python/itemResolver/index.test.ts ================================================ import { beforeEach, describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type Parser from "tree-sitter"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; import { PythonExportExtractor } from "../exportExtractor/index.ts"; import { PythonImportExtractor } from "../importExtractor/index.ts"; import { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PythonItemResolver } from "./index.ts"; import { PYTHON_INTERNAL_MODULE_TYPE } from "./types.ts"; /** * These tests verify the Python symbol resolution system, which handles: * 1. Basic symbol resolution through direct and nested imports * 2. Wildcard import behavior (from module import *) * 3. Alias handling (import x as y) * 4. Python's __all__ directive for controlling exported symbols * 5. Handling of private symbols (with _ prefix) * 6. Multiple import styles and their precedence * 7. Symbol visibility and shadowing rules * 8. Circular imports */ describe("PythonItemResolver", () => { let resolver: PythonItemResolver; let moduleResolver: PythonModuleResolver; let exportExtractor: PythonExportExtractor; let importExtractor: PythonImportExtractor; let files: Map; beforeEach(() => { // Setup basic test modules files = new Map([ // TEST CASE 1: Shadowing in nested imports // Module with original definition [ "shadow_nest_1.py", { path: "shadow_nest_1.py", rootNode: pythonParser.parse(` def shadow_func(): return "original" `).rootNode, }, ], // Module that imports and overrides with local definition [ "shadow_nest_2.py", { path: "shadow_nest_2.py", rootNode: pythonParser.parse(` from shadow_nest_1 import shadow_func def shadow_func(): return "override" # This local definition should shadow the import `).rootNode, }, ], // Module that imports from the module with shadowing [ "shadow_nest_user.py", { path: "shadow_nest_user.py", rootNode: pythonParser.parse(` from shadow_nest_2 import shadow_func `).rootNode, }, ], // TEST CASE 2: Importing submodules from packages with qualified names [ "deep_package/__init__.py", { path: "deep_package/__init__.py", rootNode: pythonParser.parse(` # Empty init file `).rootNode, }, ], [ "deep_package/submod.py", { path: "deep_package/submod.py", rootNode: pythonParser.parse(` def submod_func(): pass `).rootNode, }, ], [ "import_submodule.py", { path: "import_submodule.py", rootNode: pythonParser.parse(` # Import the submodule directly import deep_package.submod # Use the submodule qualified name def use_func(): return deep_package.submod.submod_func() `).rootNode, }, ], // TEST CASE 3: Deep package hierarchies with multiple layers of __all__ inheritance [ "nested_all_pkg/__init__.py", { path: "nested_all_pkg/__init__.py", rootNode: pythonParser.parse(` from .level1 import * from .direct import direct_func __all__ = ['direct_func', 'level1_func', 'level2_deep_func'] `).rootNode, }, ], [ "nested_all_pkg/direct.py", { path: "nested_all_pkg/direct.py", rootNode: pythonParser.parse(` def direct_func(): pass def not_exported(): pass `).rootNode, }, ], [ "nested_all_pkg/level1.py", { path: "nested_all_pkg/level1.py", rootNode: pythonParser.parse(` from .level2 import * def level1_func(): pass def level1_hidden(): pass __all__ = ['level1_func', 'level2_deep_func'] `).rootNode, }, ], [ "nested_all_pkg/level2.py", { path: "nested_all_pkg/level2.py", rootNode: pythonParser.parse(` def level2_func(): pass def level2_deep_func(): pass def level2_hidden(): pass __all__ = ['level2_func', 'level2_deep_func'] `).rootNode, }, ], [ "use_nested_all.py", { path: "use_nested_all.py", rootNode: pythonParser.parse(` from nested_all_pkg import * `).rootNode, }, ], // Basic modules with symbols [ "moduleA.py", { path: "moduleA.py", rootNode: pythonParser.parse(` def foo(): pass def bar(): pass CONSTANT = 42 `).rootNode, }, ], [ "moduleB.py", { path: "moduleB.py", rootNode: pythonParser.parse(`from moduleA import foo as f, bar`) .rootNode, }, ], // Add test module for testing conflicting imports [ "override_imports.py", { path: "override_imports.py", rootNode: pythonParser.parse(` from moduleA import foo # foo from moduleA from multi_wildcard1 import common as foo # This should override the previous import `).rootNode, }, ], // Modules for testing different import styles [ "moduleC.py", { path: "moduleC.py", rootNode: pythonParser.parse(`from moduleB import *`).rootNode, }, ], [ "moduleD.py", { path: "moduleD.py", rootNode: pythonParser.parse(` from moduleB import f as fooAlias from moduleA import bar as barAlias `).rootNode, }, ], [ "moduleE.py", { path: "moduleE.py", rootNode: pythonParser.parse(`import moduleA`).rootNode, }, ], [ "moduleF.py", { path: "moduleF.py", rootNode: pythonParser.parse(` from moduleA import foo def local_func(): pass `).rootNode, }, ], [ "moduleG.py", { path: "moduleG.py", rootNode: pythonParser.parse(` import moduleF from moduleF import local_func as alias_local `).rootNode, }, ], // Modules for import order and precedence testing [ "precedence.py", { path: "precedence.py", rootNode: pythonParser.parse(` # Define a local symbol def duplicate(): return "local" # Import a symbol with same name - this should be shadowed from moduleA import CONSTANT as duplicate # Wildcard import - should not override existing names from moduleB import * `).rootNode, }, ], [ "wildcard_precedence.py", { path: "wildcard_precedence.py", rootNode: pythonParser.parse(` # First wildcard import from moduleA import * # Second wildcard import from moduleB import * # Third explicit import - should override any previous wildcards from moduleA import foo as bar `).rootNode, }, ], // Circular import test [ "circular1.py", { path: "circular1.py", rootNode: pythonParser.parse(` def circular1_func(): pass from circular2 import circular2_func `).rootNode, }, ], [ "circular2.py", { path: "circular2.py", rootNode: pythonParser.parse(` def circular2_func(): pass from circular1 import circular1_func `).rootNode, }, ], // Nested package test [ "package/__init__.py", { path: "package/__init__.py", rootNode: pythonParser.parse(` from .submodule import sub_func from .intermediate import * `).rootNode, }, ], [ "package/intermediate.py", { path: "package/intermediate.py", rootNode: pythonParser.parse(` def intermediate_func(): pass from .submodule import * `).rootNode, }, ], [ "package/submodule.py", { path: "package/submodule.py", rootNode: pythonParser.parse(` def sub_func(): pass def deep_nested_func(): pass `).rootNode, }, ], [ "usePackage.py", { path: "usePackage.py", rootNode: pythonParser.parse(` from package import * # This should be available from the wildcard import deep_nested_func() `).rootNode, }, ], // __all__ directive test modules [ "moduleWithAll.py", { path: "moduleWithAll.py", rootNode: pythonParser.parse(` def public_func(): pass def another_func(): pass def _private_func(): pass __all__ = ['public_func'] `).rootNode, }, ], [ "importAllModule.py", { path: "importAllModule.py", rootNode: pythonParser.parse(` from moduleWithAll import * `).rootNode, }, ], [ "importSpecificFromAll.py", { path: "importSpecificFromAll.py", rootNode: pythonParser.parse(` from moduleWithAll import another_func, _private_func `).rootNode, }, ], // Private symbol handling [ "privateSymbols.py", { path: "privateSymbols.py", rootNode: pythonParser.parse(` def public_function(): pass def _private_function(): pass _PRIVATE_CONSTANT = 123 PUBLIC_CONSTANT = 456 `).rootNode, }, ], [ "importPrivate.py", { path: "importPrivate.py", rootNode: pythonParser.parse(` from privateSymbols import * # Should only import public symbols through wildcard `).rootNode, }, ], [ "explicitPrivate.py", { path: "explicitPrivate.py", rootNode: pythonParser.parse(` from privateSymbols import _private_function, PUBLIC_CONSTANT # Explicit imports can include private symbols `).rootNode, }, ], // Complex __all__ overriding private convention [ "allOverride.py", { path: "allOverride.py", rootNode: pythonParser.parse(` def regular_func(): pass def _private_func(): pass # Explicitly export a private symbol through __all__ __all__ = ['regular_func', '_private_func'] `).rootNode, }, ], [ "importAllOverride.py", { path: "importAllOverride.py", rootNode: pythonParser.parse(` from allOverride import * # Should import both regular_func and _private_func because they're in __all__ `).rootNode, }, ], // Namespace packages [ "namespace_pkg/module1.py", { path: "namespace_pkg/module1.py", rootNode: pythonParser.parse(` def ns_func1(): pass `).rootNode, }, ], [ "namespace_pkg/module2.py", { path: "namespace_pkg/module2.py", rootNode: pythonParser.parse(` def ns_func2(): pass from .module1 import ns_func1 `).rootNode, }, ], // Absolute vs relative imports [ "package2/__init__.py", { path: "package2/__init__.py", rootNode: pythonParser.parse(` from .relmod import rel_func absolute_var = "from init" `).rootNode, }, ], [ "package2/relmod.py", { path: "package2/relmod.py", rootNode: pythonParser.parse(` def rel_func(): pass from . import absolute_var `).rootNode, }, ], [ "package2/absmod.py", { path: "package2/absmod.py", rootNode: pythonParser.parse(` def abs_func(): pass from package2 import absolute_var `).rootNode, }, ], // Multiple wildcard imports [ "multi_wildcard1.py", { path: "multi_wildcard1.py", rootNode: pythonParser.parse(` def unique1(): pass def common(): return "from1" `).rootNode, }, ], [ "multi_wildcard2.py", { path: "multi_wildcard2.py", rootNode: pythonParser.parse(` def unique2(): pass def common(): return "from2" `).rootNode, }, ], [ "multi_wildcards.py", { path: "multi_wildcards.py", rootNode: pythonParser.parse(` from multi_wildcard1 import * from multi_wildcard2 import * # Should have unique1, unique2, and common (from multi_wildcard1 since it comes first) `).rootNode, }, ], ]); // Initialize resolvers exportExtractor = new PythonExportExtractor(pythonParser, files); importExtractor = new PythonImportExtractor(pythonParser, files); moduleResolver = new PythonModuleResolver(new Set(files.keys()), "3.13"); resolver = new PythonItemResolver( exportExtractor, importExtractor, moduleResolver, ); }); // ======================================================== // SECTION: Advanced Import Scenarios and Shadowing // ======================================================== describe("Advanced Import Scenarios and Shadowing", () => { test("handles shadowing in nested imports", () => { const shadowNest2 = moduleResolver.getModuleFromFilePath( "shadow_nest_2.py", ); expect(shadowNest2).toBeDefined(); // When importing shadow_func, should get the local definition from shadow_nest_2, // not the one imported from shadow_nest_1 const directResult = resolver.resolveItem(shadowNest2, "shadow_func"); expect(directResult).toBeDefined(); expect(directResult?.module?.path).toBe("shadow_nest_2.py"); // Local definition, not from shadow_nest_1 // Test that another module importing from shadow_nest_2 gets the shadowed version const shadowUser = moduleResolver.getModuleFromFilePath( "shadow_nest_user.py", ); expect(shadowUser).toBeDefined(); const userResult = resolver.resolveItem(shadowUser, "shadow_func"); expect(userResult).toBeDefined(); expect(userResult?.module?.path).toBe("shadow_nest_2.py"); // Should get the shadowed version }); test("handles deep package hierarchies with multiple layers of __all__ inheritance", () => { const useNestedAll = moduleResolver.getModuleFromFilePath( "use_nested_all.py", ); expect(useNestedAll).toBeDefined(); // Should be able to resolve symbols listed in the top-level __all__ const directResult = resolver.resolveItem(useNestedAll, "direct_func"); expect(directResult).toBeDefined(); expect(directResult?.module?.path).toBe("nested_all_pkg/direct.py"); const level1Result = resolver.resolveItem(useNestedAll, "level1_func"); expect(level1Result).toBeDefined(); expect(level1Result?.module?.path).toBe("nested_all_pkg/level1.py"); const deepResult = resolver.resolveItem(useNestedAll, "level2_deep_func"); expect(deepResult).toBeDefined(); expect(deepResult?.module?.path).toBe("nested_all_pkg/level2.py"); // Should NOT import level2_func even though it's in level2's __all__ // but not in the top-level package __all__ const level2Result = resolver.resolveItem(useNestedAll, "level2_func"); expect(level2Result).toBeUndefined(); // Should NOT import any of the hidden functions const level1HiddenResult = resolver.resolveItem( useNestedAll, "level1_hidden", ); expect(level1HiddenResult).toBeUndefined(); const level2HiddenResult = resolver.resolveItem( useNestedAll, "level2_hidden", ); expect(level2HiddenResult).toBeUndefined(); const notExportedResult = resolver.resolveItem( useNestedAll, "not_exported", ); expect(notExportedResult).toBeUndefined(); }); }); // ======================================================== // SECTION: Basic Item Resolution // ======================================================== describe("Basic Item Resolution", () => { test("resolves symbols defined directly in a module", () => { const moduleA = moduleResolver.getModuleFromFilePath("moduleA.py"); expect(moduleA).toBeDefined(); const result = resolver.resolveItem(moduleA, "foo"); expect(result).toBeDefined(); expect(result?.type).toBe(PYTHON_INTERNAL_MODULE_TYPE); expect(result?.module?.path).toBe("moduleA.py"); expect(result?.symbol?.id).toBe("foo"); }); test("resolves symbols via explicit imports", () => { const moduleB = moduleResolver.getModuleFromFilePath("moduleB.py"); expect(moduleB).toBeDefined(); // Test alias resolution const aliasResult = resolver.resolveItem(moduleB, "f"); expect(aliasResult).toBeDefined(); expect(aliasResult?.type).toBe(PYTHON_INTERNAL_MODULE_TYPE); expect(aliasResult?.module?.path).toBe("moduleA.py"); expect(aliasResult?.symbol?.id).toBe("foo"); // Test direct import resolution const directResult = resolver.resolveItem(moduleB, "bar"); expect(directResult).toBeDefined(); expect(directResult?.type).toBe(PYTHON_INTERNAL_MODULE_TYPE); expect(directResult?.module?.path).toBe("moduleA.py"); expect(directResult?.symbol?.id).toBe("bar"); }); test("resolves symbols via wildcard imports", () => { const moduleC = moduleResolver.getModuleFromFilePath("moduleC.py"); expect(moduleC).toBeDefined(); // Test resolving a symbol that was imported via wildcard from moduleB const wildcardResult = resolver.resolveItem(moduleC, "f"); expect(wildcardResult).toBeDefined(); expect(wildcardResult?.type).toBe(PYTHON_INTERNAL_MODULE_TYPE); expect(wildcardResult?.module?.path).toBe("moduleA.py"); expect(wildcardResult?.symbol?.id).toBe("foo"); // Also test bar which was imported into moduleB and then wildcard imported into moduleC const anotherResult = resolver.resolveItem(moduleC, "bar"); expect(anotherResult).toBeDefined(); expect(anotherResult?.module?.path).toBe("moduleA.py"); expect(anotherResult?.symbol?.id).toBe("bar"); }); test("resolves module imports", () => { const moduleE = moduleResolver.getModuleFromFilePath("moduleE.py"); expect(moduleE).toBeDefined(); // Test resolving a module import const moduleResult = resolver.resolveItem(moduleE, "moduleA"); expect(moduleResult).toBeDefined(); expect(moduleResult?.type).toBe(PYTHON_INTERNAL_MODULE_TYPE); expect(moduleResult?.module?.path).toBe("moduleA.py"); expect(moduleResult?.symbol).toBeUndefined(); // No specific symbol when importing whole module }); }); // ======================================================== // SECTION: Import Precedence and Shadowing // ======================================================== describe("Import Precedence and Shadowing", () => { test("local definitions override imports", () => { const precedenceModule = moduleResolver.getModuleFromFilePath( "precedence.py", ); expect(precedenceModule).toBeDefined(); // The local 'duplicate' function should be returned, not the imported one const result = resolver.resolveItem(precedenceModule, "duplicate"); expect(result).toBeDefined(); expect(result?.module?.path).toBe("precedence.py"); expect(result?.symbol?.id).toBe("duplicate"); }); test("explicit imports override wildcard imports", () => { const wildcardPrecedence = moduleResolver.getModuleFromFilePath( "wildcard_precedence.py", ); expect(wildcardPrecedence).toBeDefined(); // The 'bar' symbol is explicitly imported from moduleA.foo, overriding any wildcards const result = resolver.resolveItem(wildcardPrecedence, "bar"); expect(result).toBeDefined(); expect(result?.module?.path).toBe("moduleA.py"); expect(result?.symbol?.id).toBe("foo"); }); test("earlier wildcard imports shadow later ones", () => { const multiWildcards = moduleResolver.getModuleFromFilePath( "multi_wildcards.py", ); expect(multiWildcards).toBeDefined(); // The 'common' function should come from the first wildcard import const wildcardSymbols = resolver.getWildcardSymbols(multiWildcards); // Check for the functions from both modules expect(wildcardSymbols.has("unique1")).toBeTruthy(); expect(wildcardSymbols.has("unique2")).toBeTruthy(); // But the common function should come from the first module const commonFunc = resolver.resolveItem(multiWildcards, "common"); expect(commonFunc).toBeDefined(); expect(commonFunc?.module?.path).toBe("multi_wildcard1.py"); }); test("later imports override earlier imports with the same name", () => { const overrideModule = moduleResolver.getModuleFromFilePath( "override_imports.py", ); expect(overrideModule).toBeDefined(); // The 'foo' symbol should come from the last import (multi_wildcard1.common), not the first one (moduleA.foo) const result = resolver.resolveItem(overrideModule, "foo"); expect(result).toBeDefined(); expect(result?.module?.path).toBe("multi_wildcard1.py"); expect(result?.symbol?.id).toBe("common"); }); }); // ======================================================== // SECTION: Visibility and __all__ Directive // ======================================================== describe("Visibility and __all__ Directive", () => { test("respects __all__ in wildcard imports", () => { const importAllModule = moduleResolver.getModuleFromFilePath( "importAllModule.py", ); expect(importAllModule).toBeDefined(); // Should find public_func which is in __all__ const publicResult = resolver.resolveItem(importAllModule, "public_func"); expect(publicResult).toBeDefined(); expect(publicResult?.module?.path).toBe("moduleWithAll.py"); // Should NOT find another_func which is not in __all__ const anotherResult = resolver.resolveItem( importAllModule, "another_func", ); expect(anotherResult).toBeUndefined(); // Should NOT find _private_func which is neither in __all__ nor would be included due to _ prefix const privateResult = resolver.resolveItem( importAllModule, "_private_func", ); expect(privateResult).toBeUndefined(); }); test("explicit imports override __all__ restrictions", () => { const importSpecific = moduleResolver.getModuleFromFilePath( "importSpecificFromAll.py", ); expect(importSpecific).toBeDefined(); // Should find explicitly imported symbols even if not in __all__ const anotherResult = resolver.resolveItem( importSpecific, "another_func", ); expect(anotherResult).toBeDefined(); expect(anotherResult?.module?.path).toBe("moduleWithAll.py"); // Should also find explicitly imported private symbols const privateResult = resolver.resolveItem( importSpecific, "_private_func", ); expect(privateResult).toBeDefined(); expect(privateResult?.module?.path).toBe("moduleWithAll.py"); }); test("excludes private symbols in wildcard imports without __all__", () => { const importPrivate = moduleResolver.getModuleFromFilePath( "importPrivate.py", ); expect(importPrivate).toBeDefined(); // Should find public symbols through wildcard const publicResult = resolver.resolveItem( importPrivate, "public_function", ); expect(publicResult).toBeDefined(); const constantResult = resolver.resolveItem( importPrivate, "PUBLIC_CONSTANT", ); expect(constantResult).toBeDefined(); // Should NOT find private symbols through wildcard const privateResult = resolver.resolveItem( importPrivate, "_private_function", ); expect(privateResult).toBeUndefined(); const privateConstResult = resolver.resolveItem( importPrivate, "_PRIVATE_CONSTANT", ); expect(privateConstResult).toBeUndefined(); }); test("__all__ can override private symbol conventions", () => { const importAllOverride = moduleResolver.getModuleFromFilePath( "importAllOverride.py", ); expect(importAllOverride).toBeDefined(); // Should find both regular and private symbols listed in __all__ const regularResult = resolver.resolveItem( importAllOverride, "regular_func", ); expect(regularResult).toBeDefined(); // Even though it starts with _, it should be imported because it's in __all__ const privateResult = resolver.resolveItem( importAllOverride, "_private_func", ); expect(privateResult).toBeDefined(); }); }); // ======================================================== // SECTION: Complex Import Scenarios // ======================================================== describe("Complex Import Scenarios", () => { test("handles circular imports gracefully", () => { const circular1 = moduleResolver.getModuleFromFilePath("circular1.py"); const circular2 = moduleResolver.getModuleFromFilePath("circular1.py"); expect(circular1).toBeDefined(); expect(circular2).toBeDefined(); // Should be able to resolve the function defined in circular1 const func1Result = resolver.resolveItem(circular1, "circular1_func"); expect(func1Result).toBeDefined(); expect(func1Result?.module?.path).toBe("circular1.py"); // Should also resolve the function imported from circular2 const func2Result = resolver.resolveItem(circular1, "circular2_func"); expect(func2Result).toBeDefined(); expect(func2Result?.module?.path).toBe("circular2.py"); // And the reverse should work too const func1FromCircular2 = resolver.resolveItem( circular2, "circular1_func", ); expect(func1FromCircular2).toBeDefined(); expect(func1FromCircular2?.module?.path).toBe("circular1.py"); }); test("resolves symbols through package hierarchy with wildcard imports", () => { const usePackage = moduleResolver.getModuleFromFilePath("usePackage"); expect(usePackage).toBeDefined(); // Should be able to resolve the deeply nested function through multiple wildcards const deepResult = resolver.resolveItem(usePackage, "deep_nested_func"); expect(deepResult).toBeDefined(); expect(deepResult?.module?.path).toBe("package/submodule.py"); // Also test the intermediate function const intermediateResult = resolver.resolveItem( usePackage, "intermediate_func", ); expect(intermediateResult).toBeDefined(); expect(intermediateResult?.module?.path).toBe("package/intermediate.py"); // And the explicit import in __init__.py const subFuncResult = resolver.resolveItem(usePackage, "sub_func"); expect(subFuncResult).toBeDefined(); expect(subFuncResult?.module?.path).toBe("package/submodule.py"); }); test("handles relative imports in packages", () => { const package2 = moduleResolver.getModuleFromFilePath("package2.py"); const relmod = moduleResolver.getModuleFromFilePath("package2/relmod.py"); const absmod = moduleResolver.getModuleFromFilePath("package2/absmod.py"); expect(package2).toBeDefined(); expect(relmod).toBeDefined(); expect(absmod).toBeDefined(); // Test relative import from __init__ to submodule const relFuncFromInit = resolver.resolveItem(package2, "rel_func"); expect(relFuncFromInit).toBeDefined(); expect(relFuncFromInit?.module?.path).toBe("package2/relmod.py"); // Test relative import from submodule to package const varFromRelmod = resolver.resolveItem(relmod, "absolute_var"); expect(varFromRelmod).toBeDefined(); expect(varFromRelmod?.module?.path).toBe("package2/__init__.py"); // Test absolute import from submodule to package const varFromAbsmod = resolver.resolveItem(absmod, "absolute_var"); expect(varFromAbsmod).toBeDefined(); expect(varFromAbsmod?.module?.path).toBe("package2/__init__.py"); }); }); // ======================================================== // SECTION: Wildcard Symbol Collection // ======================================================== describe("Wildcard Symbol Collection", () => { test("collects all symbols for wildcard exports respecting __all__", () => { const moduleWithAll = moduleResolver.getModuleFromFilePath( "moduleWithAll.py", ); expect(moduleWithAll).toBeDefined(); const symbols = resolver.getWildcardSymbols(moduleWithAll); // Should only contain symbols listed in __all__ expect(symbols.size).toBe(1); expect(symbols.has("public_func")).toBeTruthy(); expect(symbols.has("another_func")).toBeFalsy(); expect(symbols.has("_private_func")).toBeFalsy(); }); test("collects all non-private symbols for wildcard exports without __all__", () => { const privateSymbols = moduleResolver.getModuleFromFilePath( "privateSymbols.py", ); expect(privateSymbols).toBeDefined(); const symbols = resolver.getWildcardSymbols(privateSymbols); // Should only contain public symbols (no _ prefix) expect(symbols.has("public_function")).toBeTruthy(); expect(symbols.has("PUBLIC_CONSTANT")).toBeTruthy(); expect(symbols.has("_private_function")).toBeFalsy(); expect(symbols.has("_PRIVATE_CONSTANT")).toBeFalsy(); }); test("combines symbols from wildcard imports with local symbols", () => { const moduleC = moduleResolver.getModuleFromFilePath("moduleC.py"); expect(moduleC).toBeDefined(); const symbols = resolver.getWildcardSymbols(moduleC); // Should have all the symbols from moduleB expect(symbols.has("f")).toBeTruthy(); // alias from moduleB expect(symbols.has("bar")).toBeTruthy(); // direct import in moduleB // Verify they resolve to the correct original source const fSymbol = symbols.get("f"); expect(fSymbol?.module?.path).toBe("moduleA.py"); expect(fSymbol?.symbol?.id).toBe("foo"); }); }); }); ================================================ FILE: src/languagePlugins/python/itemResolver/index.ts ================================================ import type { PythonExportExtractor } from "../exportExtractor/index.ts"; import type { PythonImportExtractor } from "../importExtractor/index.ts"; import { FROM_IMPORT_STATEMENT_TYPE, NORMAL_IMPORT_STATEMENT_TYPE, } from "../importExtractor/types.ts"; import type { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PYTHON_NAMESPACE_MODULE_TYPE, type PythonModule, } from "../moduleResolver/types.ts"; import { PYTHON_EXTERNAL_MODULE_TYPE, PYTHON_INTERNAL_MODULE_TYPE, type ResolvedExternalModule, type ResolvedExternalSymbol, type ResolvedInternalModule, type ResolvedInternalSymbol, type ResolvedItem, type ResolvedSymbol, } from "./types.ts"; /** * PythonItemResolver resolves items across Python modules following Python's * import resolution rules, handling both internal and external dependencies. * * The resolver implements Python's symbol resolution algorithm: * 1. Symbols defined directly in a module take precedence * 2. Explicitly imported symbols are checked next * 3. Wildcard imports are checked last, in order of appearance * * It handles both internal modules (analyzable within the project) and external * modules (from third-party or standard libraries). The resolver uses caching * to improve performance and to handle circular dependencies. * * This class ties together several components: * - ExportExtractor to find symbols defined in modules * - ImportExtractor to analyze import statements * - ModuleResolver to locate modules within the project */ export class PythonItemResolver { /** * Cache for final symbol resolution results * Maps a moduleId:symbolName pair to its resolved item */ private resolutionCache = new Map(); /** * Cache for in-progress resolutions to handle circular dependencies. * * When a symbol resolution is in progress but not completed, we store * its partial result here. This prevents infinite recursion when modules * import each other in a circular fashion, which is allowed in Python. * * Maps a moduleId:symbolName pair to its in-progress resolved item */ private recursiveCache = new Map(); /** * Cache for all symbols in a module, used for wildcard imports * Maps a module path to a map of symbol names to resolved symbols * * This caches the complete set of symbols available in a module, * including both directly defined symbols and imported symbols. */ private allSymbolsCache = new Map>(); /** * Creates a new PythonItemResolver * * @param exportExtractor - Used to extract symbols defined in modules * @param importExtractor - Used to analyze import statements * @param moduleResolver - Used to resolve module references */ constructor( private exportExtractor: PythonExportExtractor, private importExtractor: PythonImportExtractor, private moduleResolver: PythonModuleResolver, ) {} /** * Resolves a symbol name from the perspective of a module. * * This method follows Python's import resolution rules to find the definition * of a symbol, whether it's defined directly in the module, imported from another * module, or comes from a wildcard import. * * @param fromModule - The module context where the symbol is referenced * @param itemName - The name of the item (symbol or module) to resolve * @returns The resolved item information or undefined if not found */ public resolveItem( fromModule: PythonModule, itemName: string, ): ResolvedItem | undefined { // Generate cache key based on starting module and symbol name const cacheKey = `${fromModule.path}:${itemName}`; // Check if already resolved if (this.resolutionCache.has(cacheKey)) { return this.resolutionCache.get(cacheKey); } // Track visited module-symbol pairs to detect cycles const visited = new Set(); const result = this.resolveItemImpl(fromModule, itemName, visited); // Cache the final result this.resolutionCache.set(cacheKey, result); return result; } /** * Implementation of the item resolution logic following Python's resolution order * * Resolution happens in this specific order: * 1. Check if the item is defined directly in this module * 2. Check explicitly imported items (via direct imports or from-imports) * 3. Check wildcard imports last, in order of appearance * * @param module - The module from which to resolve the item * @param itemName - The name of the item (symbol or module) to resolve * @param visited - Set of already visited module:item pairs to prevent circular resolution * @returns The resolved item or undefined if not found */ private resolveItemImpl( module: PythonModule, itemName: string, visited: Set, ): ResolvedItem | undefined { const visitKey = `${module.path}:${itemName}`; // Check for circular references if (visited.has(visitKey)) { return undefined; } // Check recursive resolution cache if (this.recursiveCache.has(visitKey)) { return this.recursiveCache.get(visitKey); } visited.add(visitKey); // Python resolution order: // 1. Check symbols defined directly in this module // 2. Check explicitly imported items (via direct or from-imports) // 3. Check wildcard imports last, in order of appearance // 1. If module is a namespace package, // Cause these have are not files, so they have no // imports or sumbols. if (module.type !== PYTHON_NAMESPACE_MODULE_TYPE) { // 2. Check if symbol is defined directly in this module // Get all symbols defined in this module using the export extractor const exports = this.exportExtractor.getSymbols(module.path); // Look for direct symbol match by comparing symbol ids with the requested item name const directSymbol = exports.symbols.find((sym) => sym.id === itemName); // Direct symbol resolution: // If the symbol is defined directly in this module, we can return it immediately // Python always gives priority to symbols defined directly in the module, // regardless of any __all__ restrictions (which only affect what's exported) if (directSymbol) { // Create a resolved internal item with the found symbol const result = { type: PYTHON_INTERNAL_MODULE_TYPE, module: module, symbol: directSymbol, } as ResolvedInternalSymbol; this.recursiveCache.set(visitKey, result); return result; } // 3. Check imports in order of appearance in the file // Python resolves imports sequentially based on their order in the file, // with the only exception being wildcard imports which are always lowest priority const importStatements = this.importExtractor.getImportStatements( module.path, ); // Store last resolved item from explicit imports // In Python, later imports with the same name override earlier ones let lastExplicitImport: ResolvedItem | undefined; // Process all explicit imports first - both normal imports and from-imports // (excluding wildcards) in the order they appear in the file for (const stmt of importStatements) { if (stmt.type === FROM_IMPORT_STATEMENT_TYPE) { // For "from X import Y" statements const member = stmt.members[0]; if (member.isWildcardImport) { // Process wildcard import last continue; } // Try to resolve the source module (X in "from X import Y") // This could be an internal module or an external one const sourceModule = this.moduleResolver.resolveModule( module, member.identifierNode.text, ); // 3.1 Check if explicit import for (const item of member.items || []) { const lookupName = item.aliasNode?.text || item.identifierNode.text; // itemName matches the imported name if (itemName === lookupName) { if (sourceModule) { // internal module reference lastExplicitImport = this.resolveItemImpl( sourceModule, item.identifierNode.text, visited, ); // If we got a result and it's an internal symbol, track the re-export chain if ( lastExplicitImport && lastExplicitImport.type === PYTHON_INTERNAL_MODULE_TYPE && (lastExplicitImport as ResolvedInternalSymbol).symbol ) { const resolvedSymbol = lastExplicitImport as ResolvedInternalSymbol; // Initialize or update the re-export chain if (!resolvedSymbol.reExportChain) { resolvedSymbol.reExportChain = []; } // Add this module to the re-export chain resolvedSymbol.reExportChain.push(sourceModule); } } else { // external module reference lastExplicitImport = { type: PYTHON_EXTERNAL_MODULE_TYPE, moduleName: member.identifierNode.text, symbolName: item.identifierNode.text, } as ResolvedExternalSymbol; } // Don't return immediately, continue processing for possible overrides } } } // 3.2 Check normal import if (stmt.type === NORMAL_IMPORT_STATEMENT_TYPE) { for (const member of stmt.members) { const lookupName = member.aliasNode?.text || member.identifierNode.text; if (itemName === lookupName) { const sourceModule = this.moduleResolver.resolveModule( module, member.identifierNode.text, ); // Internal module reference if (sourceModule) { lastExplicitImport = { type: PYTHON_INTERNAL_MODULE_TYPE, module: sourceModule, } as ResolvedInternalModule; } else { // External module reference lastExplicitImport = { type: PYTHON_EXTERNAL_MODULE_TYPE, moduleName: member.identifierNode.text, } as ResolvedExternalModule; } // Don't return immediately, continue processing for possible overrides } } } } // After all explicit imports, return the last one found (if any) if (lastExplicitImport) { this.recursiveCache.set(visitKey, lastExplicitImport); return lastExplicitImport; } // 3.3 Process wildcard imports last, in order of appearance for (const stmt of importStatements) { if (stmt.type === FROM_IMPORT_STATEMENT_TYPE) { // For "from X import Y" statements const member = stmt.members[0]; // Try to resolve the source module (X in "from X import Y") // This could be an internal module or an external one const sourceModule = this.moduleResolver.resolveModule( module, member.identifierNode.text, ); // Handle wildcard imports (from X import *) if (member.isWildcardImport) { if (sourceModule) { // For internal wildcard imports, we need to respect the __all__ list // Python only imports symbols listed in __all__ if it exists const sourceExports = this.exportExtractor.getSymbols( sourceModule.path, ); if (sourceExports.publicSymbols) { // If the source module has an __all__ list defined: // Only continue if the requested item is listed in __all__ // This matches Python's behavior where wildcard imports only import // symbols explicitly listed in __all__ if (!sourceExports.publicSymbols.includes(itemName)) { continue; // Skip this import as the item isn't in __all__ } } else { // If no __all__ list exists, Python only imports non-underscore-prefixed names // Skip private symbols (those starting with underscore) if (itemName.startsWith("_")) { continue; // Skip private symbols in wildcard imports } } // Recursively try to resolve the item from the source module const result = this.resolveItemImpl( sourceModule, itemName, visited, ); if (result) { this.recursiveCache.set(visitKey, result); return result; } } // For external wildcard imports (modules we can't analyze): // We can't determine what symbols are available without analyzing the external module // We skip this import and continue checking other imports continue; } } } } // Check if the item is defined directly in this module const childModule = module.children.get(itemName); if (childModule) { const result = { type: PYTHON_INTERNAL_MODULE_TYPE, module: childModule, } as ResolvedInternalModule; // Return the resolved child module as an internal item return result; } // Not found in this module or any imports this.recursiveCache.set(visitKey, undefined); return undefined; } /** * Returns all symbols that would be imported through a wildcard import from this module. * * This method follows Python's wildcard import rules: * - If __all__ is defined in the module, only symbols listed there are included * - Otherwise, all symbols that don't begin with an underscore are included * - Direct definitions in the module take precedence over any imports * - Explicit imports take precedence over wildcard imports * - Wildcard imports have lowest precedence, processed in order of appearance * * @param module - The module to collect wildcard-importable symbols from * @returns Map of symbol names to their resolved definitions */ public getWildcardSymbols(module: PythonModule): Map { // Check cache first const cacheKey = module.path; if (this.allSymbolsCache.has(cacheKey)) { return this.allSymbolsCache.get(cacheKey) as Map; } // Create a new map for storing all symbols const result = new Map(); // Track visited modules to prevent infinite recursion const visited = new Set(); this.collectSymbols(module, result, visited); // Cache the result this.allSymbolsCache.set(cacheKey, result); return result; } /** * Collects all symbols from a module that would be available for import, * following Python's symbol visibility and precedence rules. * * Python's symbol resolution has specific rules for wildcard imports: * 1. Local definitions take precedence over any imports * - If __all__ is defined, only symbols in __all__ are included * - Otherwise, non-underscore-prefixed symbols are included * 2. Explicit imports (both regular and from-imports) are processed next, in order * 3. Wildcard imports have lowest precedence and are processed last, in order * * This ensures consistent symbol resolution matching Python's behavior. * * @param module - The module from which to collect symbols * @param symbolsMap - Map to store the collected symbols * @param visited - Set of already visited modules to prevent circular imports */ private collectSymbols( module: PythonModule, symbolsMap: Map, visited: Set, ): void { // Prevent circular imports if (visited.has(module.path)) { return; } visited.add(module.path); // for namespace modules, they don't have direct symbols if (module.type === PYTHON_NAMESPACE_MODULE_TYPE) { return; } // 1. Get all symbols defined directly in this module // Python gives highest precedence to local definitions const exports = this.exportExtractor.getSymbols(module.path); for (const symbol of exports.symbols) { if (exports.publicSymbols) { // Check if the symbol is in __all__ if (exports.publicSymbols.includes(symbol.id)) { symbolsMap.set(symbol.id, { type: PYTHON_INTERNAL_MODULE_TYPE, module: module, symbol: symbol, } as ResolvedInternalSymbol); } } else { // If no __all__, add all public symbols // unless they are private (starting with underscore) if (!symbol.id.startsWith("_")) { symbolsMap.set(symbol.id, { type: PYTHON_INTERNAL_MODULE_TYPE, module: module, symbol: symbol, } as ResolvedInternalSymbol); } } } // 2. Process imports in the correct order of precedence: // First explicit imports, then wildcard imports const importStatements = this.importExtractor.getImportStatements( module.path, ); // First pass: process all explicit imports in order of appearance // Explicit imports take precedence over wildcard imports in Python for (const stmt of importStatements) { if (stmt.type === FROM_IMPORT_STATEMENT_TYPE) { const member = stmt.members[0]; // Skip wildcard imports in first pass if (member.isWildcardImport) { continue; } const sourceModule = this.moduleResolver.resolveModule( module, member.identifierNode.text, ); if (member.isWildcardImport) { // Process wildcard import last continue; } // Handle explicit imports for (const item of member.items || []) { const lookupName = item.aliasNode?.text || item.identifierNode.text; if (sourceModule) { // for internal modules const resolved = this.resolveItem( sourceModule, item.identifierNode.text, ); if (resolved) { if (resolved.type === PYTHON_INTERNAL_MODULE_TYPE) { // Explicit imports always override previously imported names symbolsMap.set(lookupName, { type: PYTHON_INTERNAL_MODULE_TYPE, module: resolved.module, symbol: resolved.symbol, } as ResolvedInternalSymbol); } else { // External module reference symbolsMap.set(lookupName, { type: PYTHON_EXTERNAL_MODULE_TYPE, moduleName: resolved.moduleName, symbolName: item.identifierNode.text, } as ResolvedExternalSymbol); } } } else { // External module reference symbolsMap.set(lookupName, { type: PYTHON_EXTERNAL_MODULE_TYPE, moduleName: member.identifierNode.text, symbolName: item.identifierNode.text, } as ResolvedExternalSymbol); } } } } // Second pass: process wildcard imports in order of appearance // Wildcard imports have lowest precedence in Python's import system for (const stmt of importStatements) { if (stmt.type === FROM_IMPORT_STATEMENT_TYPE) { const member = stmt.members[0]; // Only process wildcard imports in second pass if (member.isWildcardImport) { const sourceModule = this.moduleResolver.resolveModule( module, member.identifierNode.text, ); // Handle wildcard imports if (sourceModule) { // For internal modules, we can get all symbols const sourceSymbols = this.getWildcardSymbols(sourceModule); for (const [name, resolved] of sourceSymbols) { // Check if the symbol is not already defined // In Python, symbols from earlier imports (or local definitions) // always shadow symbols from later wildcard imports if (!symbolsMap.has(name)) { if (resolved.type === PYTHON_INTERNAL_MODULE_TYPE) { // If it's an internal symbol, add it to the map symbolsMap.set(name, { type: PYTHON_INTERNAL_MODULE_TYPE, module: resolved.module, symbol: resolved.symbol, } as ResolvedInternalSymbol); } else { // If it's an external symbol, add it to the map symbolsMap.set(name, { type: PYTHON_EXTERNAL_MODULE_TYPE, moduleName: resolved.moduleName, symbolName: name, } as ResolvedExternalSymbol); } } } } } } } } } ================================================ FILE: src/languagePlugins/python/itemResolver/types.ts ================================================ import type { PythonSymbol } from "../exportExtractor/types.ts"; import type { PythonModule } from "../moduleResolver/types.ts"; /** * Constant representing an internal Python module/symbol that can be resolved * within the project's source code. */ export const PYTHON_INTERNAL_MODULE_TYPE = "internal"; /** * Constant representing an external Python module/symbol that comes from * third-party dependencies or standard library and cannot be directly analyzed. */ export const PYTHON_EXTERNAL_MODULE_TYPE = "external"; /** * Represents the type of a Python module or symbol - either internal (within the project) * or external (from third-party libraries or standard library). * * Internal items exist within the project's codebase and can be analyzed directly. * External items are from third-party libraries or the standard library and only their * references can be tracked. */ export type PythonModuleType = | typeof PYTHON_INTERNAL_MODULE_TYPE | typeof PYTHON_EXTERNAL_MODULE_TYPE; /** * Represents a resolved Python item (module or symbol) after the resolution process. * * This interface is the base for both internal and external resolved items and contains * properties relevant to both types. The concrete type (internal/external) determines * which fields will be populated. * * This is used as the base interface for more specific resolved item types. */ export interface ResolvedItem { /** Indicates whether the item is internal or external */ type: PythonModuleType; /** For internal items, the resolved module reference */ module?: PythonModule; /** For internal items that are symbols, the symbol definition */ symbol?: PythonSymbol; /** For external items, the name of the module */ moduleName?: string; /** For external items that are symbols, the name of the symbol */ symbolName?: string; } /** * Represents a resolved Python module after the resolution process. * * This specialized interface is for modules (as opposed to symbols) and serves * as a base for both internal and external module references. * * It includes all properties from ResolvedItem but with a focus on module-specific attributes. * Concrete implementations include ResolvedInternalModule and ResolvedExternalModule. */ export interface ResolvedModule extends ResolvedItem { /** Indicates whether the module is internal or external */ type: PythonModuleType; /** For internal modules, the resolved module reference */ module?: PythonModule; /** For external modules, the name of the module */ moduleName?: string; } /** * Represents a resolved internal Python module that exists within the analyzed project. * * Internal modules are those defined within the project's source code that can be * directly analyzed. This interface specifically represents modules, not symbols. * * Example: A module like "myproject.utils" defined within the project's codebase. */ export interface ResolvedInternalModule extends ResolvedModule { /** Always set to "internal" for internal modules */ type: typeof PYTHON_INTERNAL_MODULE_TYPE; /** Reference to the resolved Python module */ module: PythonModule; } /** * Represents a resolved external Python module that comes from outside the analyzed project. * * External modules are those defined in third-party libraries or Python's standard library * that cannot be directly analyzed. We can only track their names and references. * This interface specifically represents modules, not symbols. * * Examples: Standard library modules like "os" or "sys", or third-party modules like "numpy" or "pandas". */ export interface ResolvedExternalModule extends ResolvedModule { /** Always set to "external" for external modules */ type: typeof PYTHON_EXTERNAL_MODULE_TYPE; /** The name of the external module */ moduleName: string; } /** * Represents a resolved Python symbol (not a module) after the resolution process. * * This specialized interface is for symbols (as opposed to modules) and serves * as a base for both internal and external symbol references. * * Symbols include functions, classes, variables, or any other named entity in Python. * Concrete implementations include ResolvedInternalSymbol and ResolvedExternalSymbol. */ export interface ResolvedSymbol extends ResolvedItem { /** Indicates whether the symbol is internal or external */ type: PythonModuleType; /** For internal symbols, the module where the symbol is defined */ module?: PythonModule; /** For internal symbols, the symbol definition */ symbol?: PythonSymbol; /** For external symbols, the name of the module */ moduleName?: string; /** For external symbols, the name of the symbol */ symbolName?: string; } /** * Represents a resolved internal Python symbol defined within the analyzed project. * * Used specifically for symbols (variables, functions, classes) that are defined * within the project's source code and can be directly analyzed. * * Internal symbols have complete information available including their module location * and full symbol definition with metadata. * * Example: A function called "process_data" defined in a module within the project. */ export interface ResolvedInternalSymbol extends ResolvedSymbol { /** Always set to "internal" for internal symbols */ type: typeof PYTHON_INTERNAL_MODULE_TYPE; /** The module where the symbol is defined */ module: PythonModule; /** The symbol definition */ symbol: PythonSymbol; /** * Chain of modules that re-export this symbol * This tracks the re-export path when a symbol is imported and then re-exported by another module. */ reExportChain: PythonModule[]; } /** * Represents a resolved external Python symbol that comes from outside the analyzed project. * * External symbols are those defined in third-party libraries or Python's standard library * that cannot be directly analyzed. We can only track their names and references. * * Examples: Functions like "os.path.join" from the standard library, or third-party * classes like "pandas.DataFrame". */ export interface ResolvedExternalSymbol extends ResolvedSymbol { /** Always set to "external" for external symbols */ type: typeof PYTHON_EXTERNAL_MODULE_TYPE; /** The name of the external module */ moduleName: string; /** The name of the external symbol */ symbolName: string; } ================================================ FILE: src/languagePlugins/python/metricAnalyzer/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type Parser from "tree-sitter"; import { PythonMetricsAnalyzer } from "./index.ts"; import { PythonExportExtractor } from "../exportExtractor/index.ts"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; describe("PythonMetricsAnalyzer", () => { const parser = pythonParser; // Helper function to set up the test environment function setupTest(code: string, filename = "test.py") { const tree = parser.parse(code); const files = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); files.set(filename, { path: filename, rootNode: tree.rootNode, }); const exportExtractor = new PythonExportExtractor(parser, files); const complexityAnalyzer = new PythonMetricsAnalyzer(parser); return { exportExtractor, complexityAnalyzer, filename, tree }; } test("should analyze a simple function", () => { const code = ` def simple_function(): return 42 `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); expect(symbols.length).toBeGreaterThan(0); const functionSymbol = symbols.find((s) => s.id === "simple_function"); expect(functionSymbol).toBeDefined(); if (!functionSymbol) { throw new Error("Function symbol not found"); } // Use the nodes property (not node) const nodes = functionSymbol.nodes; const metrics = complexityAnalyzer.analyzeNodes(nodes); expect(metrics.cyclomaticComplexity).toBe(1); expect(metrics.codeLinesCount).toBeGreaterThan(0); expect(metrics.linesCount).toBeGreaterThan(0); expect(metrics.characterCount).toBeGreaterThan(0); expect(metrics.codeCharacterCount).toBeGreaterThan(0); }); test("should analyze a function with branches", () => { const code = ` def complex_function(a, b): if a > 0: if b > 0: return a + b else: return a - b elif a < 0: return -a + b else: for i in range(10): if i % 2 == 0: continue elif i % 3 == 0: break return 0 `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); const functionSymbol = symbols.find((s) => s.id === "complex_function"); expect(functionSymbol).toBeDefined(); if (!functionSymbol) { throw new Error("Function symbol not found"); } // Use the nodes property (not node) const nodes = functionSymbol.nodes; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Base complexity 1 + branches: // 1(if a>0) + 1(if b>0) + 1(else for b) + 1(elif a<0) + 1(else for a) + // 1(for loop) + 1(if i%2) + 1(elif i%3) // = 1 + 8 = 9 expect(metrics.cyclomaticComplexity).toBeGreaterThan(1); }); test("should analyze a function with logical operators", () => { const code = ` def logical_function(a, b, c): if a > 0 and b > 0: return True elif a < 0 or b < 0: if c and (a or b): return False return None `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); const functionSymbol = symbols.find((s) => s.id === "logical_function"); expect(functionSymbol).toBeDefined(); if (!functionSymbol) { throw new Error("Function symbol not found"); } // Use the nodes property (not node) const nodes = functionSymbol.nodes; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Base complexity 1 + branches: // 1(if) + 1(and) + 1(elif) + 1(or) + 1(if c) + 1(and) + 1(or) // = 1 + 7 = 8 expect(metrics.cyclomaticComplexity).toBeGreaterThan(4); }); test("should analyze a class with methods", () => { const code = ` class TestClass: def __init__(self, value): self.value = value def simple_method(self): return self.value def complex_method(self, factor): if factor > 0: return self.value * factor else: return self.value `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); const classSymbol = symbols.find((s) => s.id === "TestClass"); expect(classSymbol).toBeDefined(); if (!classSymbol) { throw new Error("Class symbol not found"); } // Use the nodes property (not node) const nodes = classSymbol.nodes; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Should have found the if-else in the complex_method expect(metrics.cyclomaticComplexity).toBeGreaterThan(1); }); test("should handle comments and empty lines correctly", () => { const code = ` def commented_function(): # This is a comment # Another comment value = 42 # Inline comment # Final comment return value `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); const functionSymbol = symbols.find((s) => s.id === "commented_function"); expect(functionSymbol).toBeDefined(); if (!functionSymbol) { throw new Error("Function symbol not found"); } // Use the nodes property (not node) const nodes = functionSymbol.nodes; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Only actual code lines should be counted expect(metrics.linesCount).toBeGreaterThan(metrics.codeLinesCount); expect(metrics.codeLinesCount).toBeGreaterThan(0); expect(metrics.characterCount).toBeGreaterThan(metrics.codeCharacterCount); expect(metrics.codeCharacterCount).toBeGreaterThan(0); }); test("should analyze try/except blocks", () => { const code = ` def exception_function(): try: value = 42 return value except ValueError: return "Value error" except TypeError: return "Type error" finally: print("Cleanup") `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); const functionSymbol = symbols.find((s) => s.id === "exception_function"); expect(functionSymbol).toBeDefined(); if (!functionSymbol) { throw new Error("Function symbol not found"); } // Use the nodes property (not node) const nodes = functionSymbol.nodes; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Should have complexity from try and except blocks expect(metrics.cyclomaticComplexity).toBeGreaterThan(2); }); test("should analyze an entire file", () => { const code = ` def function1(): return 1 def function2(x): if x > 0: return x return 0 class TestClass: def method1(self): try: return 42 except: return 0 `; const { complexityAnalyzer, tree } = setupTest(code); // Analyze the entire file using the root node const nodes = [tree.rootNode]; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Should include complexity from all functions and methods expect(metrics.cyclomaticComplexity).toBeGreaterThan(3); expect(metrics.linesCount).toBeGreaterThan(0); expect(metrics.codeLinesCount).toBeGreaterThan(0); expect(metrics.characterCount).toBeGreaterThan(0); expect(metrics.codeCharacterCount).toBeGreaterThan(0); }); test("should analyze multiple nodes", () => { const code = ` def function1(): return 1 def function2(x): if x > 0: return x return 0 `; const { exportExtractor, complexityAnalyzer, filename } = setupTest(code); const { symbols } = exportExtractor.getSymbols(filename); // Get both function nodes const function1Symbol = symbols.find((s) => s.id === "function1"); const function2Symbol = symbols.find((s) => s.id === "function2"); expect(function1Symbol).toBeDefined(); expect(function2Symbol).toBeDefined(); if (!function1Symbol || !function2Symbol) { throw new Error("Function symbols not found"); } // Use the nodes property (not node) const nodes = [...function1Symbol.nodes, ...function2Symbol.nodes]; const metrics = complexityAnalyzer.analyzeNodes(nodes); // Should include complexity from both functions expect(metrics.cyclomaticComplexity).toBeGreaterThan(1); // Compare with analyzing them separately const metrics1 = complexityAnalyzer.analyzeNodes(function1Symbol.nodes); const metrics2 = complexityAnalyzer.analyzeNodes(function2Symbol.nodes); // Total metrics should be sum of individual metrics (except base complexity is counted once) expect(metrics.codeLinesCount).toBe( metrics1.codeLinesCount + metrics2.codeLinesCount, ); expect(metrics.linesCount).toBe(metrics1.linesCount + metrics2.linesCount); expect(metrics.characterCount).toBe( metrics1.characterCount + metrics2.characterCount, ); expect(metrics.codeCharacterCount).toBe( metrics1.codeCharacterCount + metrics2.codeCharacterCount, ); }); }); ================================================ FILE: src/languagePlugins/python/metricAnalyzer/index.ts ================================================ import Parser from "tree-sitter"; import type { PythonComplexityMetrics } from "./types.ts"; /** * Interface for representing comment spans in the code */ interface CommentSpan { start: { row: number; column: number }; end: { row: number; column: number }; } /** * PythonMetricsAnalyzer calculates complexity metrics for Python symbols. * * This class performs the following: * - Calculates cyclomatic complexity for Python symbols using Tree-sitter * - Analyzes code structure to count decision points (if, elif, while, for, and, or, etc.) * - Provides detailed metrics for each symbol including line counts and character counts * * Note: This implementation uses a simplified approach to calculating cyclomatic complexity * by counting basic control flow structures and boolean operators. */ export class PythonMetricsAnalyzer { private parser: Parser; private complexityQuery: Parser.Query; private commentQuery: Parser.Query; constructor(parser: Parser) { this.parser = parser; // This query captures all nodes that contribute to cyclomatic complexity // Each capture increases the complexity by 1 this.complexityQuery = new Parser.Query( this.parser.getLanguage(), ` ; Basic control flow structures (if_statement) @dec (elif_clause) @dec (while_statement) @dec (for_statement) @dec (with_statement) @dec (try_statement) @dec (except_clause) @dec (conditional_expression) @dec (boolean_operator) @dec (if_clause) @dec `, ); // Query to find comments this.commentQuery = new Parser.Query( this.parser.getLanguage(), ` (comment) @comment `, ); } /** * Analyzes a list of AST nodes and calculates its complexity metrics * * @param nodes The AST nodes to analyze * @returns Complexity metrics for the nodes */ public analyzeNodes(nodes: Parser.SyntaxNode[]) { // Base complexity starts at 1 let cyclomaticComplexity = 1; let codeLinesCount = 0; let linesCount = 0; let characterCount = 0; let codeCharacterCount = 0; // Process each node of the symbol to count metrics for (const node of nodes) { const nodeMetrics = this.analyzeNode(node); // Accumulate metrics from this node linesCount += nodeMetrics.linesCount; codeLinesCount += nodeMetrics.codeLinesCount; characterCount += nodeMetrics.characterCount; codeCharacterCount += nodeMetrics.codeCharacterCount; cyclomaticComplexity += nodeMetrics.complexityCount; } const metrics: PythonComplexityMetrics = { cyclomaticComplexity, codeLinesCount, linesCount, codeCharacterCount, characterCount, }; return metrics; } /** * Analyzes a single syntax node and calculates its metrics * * @param node The syntax node to analyze * @returns Metrics for the node */ private analyzeNode(node: Parser.SyntaxNode) { // Count total lines and characters const linesCount = node.endPosition.row - node.startPosition.row + 1; const characterCount = node.text.length; // Split the node text into lines for processing const lines = node.text.split("\n"); // Find comments and their spans const { pureCommentLines, commentSpans } = this.findComments(node, lines); // Find empty lines const emptyLines = this.findEmptyLines(node, lines); // Calculate code lines const nonCodeLines = new Set([...pureCommentLines, ...emptyLines]); const codeLinesCount = linesCount - nonCodeLines.size; // Calculate code characters const codeCharacterCount = this.calculateCodeCharacters( node, lines, pureCommentLines, emptyLines, commentSpans, ); // Calculate cyclomatic complexity const complexityCount = this.calculateComplexity(node); return { linesCount, codeLinesCount, characterCount, codeCharacterCount, complexityCount, }; } /** * Finds all comments in a node and categorizes them * * @param node The syntax node to analyze * @param lines The lines of text in the node * @returns Object containing pure comment lines and comment spans */ private findComments( node: Parser.SyntaxNode, lines: string[], ): { pureCommentLines: Set; commentSpans: CommentSpan[]; } { const pureCommentLines = new Set(); const commentSpans: CommentSpan[] = []; const commentCaptures = this.commentQuery.captures(node); for (const capture of commentCaptures) { const commentNode = capture.node; // Record the comment span for character counting commentSpans.push({ start: { row: commentNode.startPosition.row, column: commentNode.startPosition.column, }, end: { row: commentNode.endPosition.row, column: commentNode.endPosition.column, }, }); // Check if the comment starts at the beginning of the line (ignoring whitespace) const lineIdx = commentNode.startPosition.row - node.startPosition.row; if (lineIdx >= 0 && lineIdx < lines.length) { const lineText = lines[lineIdx]; const textBeforeComment = lineText.substring( 0, commentNode.startPosition.column, ); // If there's only whitespace before the comment, it's a pure comment line if (textBeforeComment.trim().length === 0) { for ( let line = commentNode.startPosition.row; line <= commentNode.endPosition.row; line++ ) { pureCommentLines.add(line); } } } } return { pureCommentLines, commentSpans }; } /** * Finds all empty lines in a node * * @param node The syntax node to analyze * @param lines The lines of text in the node * @returns Set of line numbers that are empty */ private findEmptyLines( node: Parser.SyntaxNode, lines: string[], ): Set { const emptyLines = new Set(); for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; if (lines[i].trim().length === 0) { emptyLines.add(lineIndex); } } return emptyLines; } /** * Calculates the number of characters that are actual code * * @param node The syntax node to analyze * @param lines The lines of text in the node * @param pureCommentLines Set of line numbers that are pure comments * @param emptyLines Set of line numbers that are empty * @param commentSpans Array of comment spans to exclude * @returns Number of code characters */ private calculateCodeCharacters( node: Parser.SyntaxNode, lines: string[], pureCommentLines: Set, emptyLines: Set, commentSpans: CommentSpan[], ): number { let codeCharCount = 0; // Process each line individually for (let i = 0; i < lines.length; i++) { const lineIndex = node.startPosition.row + i; const line = lines[i]; // Skip empty lines and pure comment lines if (emptyLines.has(lineIndex) || pureCommentLines.has(lineIndex)) { continue; } // Process line for code characters let lineText = line; // Remove comment content from the line if present for (const span of commentSpans) { if (span.start.row === lineIndex) { // Comment starts on this line lineText = lineText.substring(0, span.start.column); } } // Count normalized code characters (trim excessive whitespace) const normalizedText = lineText.trim().replace(/\s+/g, " "); codeCharCount += normalizedText.length; } return codeCharCount; } /** * Calculates the cyclomatic complexity of a node * * @param node The syntax node to analyze * @returns Number of decision points that contribute to complexity */ private calculateComplexity(node: Parser.SyntaxNode): number { const captures = this.complexityQuery.captures(node); return captures.length; } } ================================================ FILE: src/languagePlugins/python/metricAnalyzer/types.ts ================================================ /** * Represents complexity metrics for a Python symbol */ export interface PythonComplexityMetrics { /** Cyclomatic complexity (McCabe complexity) */ cyclomaticComplexity: number; /** Code lines (not including whitespace or comments) */ codeLinesCount: number; /** Total lines (including whitespace and comments) */ linesCount: number; /** Characters of actual code (excluding comments and excessive whitespace) */ codeCharacterCount: number; /** Total characters in the entire symbol */ characterCount: number; } ================================================ FILE: src/languagePlugins/python/moduleResolver/index.test.ts ================================================ import { beforeEach, describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { PythonModuleResolver } from "./index.ts"; import { PYTHON_MODULE_TYPE, PYTHON_NAMESPACE_MODULE_TYPE, PYTHON_PACKAGE_MODULE_TYPE, } from "./types.ts"; import { SEPARATOR } from "@std/path"; describe("PythonModuleResolver", () => { describe("Module Map Building", () => { test("should build module map for a single file", () => { const resolver = new PythonModuleResolver(new Set(["main.py"]), "3.13"); const root = resolver.pythonModule; expect(root.name).toBe(""); expect(root.type).toBe(PYTHON_NAMESPACE_MODULE_TYPE); expect(root.children.size).toBe(1); const mainModule = root.children.get("main"); expect(mainModule).toBeDefined(); expect(mainModule?.name).toBe("main"); expect(mainModule?.type).toBe(PYTHON_MODULE_TYPE); expect(mainModule?.path).toBe("main.py"); expect(mainModule?.fullName).toBe("main"); expect(mainModule?.parent).toBe(root); }); test("should build module map for multiple files at root level", () => { const resolver = new PythonModuleResolver( new Set(["main.py", "utils.py", "config.py"]), "3.13", ); const root = resolver.pythonModule; expect(root.children.size).toBe(3); const moduleNames = Array.from(root.children.keys()); expect(moduleNames).toContain("main"); expect(moduleNames).toContain("utils"); expect(moduleNames).toContain("config"); // Check each module has correct properties for (const name of moduleNames) { const module = root.children.get(name); expect(module?.name).toBe(name); expect(module?.type).toBe(PYTHON_MODULE_TYPE); expect(module?.path).toBe(`${name}.py`); expect(module?.fullName).toBe(name); } }); test("should build module map for a simple package", () => { const resolver = new PythonModuleResolver( new Set(["pkg/__init__.py", "pkg/module.py"]), "3.13", ); const root = resolver.pythonModule; expect(root.children.size).toBe(1); const pkgModule = root.children.get("pkg"); expect(pkgModule).toBeDefined(); expect(pkgModule?.name).toBe("pkg"); expect(pkgModule?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(pkgModule?.path).toBe("pkg/__init__.py"); expect(pkgModule?.fullName).toBe("pkg"); expect(pkgModule?.children.size).toBe(1); const subModule = pkgModule?.children.get("module"); expect(subModule?.name).toBe("module"); expect(subModule?.type).toBe(PYTHON_MODULE_TYPE); expect(subModule?.path).toBe("pkg/module.py"); expect(subModule?.fullName).toBe("pkg.module"); expect(subModule?.parent).toBe(pkgModule); }); test("should build module map for nested packages", () => { const resolver = new PythonModuleResolver( new Set([ "pkg/__init__.py", "pkg/module.py", "pkg/subpkg/__init__.py", "pkg/subpkg/submodule.py", "pkg/subpkg/deeper/__init__.py", "pkg/subpkg/deeper/core.py", ]), "3.13", ); const root = resolver.pythonModule; // Check first level const pkgModule = root.children.get("pkg"); expect(pkgModule).toBeDefined(); expect(pkgModule?.name).toBe("pkg"); expect(pkgModule?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); // Check second level expect(pkgModule?.children.size).toBe(2); // module.py and subpkg const moduleModule = pkgModule?.children.get("module"); expect(moduleModule?.name).toBe("module"); expect(moduleModule?.fullName).toBe("pkg.module"); const subpkgModule = pkgModule?.children.get("subpkg"); expect(subpkgModule?.name).toBe("subpkg"); expect(subpkgModule?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(subpkgModule?.fullName).toBe("pkg.subpkg"); // Check third level expect(subpkgModule?.children.size).toBe(2); // submodule.py and deeper const submoduleModule = subpkgModule?.children.get("submodule"); expect(submoduleModule?.name).toBe("submodule"); expect(submoduleModule?.fullName).toBe("pkg.subpkg.submodule"); const deeperModule = subpkgModule?.children.get("deeper"); expect(deeperModule?.name).toBe("deeper"); expect(deeperModule?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(deeperModule?.fullName).toBe("pkg.subpkg.deeper"); // Check fourth level const coreModule = deeperModule?.children.get("core"); expect(coreModule?.name).toBe("core"); expect(coreModule?.fullName).toBe("pkg.subpkg.deeper.core"); }); test("should handle package namespaces with multiple modules", () => { const resolver = new PythonModuleResolver( new Set([ "pkg/__init__.py", "pkg/module1.py", "pkg/module2.py", "pkg/module3.py", ]), "3.13", ); const pkgModule = resolver.pythonModule.children.get("pkg"); expect(pkgModule?.children.size).toBe(3); const moduleNames = Array.from(pkgModule?.children.keys() || []); expect(moduleNames).toContain("module1"); expect(moduleNames).toContain("module2"); expect(moduleNames).toContain("module3"); }); test("should handle multiple packages at root level", () => { const resolver = new PythonModuleResolver( new Set([ "pkg1/__init__.py", "pkg1/module.py", "pkg2/__init__.py", "pkg2/module.py", "main.py", ]), "3.13", ); const root = resolver.pythonModule; expect(root.children.size).toBe(3); // pkg1, pkg2, main const pkg1 = root.children.get("pkg1"); const pkg2 = root.children.get("pkg2"); const main = root.children.get("main"); expect(pkg1?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(pkg2?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(main?.type).toBe(PYTHON_MODULE_TYPE); expect(pkg1?.children.size).toBe(1); expect(pkg2?.children.size).toBe(1); }); }); describe("Module Resolution", () => { let resolver: PythonModuleResolver; beforeEach(() => { resolver = new PythonModuleResolver( new Set([ "main.py", "utils.py", "config.py", "pkg/__init__.py", "pkg/module1.py", "pkg/module2.py", "pkg/subpkg/__init__.py", "pkg/subpkg/submodule1.py", "pkg/subpkg/submodule2.py", "pkg/subpkg/deeper/__init__.py", "pkg/subpkg/deeper/core.py", "anotherpkg/__init__.py", "anotherpkg/helper.py", ]), "3.13", ); }); describe("getModuleFromFilePath", () => { test("should resolve module from file path for regular modules", () => { const mainModule = resolver.getModuleFromFilePath("main.py"); expect(mainModule.name).toBe("main"); expect(mainModule.fullName).toBe("main"); const utilsModule = resolver.getModuleFromFilePath("utils.py"); expect(utilsModule.name).toBe("utils"); expect(utilsModule.fullName).toBe("utils"); }); test("should resolve module from file path for packages", () => { const pkgModule = resolver.getModuleFromFilePath("pkg/__init__.py"); expect(pkgModule.name).toBe("pkg"); expect(pkgModule.fullName).toBe("pkg"); expect(pkgModule.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); }); test("should handle package path without __init__.py", () => { const pkgModule = resolver.getModuleFromFilePath("pkg"); expect(pkgModule.name).toBe("pkg"); expect(pkgModule.fullName).toBe("pkg"); }); test("should handle package path with trailing separator", () => { const pkgModule = resolver.getModuleFromFilePath(`pkg${SEPARATOR}`); expect(pkgModule.name).toBe("pkg"); expect(pkgModule.fullName).toBe("pkg"); }); test("should resolve module from file path for nested packages", () => { const subpkgModule = resolver.getModuleFromFilePath( "pkg/subpkg/__init__.py", ); expect(subpkgModule.name).toBe("subpkg"); expect(subpkgModule.fullName).toBe("pkg.subpkg"); expect(subpkgModule.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); const deeperModule = resolver.getModuleFromFilePath( "pkg/subpkg/deeper/__init__.py", ); expect(deeperModule.name).toBe("deeper"); expect(deeperModule.fullName).toBe("pkg.subpkg.deeper"); }); test("should resolve module from file path for modules in packages", () => { const moduleModule = resolver.getModuleFromFilePath("pkg/module1.py"); expect(moduleModule.name).toBe("module1"); expect(moduleModule.fullName).toBe("pkg.module1"); const submoduleModule = resolver.getModuleFromFilePath( "pkg/subpkg/submodule1.py", ); expect(submoduleModule.name).toBe("submodule1"); expect(submoduleModule.fullName).toBe("pkg.subpkg.submodule1"); const coreModule = resolver.getModuleFromFilePath( "pkg/subpkg/deeper/core.py", ); expect(coreModule.name).toBe("core"); expect(coreModule.fullName).toBe("pkg.subpkg.deeper.core"); }); test("should handle module path without .py extension", () => { const moduleModule = resolver.getModuleFromFilePath("pkg/module1"); expect(moduleModule.name).toBe("module1"); expect(moduleModule.fullName).toBe("pkg.module1"); }); test("should throw an error for non-existent modules", () => { expect(() => { resolver.getModuleFromFilePath("nonexistent.py"); }).toThrow(); expect(() => { resolver.getModuleFromFilePath("pkg/nonexistent.py"); }).toThrow(); }); test("should use cache for repeat lookups", () => { const initialCacheSize = resolver["modulePathCache"].size; expect(initialCacheSize).toBe(0); // First lookup should cache the result const moduleFirst = resolver.getModuleFromFilePath("pkg/module1.py"); expect(moduleFirst.name).toBe("module1"); // Get the cache size before second lookup const cacheSizeBefore = resolver["modulePathCache"].size; expect(cacheSizeBefore).toBe(1); // Second lookup should use the cache const moduleSecond = resolver.getModuleFromFilePath("pkg/module1.py"); expect(moduleSecond).toBe(moduleFirst); // Same instance // Cache size should not have increased since we used the cached value expect(resolver["modulePathCache"].size).toBe(1); }); }); describe("Absolute Import Resolution", () => { test("should resolve top-level module imports", () => { // From main.py, import utils const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedUtils = resolver.resolveModule(mainModule, "utils"); expect(resolvedUtils).toBeDefined(); expect(resolvedUtils?.name).toBe("utils"); expect(resolvedUtils?.fullName).toBe("utils"); }); test("should resolve package imports", () => { // From main.py, import pkg const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedPkg = resolver.resolveModule(mainModule, "pkg"); expect(resolvedPkg).toBeDefined(); expect(resolvedPkg?.name).toBe("pkg"); expect(resolvedPkg?.fullName).toBe("pkg"); expect(resolvedPkg?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); }); test("should resolve submodule imports with dotted names", () => { // From main.py, import pkg.module1 const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedModule = resolver.resolveModule( mainModule, "pkg.module1", ); expect(resolvedModule).toBeDefined(); expect(resolvedModule?.name).toBe("module1"); expect(resolvedModule?.fullName).toBe("pkg.module1"); }); test("should resolve deeply nested imports", () => { // From main.py, import pkg.subpkg.deeper.core const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedCore = resolver.resolveModule( mainModule, "pkg.subpkg.deeper.core", ); expect(resolvedCore).toBeDefined(); expect(resolvedCore?.name).toBe("core"); expect(resolvedCore?.fullName).toBe("pkg.subpkg.deeper.core"); }); test("should resolve imports from within packages", () => { // From pkg/module1.py, import anotherpkg.helper const module1 = resolver.getModuleFromFilePath("pkg/module1.py"); const resolvedHelper = resolver.resolveModule( module1, "anotherpkg.helper", ); expect(resolvedHelper).toBeDefined(); expect(resolvedHelper?.name).toBe("helper"); expect(resolvedHelper?.fullName).toBe("anotherpkg.helper"); }); test("should handle absolute imports that don't exist", () => { const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedNonExistent = resolver.resolveModule( mainModule, "nonexistent", ); expect(resolvedNonExistent).toBeUndefined(); const resolvedNestedNonExistent = resolver.resolveModule( mainModule, "pkg.nonexistent", ); expect(resolvedNestedNonExistent).toBeUndefined(); }); test("should not resolve standard library modules", () => { const mainModule = resolver.getModuleFromFilePath("main.py"); // Test with common stdlib modules const resolvedOs = resolver.resolveModule(mainModule, "os"); expect(resolvedOs).toBeUndefined(); const resolvedSys = resolver.resolveModule(mainModule, "sys"); expect(resolvedSys).toBeUndefined(); const resolvedJson = resolver.resolveModule(mainModule, "json"); expect(resolvedJson).toBeUndefined(); }); test("should handle circular imports", () => { // From main.py, import main (itself) const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedSelf = resolver.resolveModule(mainModule, "main"); // Self-imports should return undefined to avoid circular references expect(resolvedSelf).toBeUndefined(); }); test("should use cache for repeated resolution", () => { const mainModule = resolver.getModuleFromFilePath("main.py"); // First resolution should cache the result const utils1 = resolver.resolveModule(mainModule, "utils"); expect(utils1).toBeDefined(); // Get the cache size before second resolution const cacheSizeBefore = resolver["importResolutionCache"].size; expect(cacheSizeBefore).toBe(1); // Second resolution of the same import should use the cache const utils2 = resolver.resolveModule(mainModule, "utils"); expect(utils2).toBe(utils1); // Same instance // Cache size should not have increased since we used the cached value expect(resolver["importResolutionCache"].size).toBe(1); }); }); describe("Relative Import Resolution", () => { test("should resolve same-level relative imports", () => { // From pkg/module1.py, import .module2 const module1 = resolver.getModuleFromFilePath("pkg/module1.py"); const resolvedModule2 = resolver.resolveModule(module1, ".module2"); expect(resolvedModule2).toBeDefined(); expect(resolvedModule2?.name).toBe("module2"); expect(resolvedModule2?.fullName).toBe("pkg.module2"); }); test("should resolve parent-level relative imports", () => { // From pkg/subpkg/submodule1.py, import ..module1 const submodule1 = resolver.getModuleFromFilePath( "pkg/subpkg/submodule1.py", ); const resolvedModule1 = resolver.resolveModule(submodule1, "..module1"); expect(resolvedModule1).toBeDefined(); expect(resolvedModule1?.name).toBe("module1"); expect(resolvedModule1?.fullName).toBe("pkg.module1"); }); test("should resolve multiple-level parent relative imports", () => { // From pkg/subpkg/deeper/core.py, import ...module1 const core = resolver.getModuleFromFilePath( "pkg/subpkg/deeper/core.py", ); const resolvedModule1 = resolver.resolveModule(core, "...module1"); expect(resolvedModule1).toBeDefined(); expect(resolvedModule1?.name).toBe("module1"); expect(resolvedModule1?.fullName).toBe("pkg.module1"); }); test("should resolve relative imports to the root level", () => { // From pkg/subpkg/deeper/core.py, import ....utils const core = resolver.getModuleFromFilePath( "pkg/subpkg/deeper/core.py", ); const resolvedUtils = resolver.resolveModule(core, "....utils"); expect(resolvedUtils).toBeDefined(); expect(resolvedUtils?.name).toBe("utils"); expect(resolvedUtils?.fullName).toBe("utils"); }); test("should resolve relative imports with subpaths", () => { // From pkg/module1.py, import .subpkg.submodule1 const module1 = resolver.getModuleFromFilePath("pkg/module1.py"); const resolvedSubmodule = resolver.resolveModule( module1, ".subpkg.submodule1", ); expect(resolvedSubmodule).toBeDefined(); expect(resolvedSubmodule?.name).toBe("submodule1"); expect(resolvedSubmodule?.fullName).toBe("pkg.subpkg.submodule1"); }); test("should handle relative imports to non-existent modules", () => { const module1 = resolver.getModuleFromFilePath("pkg/module1.py"); const resolvedNonExistent = resolver.resolveModule( module1, ".nonexistent", ); expect(resolvedNonExistent).toBeUndefined(); }); test("should handle too many dots in relative imports", () => { // If we go beyond the root level with too many dots const module1 = resolver.getModuleFromFilePath("pkg/module1.py"); const resolvedTooManyDots = resolver.resolveModule( module1, "....toomany", ); expect(resolvedTooManyDots).toBeUndefined(); }); test("should handle empty remainder in relative imports", () => { // Just dots means import the package itself at that level // From pkg/subpkg/submodule1.py, import .. const submodule1 = resolver.getModuleFromFilePath( "pkg/subpkg/submodule1.py", ); const resolvedParentPackage = resolver.resolveModule(submodule1, ".."); expect(resolvedParentPackage).toBeDefined(); expect(resolvedParentPackage?.name).toBe("pkg"); expect(resolvedParentPackage?.fullName).toBe("pkg"); }); test("should use cache for repeated relative imports", () => { const submodule1 = resolver.getModuleFromFilePath( "pkg/subpkg/submodule1.py", ); // First resolution const module1 = resolver.resolveModule(submodule1, "..module1"); expect(module1).toBeDefined(); // Get the cache size before second resolution const cacheSizeBefore = resolver["importResolutionCache"].size; expect(cacheSizeBefore).toBe(1); // Second resolution should use cache const module1Again = resolver.resolveModule(submodule1, "..module1"); expect(module1Again).toBe(module1); // Same instance // Cache size should not have increased since we used the cached value expect(resolver["importResolutionCache"].size).toBe(1); }); }); describe("Edge Cases and Special Scenarios", () => { test("should not allow circular references", () => { // Test various patterns that could create circular references const mainModule = resolver.getModuleFromFilePath("main.py"); expect(resolver.resolveModule(mainModule, "main")).toBeUndefined(); const pkgInit = resolver.getModuleFromFilePath("pkg/__init__.py"); expect(resolver.resolveModule(pkgInit, "pkg")).toBeUndefined(); const submodule = resolver.getModuleFromFilePath( "pkg/subpkg/submodule1.py", ); expect( resolver.resolveModule(submodule, "..subpkg.submodule1"), ).toBeUndefined(); }); test("should handle mixed path separators", () => { // Test with a mix of forward and backward slashes const mixedPath = "pkg/subpkg\\submodule1.py".replace(/\\/g, SEPARATOR); const module = resolver.getModuleFromFilePath(mixedPath); expect(module.name).toBe("submodule1"); expect(module.fullName).toBe("pkg.subpkg.submodule1"); }); test("should resolve imports when importing a package", () => { // From main.py, import pkg.subpkg const mainModule = resolver.getModuleFromFilePath("main.py"); const subpkg = resolver.resolveModule(mainModule, "pkg.subpkg"); expect(subpkg).toBeDefined(); expect(subpkg?.name).toBe("subpkg"); expect(subpkg?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(subpkg?.fullName).toBe("pkg.subpkg"); }); test("should handle namespace packages (PEP 420)", () => { // Python 3.3+ allows namespace packages without __init__.py const resolver = new PythonModuleResolver( new Set(["main.py", "namespace/pkg/module.py"]), "3.13", ); // Should create implicit namespace package const root = resolver.pythonModule; const namespaceModule = root.children.get("namespace"); expect(namespaceModule).toBeDefined(); expect(namespaceModule?.type).toBe(PYTHON_NAMESPACE_MODULE_TYPE); const pkgModule = namespaceModule?.children.get("pkg"); expect(pkgModule).toBeDefined(); expect(pkgModule?.type).toBe(PYTHON_NAMESPACE_MODULE_TYPE); // Should be able to resolve the module const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedModule = resolver.resolveModule( mainModule, "namespace.pkg.module", ); expect(resolvedModule).toBeDefined(); expect(resolvedModule?.name).toBe("module"); expect(resolvedModule?.fullName).toBe("namespace.pkg.module"); }); test("should handle special file names", () => { // Files with names matching keywords or special patterns const resolver = new PythonModuleResolver( new Set([ "main.py", "special/class.py", "special/_private.py", "special/with.py", ]), "3.13", ); // These names are valid Python module names despite being keywords const classModule = resolver.getModuleFromFilePath("special/class.py"); expect(classModule.name).toBe("class"); const privateModule = resolver.getModuleFromFilePath( "special/_private.py", ); expect(privateModule.name).toBe("_private"); // Import paths should resolve const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedClass = resolver.resolveModule( mainModule, "special.class", ); expect(resolvedClass).toBeDefined(); expect(resolvedClass?.name).toBe("class"); const resolvedPrivate = resolver.resolveModule( mainModule, "special._private", ); expect(resolvedPrivate).toBeDefined(); expect(resolvedPrivate?.name).toBe("_private"); }); test("should handle importing from deeply nested paths", () => { // Tests the from X.Y.Z import A syntax equivalent const resolver = new PythonModuleResolver( new Set([ "main.py", "deep/a/__init__.py", "deep/a/b/__init__.py", "deep/a/b/c/__init__.py", "deep/a/b/c/d.py", ]), "3.13", ); const mainModule = resolver.getModuleFromFilePath("main.py"); // Test importing d from deep.a.b.c const resolvedD = resolver.resolveModule(mainModule, "deep.a.b.c.d"); expect(resolvedD).toBeDefined(); expect(resolvedD?.name).toBe("d"); expect(resolvedD?.fullName).toBe("deep.a.b.c.d"); // Test importing the package itself const resolvedC = resolver.resolveModule(mainModule, "deep.a.b.c"); expect(resolvedC).toBeDefined(); expect(resolvedC?.type).toBe(PYTHON_PACKAGE_MODULE_TYPE); expect(resolvedC?.fullName).toBe("deep.a.b.c"); }); test("should handle imports with ..* patterns", () => { const resolver = new PythonModuleResolver( new Set([ "patterns/a/__init__.py", "patterns/a/b/__init__.py", "patterns/a/b/module.py", "patterns/a/other.py", ]), "3.13", ); // Get the module point of view const moduleFile = resolver.getModuleFromFilePath( "patterns/a/b/module.py", ); // Test relative import with dots followed by wildcard-like name // In Python, 'from .. import *' would import everything from parent // Here we're testing "..other" - the parent's "other" module const resolvedOther = resolver.resolveModule(moduleFile, "..other"); expect(resolvedOther).toBeDefined(); expect(resolvedOther?.name).toBe("other"); expect(resolvedOther?.fullName).toBe("patterns.a.other"); }); test("should handle resolution of _name modules", () => { // Modules starting with underscore are treated as internal/private const resolver = new PythonModuleResolver( new Set([ "main.py", "pkg/__init__.py", "pkg/_internal.py", "pkg/public.py", ]), "3.13", ); // Get the internal module const internalModule = resolver.getModuleFromFilePath( "pkg/_internal.py", ); expect(internalModule.name).toBe("_internal"); // Should be able to resolve _name from outside const mainModule = resolver.getModuleFromFilePath("main.py"); const resolvedInternal = resolver.resolveModule( mainModule, "pkg._internal", ); expect(resolvedInternal).toBeDefined(); expect(resolvedInternal?.name).toBe("_internal"); }); }); }); }); ================================================ FILE: src/languagePlugins/python/moduleResolver/index.ts ================================================ import pythonStdLib from "../../../scripts/generate_python_stdlib_list/output.json" with { type: "json", }; import { PYTHON_MODULE_TYPE, PYTHON_NAMESPACE_MODULE_TYPE, PYTHON_PACKAGE_MODULE_TYPE, type PythonModule, type PythonModuleType, } from "./types.ts"; /** * PythonModuleResolver builds a hierarchical tree structure representing * the modules of a Python project based on its file structure. * * It processes a set of files (each with a file path and a parsed syntax tree) * to create a tree where: * - Directories become namespace modules. * - __init__.py files are interpreted as packages (using the parent folder as the package name). * - Other .py files become regular modules. * * The class also provides methods to resolve internal module import statements, * handling both relative and absolute imports within the project. * (Note: Imports to external libraries are not resolved.) * * For performance optimization, this class implements caching for module path resolution * to avoid redundant lookups when the same file path is repeatedly requested. */ export class PythonModuleResolver { /** * The root PythonModule representing the top-level namespace of the project. * This module serves as the entry point for traversing the module tree. * * All project modules will be children or descendants of this root module. */ public pythonModule: PythonModule; /** * The version of Python being used (only major). */ public pythonVersion: string; /** * Set containing standard library module names for faster lookups. * * Used to quickly identify imports from the Python standard library versus * project modules or third-party dependencies. */ private stdModuleSet: Set; /** * Cache that maps file paths to their corresponding resolved PythonModule objects. * This improves performance by avoiding redundant resolution for frequently accessed modules. * * Key: filesystem path of the module * Value: resolved PythonModule object */ private modulePathCache: Map; /** * Cache that maps import strings to their resolved module within a specific context. * Format: `${currentModule.fullName}:${importString}` → resolvedModule * * This caches the results of import resolution to avoid redundant lookups, * taking into account both the module being imported and the context from which * it's being imported (for handling relative imports correctly). */ private importResolutionCache: Map; /** * Constructs a PythonModuleResolver. * * @param files - A mapping where each entry represents a file in the project, * containing its file system path and its parsed syntax tree. * @param pythonVersion - The version of Python being used (only major). */ constructor(filePaths: Set, pythonVersion: string) { this.pythonModule = this.buildModuleMap(filePaths); this.pythonVersion = pythonVersion; this.stdModuleSet = this.getPythonStdModules(pythonVersion); // Initialize empty caches this.modulePathCache = new Map(); this.importResolutionCache = new Map(); } private getPythonStdModules(version: string) { // Extract major.minor version const versionMatch = version.match(/^(\d+)(?:\.(\d+))?/); if (!versionMatch) { throw new Error(`Invalid Python version format: ${version}`); } const major = versionMatch[1]; const minor = versionMatch[2] || "0"; const pythonMajorVersion = `${major}.${minor}`; const stdLib = pythonStdLib as Record; const stdModuleList = stdLib[pythonMajorVersion]; if (!stdModuleList) { console.warn( `No standard library modules found for Python version ${pythonMajorVersion}. Using standard library for Python 3.9 as a fallback`, ); const fallbackStdLib = pythonStdLib["3.9"]; if (!fallbackStdLib) { throw new Error( `No standard library modules found for Python version 3.9.`, ); } return new Set(fallbackStdLib); } return new Set(stdModuleList); } /** * Constructs the hierarchical module tree for the project. * * The method iterates over each file provided and: * - Splits the file path into directory and file name components. * - Interprets __init__.py files as package indicators by removing the file name. * - Strips the .py extension from regular module files. * - Builds or reuses intermediate namespace modules corresponding to directories. * - Sets the final module's type and full file path. * * @returns The root PythonModule representing the project's top-level namespace. */ private buildModuleMap(filePaths: Set): PythonModule { const root: PythonModule = { name: "", fullName: "", path: "", type: PYTHON_NAMESPACE_MODULE_TYPE, children: new Map(), parent: undefined, }; filePaths.forEach((filePath) => { // Split the file path into directories and file name. const parts = filePath.split("/"); // Default to a normal module unless it is an __init__.py file. let endModuleType: PythonModuleType = PYTHON_MODULE_TYPE; if (parts[parts.length - 1] === "__init__.py") { endModuleType = PYTHON_PACKAGE_MODULE_TYPE; // Remove the "__init__.py" segment to represent the package as its directory. parts.pop(); } else { // Remove the .py extension from the module name. parts[parts.length - 1] = parts[parts.length - 1].slice(0, -3); } let currentFullName = ""; let currentPath = ""; let currentModule = root; // Traverse or create the module tree based on each part of the path. parts.forEach((part) => { currentFullName = currentFullName ? `${currentFullName}.${part}` : part; currentPath = currentPath ? `${currentPath}/${part}` : part; const existingModule = currentModule.children.get(part); if (existingModule) { currentModule = existingModule; } else { const newModule: PythonModule = { name: part, fullName: currentFullName, path: currentPath, // Initially mark as a namespace until further refined. type: PYTHON_NAMESPACE_MODULE_TYPE, children: new Map(), parent: currentModule, }; currentModule.children.set(part, newModule); currentModule = newModule; } }); // Update the final module with the correct type and its original file path. currentModule.type = endModuleType; currentModule.path = filePath; }); return root; } /** * Retrieves the PythonModule associated with a given file path. * * This method uses a caching mechanism to improve performance when the same * file path is requested multiple times. On the first request for a path, * the module is resolved and cached; subsequent requests for the same path * return the cached result without re-resolving. * * For file paths ending in __init__.py, the package directory is returned * (by stripping off the __init__.py segment). For other .py files, * the .py extension is removed before traversal. * * @param filePath - The file system path to resolve. * @returns The matching PythonModule if found. * @throws Error if the module could not be found in the project. */ public getModuleFromFilePath(filePath: string): PythonModule { // Check if this path has already been resolved and return from cache if available if (this.modulePathCache.has(filePath)) { return this.modulePathCache.get(filePath) as PythonModule; } // Treat __init__.py as indicating the package's directory. if (filePath.endsWith(`/__init__.py`)) { filePath = filePath.slice(0, -"__init__.py".length); // Remove trailing separator if it exists. if (filePath.endsWith("/")) { filePath = filePath.slice(0, -"/".length); } } else if (filePath.endsWith(".py")) { // Remove the .py extension from module files. filePath = filePath.slice(0, -3); } let currentNode = this.pythonModule; for (const part of filePath.split("/")) { if (part === "") continue; // Skip empty segments. const candidateNode = currentNode.children.get(part); if (!candidateNode) { throw new Error( `Module not found for path: ${filePath}. Check if the module is part of the project.`, ); } currentNode = candidateNode; } // Cache the result before returning this.modulePathCache.set(filePath, currentNode); return currentNode; } /** * Resolves an internal module import from a module file. * * This method determines whether the provided import string is relative * (i.e. starts with ".") or absolute, and then delegates the resolution * to the corresponding helper method. This function exclusively handles * imports internal to the project; external libraries are not processed. * * Implements caching to avoid redundant resolution of the same imports. * * @param currentModule - The module where the import occurs. * @param moduleName - The import string (e.g. ".helper" or "project.module"). * @returns The resolved PythonModule if found, or undefined otherwise. */ public resolveModule( currentModule: PythonModule, moduleName: string, ): PythonModule | undefined { // Create a cache key using the current module's name and the import string const cacheKey = `${currentModule.fullName}:${moduleName}`; // Check if we've already resolved this import in this context if (this.importResolutionCache.has(cacheKey)) { return this.importResolutionCache.get(cacheKey); } const pythonModule = moduleName.startsWith(".") ? this.resolveRelativeModule(currentModule, moduleName) : this.resolveAbsoluteImport(currentModule, moduleName); // Don't return self-references const result = pythonModule === currentModule ? undefined : pythonModule; // Cache the result this.importResolutionCache.set(cacheKey, result); return result; } /** * Resolves a relative import for a given module. * * Relative import strings use leading dots to indicate how many levels up * in the package hierarchy to traverse before locating the target module. * The remaining dotted path is then followed from the base package. * * Examples: * - ".helper" resolves to a sibling module named "helper". * - "..module" moves up one level and resolves to a module named "module". * * @param currentModule - The module performing the import. * @param moduleName - The relative import string (e.g. ".helper" or "..module.sub"). * @returns The corresponding PythonModule if found, or undefined otherwise. */ private resolveRelativeModule( currentModule: PythonModule, moduleName: string, ): PythonModule | undefined { // Count the number of leading dots to determine the package level. let level = 0; while (moduleName[level] === ".") { level++; } const remainder = moduleName.slice(level); // If the current module is a regular file (not a package), start from its parent. let baseModule = currentModule; if (currentModule.path && !currentModule.path.endsWith("__init__.py")) { if (currentModule.parent) { baseModule = currentModule.parent; } } // Traverse upward in the hierarchy based on the number of dots. for (let i = 1; i < level; i++) { if (!baseModule.parent) return undefined; baseModule = baseModule.parent; } // If no additional module path is provided, return the base package. if (!remainder) return baseModule; // Traverse downward using the remaining dotted path. const parts = remainder.split("."); let resolved: PythonModule | undefined = baseModule; for (const part of parts) { resolved = resolved?.children.get(part); if (!resolved) return undefined; } return resolved; } /** * Resolves an absolute import starting from the current module's package context. * * The method splits the import string into its dotted components and then * traverses upward from the current module's package, checking each ancestor's * children for a matching module path. * * For example, an import like "module.sub" will be searched for in the current * package and, if not found, in higher-level packages. * * Uses intermediate caching to improve performance when resolving deep import paths. * * @param currentModule - The module performing the import. * @param moduleName - The absolute import string (e.g. "module.sub" or "project.utils.helper"). * @returns The resolved PythonModule if it exists, or undefined otherwise. */ private resolveAbsoluteImport( currentModule: PythonModule, moduleName: string, ): PythonModule | undefined { // Check if it's a standard library module using the Set for faster lookup if (this.stdModuleSet.has(moduleName)) { return undefined; } // Split import into segments const parts = moduleName.split("."); // Cache for intermediate resolution results during this method call // Maps partial paths to their resolved modules to avoid redundant lookups const partialResolutionCache = new Map(); if (currentModule.path && !currentModule.path.endsWith("__init__.py")) { if (currentModule.parent) { currentModule = currentModule.parent; } } // Walk upward in the module hierarchy to find a matching candidate let ancestor: PythonModule | undefined = currentModule; while (ancestor) { let candidate: PythonModule | undefined = ancestor; let partialName = ""; // Try to resolve each part of the import path for (const part of parts) { // Build the partial import path as we go partialName = partialName ? `${partialName}.${part}` : part; // Check if we already resolved this partial path from the current ancestor const cacheKey = `${ancestor.fullName}:${partialName}`; if (partialResolutionCache.has(cacheKey)) { candidate = partialResolutionCache.get(cacheKey); continue; } // If not in cache, resolve the next part const nextCandidate = candidate?.children.get(part); candidate = nextCandidate; // Cache this partial resolution result partialResolutionCache.set(cacheKey, candidate); if (!candidate) { // Cache negative result to avoid redundant lookups partialResolutionCache.set(cacheKey, undefined); break; } } if (candidate) { return candidate; } ancestor = ancestor.parent; } return undefined; } } ================================================ FILE: src/languagePlugins/python/moduleResolver/types.ts ================================================ export const PYTHON_MODULE_TYPE = "module"; export const PYTHON_PACKAGE_MODULE_TYPE = "package"; export const PYTHON_NAMESPACE_MODULE_TYPE = "namespace"; /** * Represents the different types of Python modules that can exist in a project. * * - module: A standard Python file (.py) * - package: A directory with an __init__.py file * - namespace: A directory containing Python modules/packages but without an __init__.py */ export type PythonModuleType = | typeof PYTHON_MODULE_TYPE | typeof PYTHON_PACKAGE_MODULE_TYPE | typeof PYTHON_NAMESPACE_MODULE_TYPE; /** * Represents a Python module or package within a project's module tree. * * Each module has a simple name, a full dotted path name (fullName), * a file system path, a type (regular module, package, or namespace), * a collection of child modules (if any), and an optional reference to its parent module. * * Examples: * - Regular module: example.py (type: "module") * - Package: directory with __init__.py (type: "package") * - Namespace package: directory without __init__.py (type: "namespace") */ export interface PythonModule { /** The simple name of the module (e.g., 'math' from 'mypackage.math') */ name: string; /** The full dotted name of the module (e.g., 'mypackage.math') */ fullName: string; /** The filesystem path to the module */ path: string; /** The type of module (regular, package, or namespace) */ type: PythonModuleType; /** Map of child modules (relevant for packages and namespace packages) */ children: Map; /** Reference to the parent module (undefined for top-level modules) */ parent?: PythonModule; } ================================================ FILE: src/languagePlugins/python/symbolExtractor/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type Parser from "tree-sitter"; import { PythonSymbolExtractor } from "./index.ts"; import { PythonExportExtractor } from "../exportExtractor/index.ts"; import { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PythonItemResolver } from "../itemResolver/index.ts"; import { PythonImportExtractor } from "../importExtractor/index.ts"; import { PythonUsageResolver } from "../usageResolver/index.ts"; import { pythonLanguage, pythonParser, } from "../../../helpers/treeSitter/parsers.ts"; import type { localConfigSchema } from "../../../cli/middlewares/napiConfig.ts"; import type z from "zod"; import { generatePythonDependencyManifest } from "../../../manifest/dependencyManifest/python/index.ts"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; describe("PythonSymbolExtractor", () => { // Helper to create a map of parsed files function createParsedFiles( files: Map, ) { const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const { path, content } of files.values()) { const rootNode = pythonParser.parse(content, undefined, { bufferSize: content.length + 10, }).rootNode; parsedFiles.set(path, { path, rootNode }); } return parsedFiles; } // Helper to create a dependency manifest for testing function createDependencyManifest( files: Map, ): DependencyManifest { const dependencyManifest = generatePythonDependencyManifest(files, { language: pythonLanguage, python: { version: "3.10", }, outDir: "napi_out", project: { include: [], exclude: [], }, } as z.infer); return dependencyManifest; } function createSymbolExtractor( files: Map, ) { const parsedFiles = createParsedFiles(files); const dependencyManifest = createDependencyManifest(files); const exportExtractor = new PythonExportExtractor( pythonParser, parsedFiles, ); const importExtractor = new PythonImportExtractor( pythonParser, parsedFiles, ); const moduleResolver = new PythonModuleResolver( new Set(parsedFiles.keys()), "3.10", ); const itemResolver = new PythonItemResolver( exportExtractor, importExtractor, moduleResolver, ); const usageResolver = new PythonUsageResolver( pythonParser, exportExtractor, ); const symbolExtractor = new PythonSymbolExtractor( pythonParser, parsedFiles, exportExtractor, importExtractor, moduleResolver, itemResolver, usageResolver, dependencyManifest, ); return symbolExtractor; } // Basic test setup test("should extract a class and its dependencies", () => { // Create test files with a class and its dependencies const files = new Map([ [ "main.py", { path: "main.py", content: ` from utils import Helper class MyClass: def __init__(self): self.helper = Helper() def do_something(self): return self.helper.help() def foo(): return "foo" `.trim(), }, ], [ "utils.py", { path: "utils.py", content: ` class Helper: def help(self): return "Helping..." `.trim(), }, ], ]); const symbolExtractor = createSymbolExtractor(files); const symbolsToExtract = new Map([ [ "main.py", { filePath: "main.py", symbols: new Set(["MyClass"]), }, ], ]); const result = symbolExtractor.extractSymbol(symbolsToExtract); expect(result).toBeDefined(); expect(result.size).toBe(2); expect(result.get("main.py")).toBeDefined(); expect(result.get("main.py")?.content.trim()).toEqual( ` from utils import Helper class MyClass: def __init__(self): self.helper = Helper() def do_something(self): return self.helper.help() `.trim(), ); expect(result.get("utils.py")).toBeDefined(); expect(result.get("utils.py")?.content.trim()).toEqual( ` class Helper: def help(self): return "Helping..." `.trim(), ); }); test("should extract multiple symbols with nested dependencies", () => { const files = new Map([ [ "app.py", { path: "app.py", content: ` from services.user_service import UserService from services.auth_service import AuthService from models.user import User class Application: def __init__(self): self.user_service = UserService() self.auth_service = AuthService() def register_user(self, username, password): user = User(username) self.auth_service.set_password(user, password) return self.user_service.save_user(user) def authenticate(self, username, password): return self.auth_service.verify(username, password) def foo(): return "foo" `.trim(), }, ], [ "services/user_service.py", { path: "services/user_service.py", content: ` from models.user import User from database.repository import Repository class UserService: def __init__(self): self.repository = Repository("users") def save_user(self, user): return self.repository.save(user) def get_user(self, username): return self.repository.find_one({"username": username}) def bar(): return "bar" `.trim(), }, ], [ "services/auth_service.py", { path: "services/auth_service.py", content: ` from models.user import User from services.user_service import UserService import hashlib class AuthService: def __init__(self): self.user_service = UserService() def set_password(self, user, password): user.password_hash = hashlib.sha256(password.encode()).hexdigest() def verify(self, username, password): user = self.user_service.get_user(username) if not user: return False password_hash = hashlib.sha256(password.encode()).hexdigest() return user.password_hash == password_hash `.trim(), }, ], [ "models/user.py", { path: "models/user.py", content: ` class User: def __init__(self, username): self.username = username self.password_hash = None self.is_active = True def deactivate(self): self.is_active = False def __str__(self): return f"User({self.username})" `.trim(), }, ], [ "database/repository.py", { path: "database/repository.py", content: ` class Repository: def __init__(self, collection_name): self.collection_name = collection_name self.data = {} def save(self, entity): key = getattr(entity, "username", id(entity)) self.data[key] = entity return entity def find_one(self, query): username = query.get("username") if username in self.data: return self.data[username] return None `.trim(), }, ], ]); const symbolExtractor = createSymbolExtractor(files); const symbolsToExtract = new Map([ [ "app.py", { filePath: "app.py", symbols: new Set(["Application"]), }, ], ]); const result = symbolExtractor.extractSymbol(symbolsToExtract); expect(result).toBeDefined(); expect(result.size).toBe(5); expect(result.get("app.py")).toBeDefined(); expect(result.get("app.py")?.content.trim()).toEqual( ` from services.user_service import UserService from services.auth_service import AuthService from models.user import User class Application: def __init__(self): self.user_service = UserService() self.auth_service = AuthService() def register_user(self, username, password): user = User(username) self.auth_service.set_password(user, password) return self.user_service.save_user(user) def authenticate(self, username, password): return self.auth_service.verify(username, password) `.trim(), ); expect(result.get("services/user_service.py")).toBeDefined(); expect(result.get("services/user_service.py")?.content.trim()).toEqual( ` from models.user import User from database.repository import Repository class UserService: def __init__(self): self.repository = Repository("users") def save_user(self, user): return self.repository.save(user) def get_user(self, username): return self.repository.find_one({"username": username}) `.trim(), ); expect(result.get("services/auth_service.py")).toBeDefined(); expect(result.get("services/auth_service.py")?.content.trim()).toEqual( ` from models.user import User from services.user_service import UserService import hashlib class AuthService: def __init__(self): self.user_service = UserService() def set_password(self, user, password): user.password_hash = hashlib.sha256(password.encode()).hexdigest() def verify(self, username, password): user = self.user_service.get_user(username) if not user: return False password_hash = hashlib.sha256(password.encode()).hexdigest() return user.password_hash == password_hash `.trim(), ); expect(result.get("models/user.py")).toBeDefined(); expect(result.get("models/user.py")?.content.trim()).toEqual( ` class User: def __init__(self, username): self.username = username self.password_hash = None self.is_active = True def deactivate(self): self.is_active = False def __str__(self): return f"User({self.username})" `.trim(), ); expect(result.get("database/repository.py")).toBeDefined(); expect(result.get("database/repository.py")?.content.trim()).toEqual( ` class Repository: def __init__(self, collection_name): self.collection_name = collection_name self.data = {} def save(self, entity): key = getattr(entity, "username", id(entity)) self.data[key] = entity return entity def find_one(self, query): username = query.get("username") if username in self.data: return self.data[username] return None `.trim(), ); }); test("should remove invalid normal imports", () => { const files = new Map([ [ "main.py", { path: "main.py", content: ` import valid_module import invalid_module class MyClass: def __init__(self): self.helper = valid_module.valid_function() class AnotherClass: def __init__(self): self.invalid = invalid_module.something() `.trim(), }, ], [ "valid_module.py", { path: "valid_module.py", content: ` def valid_function(): return "I'm valid" `.trim(), }, ], [ "invalid_module.py", { path: "invalid_module.py", content: ` def something(): return "I'll be removed" `.trim(), }, ], ]); const symbolExtractor = createSymbolExtractor(files); // Only extract MyClass which depends on Helper but not on invalid_module const symbolsToExtract = new Map([ [ "main.py", { filePath: "main.py", symbols: new Set(["MyClass"]), }, ], ]); const result = symbolExtractor.extractSymbol(symbolsToExtract); expect(result.size).toBe(2); expect(result.get("main.py")).toBeDefined(); expect(result.get("main.py")?.content.trim()).toEqual( ` import valid_module class MyClass: def __init__(self): self.helper = valid_module.valid_function() `.trim(), ); expect(result.get("valid_module.py")).toBeDefined(); expect(result.get("valid_module.py")?.content.trim()).toEqual( `def valid_function(): return "I'm valid" `.trim(), ); }); test("should remove invalid from imports", () => { const files = new Map([ [ "main.py", { path: "main.py", content: ` from valid_module import valid_function from invalid_module import something class MyClass: def __init__(self): self.helper = valid_function() class AnotherClass: def __init__(self): self.invalid = something() `.trim(), }, ], [ "valid_module.py", { path: "valid_module.py", content: ` def valid_function(): return "I'm valid" `.trim(), }, ], [ "invalid_module.py", { path: "invalid_module.py", content: ` def something(): return "I'll be removed" `.trim(), }, ], ]); const symbolExtractor = createSymbolExtractor(files); const symbolsToExtract = new Map([ [ "main.py", { filePath: "main.py", symbols: new Set(["MyClass"]), }, ], ]); const result = symbolExtractor.extractSymbol(symbolsToExtract); expect(result.size).toBe(2); expect(result.get("main.py")).toBeDefined(); expect(result.get("main.py")?.content.trim()).toEqual( `from valid_module import valid_function class MyClass: def __init__(self): self.helper = valid_function() `.trim(), ); expect(result.get("valid_module.py")).toBeDefined(); expect(result.get("valid_module.py")?.content.trim()).toEqual( `def valid_function(): return "I'm valid" `.trim(), ); }); }); ================================================ FILE: src/languagePlugins/python/symbolExtractor/index.ts ================================================ import Parser from "tree-sitter"; import { PythonExportExtractor } from "../exportExtractor/index.ts"; import { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PythonItemResolver } from "../itemResolver/index.ts"; import { PythonImportExtractor } from "../importExtractor/index.ts"; import { PythonUsageResolver } from "../usageResolver/index.ts"; import type { DependencyManifest } from "../../../manifest/dependencyManifest/types.ts"; import { removeIndexesFromSourceCode } from "../../../helpers/sourceCode/index.ts"; import { FROM_IMPORT_STATEMENT_TYPE, NORMAL_IMPORT_STATEMENT_TYPE, } from "../importExtractor/types.ts"; /** * Python Symbol Extractor * * This class extracts a Python symbol and all its dependencies from a project * while preserving the original project structure. */ export class PythonSymbolExtractor { private originalFiles: Map< string, { path: string; rootNode: Parser.SyntaxNode } >; private parser: Parser; private exportExtractor: PythonExportExtractor; public importExtractor: PythonImportExtractor; public moduleResolver: PythonModuleResolver; public itemResolver: PythonItemResolver; public usageResolver: PythonUsageResolver; private dependencyManifest: DependencyManifest; private errorNodeQuery: Parser.Query; private processedSymbols: Set = new Set(); /** * Creates a new Python Symbol Extractor */ constructor( parser: Parser, originalFiles: Map, exportExtractor: PythonExportExtractor, importExtractor: PythonImportExtractor, moduleResolver: PythonModuleResolver, itemResolver: PythonItemResolver, usageResolver: PythonUsageResolver, dependencyManifest: DependencyManifest, ) { this.parser = parser; this.originalFiles = originalFiles; this.exportExtractor = exportExtractor; this.importExtractor = importExtractor; this.moduleResolver = moduleResolver; this.itemResolver = itemResolver; this.usageResolver = usageResolver; this.dependencyManifest = dependencyManifest; this.errorNodeQuery = new Parser.Query( this.parser.getLanguage(), "(ERROR) @error", ); } /** * Extracts a symbol and all its dependencies from the project * * @param symbolsToExtract A list of symbols to extract * @returns Symbol extraction result */ public extractSymbol( symbolsMap: Map< string, { filePath: string; symbols: Set; } >, ) { // 1. Identify symbols to keep and their dependencies recursively console.info("Finding dependencies for all symbols to extract..."); const symbolsToKeep = this.identifySymbolsAndDependencies(symbolsMap); let totalSymbols = 0; for (const { symbols } of symbolsToKeep.values()) { totalSymbols += symbols.size; } console.info( `Found ${totalSymbols} symbols to keep across ${symbolsToKeep.size} files`, ); // 2. Extract all the symbols console.info(`Extracting files in-memory...`); const extractedFiles = this.extractFilesInMemory(symbolsToKeep); // 3. Clean error nodes console.info("Cleaning error nodes from files..."); this.cleanErrorNodes(extractedFiles); // 4. Remove invalid imports. console.info("Removing invalid imports..."); this.cleanImports(extractedFiles); // 5. Clean error nodes console.info("Cleaning error nodes from files..."); this.cleanErrorNodes(extractedFiles); console.info("Successfully extracted all symbols!"); // Return the extracted files return extractedFiles; } /** * Identifies symbols and their dependencies to keep */ private identifySymbolsAndDependencies( symbolsMap: Map< string, { filePath: string; symbols: Set; } >, ) { const symbolsToKeep = new Map< string, { filePath: string; symbols: Set; } >(); symbolsMap.values().forEach(({ filePath, symbols }) => { symbols.forEach((symbol) => { this.addSymbolAndDependencies(filePath, symbol, symbolsToKeep); }); }); return symbolsToKeep; } private addSymbolAndDependencies( filePath: string, symbolName: string, symbolsToKeep = new Map< string, { filePath: string; symbols: Set; } >(), ) { // Create a unique key for this symbol to track processing const symbolKey = `${filePath}:${symbolName}`; // If we've already processed this symbol, return to avoid circular dependency if (this.processedSymbols.has(symbolKey)) { return symbolsToKeep; } // Mark this symbol as being processed this.processedSymbols.add(symbolKey); // Add the symbol itself to the filesToKeep map let fileToKeep = symbolsToKeep.get(filePath); if (!fileToKeep) { fileToKeep = { filePath, symbols: new Set(), }; } fileToKeep.symbols.add(symbolName); symbolsToKeep.set(filePath, fileToKeep); const fileManifest = this.dependencyManifest[filePath]; if (!fileManifest) { throw new Error(`Could not find dependency file for ${filePath}`); } const symbolManifest = fileManifest.symbols[symbolName]; if (!symbolManifest) { throw new Error(`Could not find symbol manifest for ${symbolName}`); } for (const dependency of Object.values(symbolManifest.dependencies)) { // skip external dependencies if (dependency.isExternal) { continue; } // add the file to the filesToKeep map if (!symbolsToKeep.has(dependency.id)) { symbolsToKeep.set(dependency.id, { filePath: dependency.id, symbols: new Set(), }); } let fileToKeep = symbolsToKeep.get(dependency.id); if (!fileToKeep) { fileToKeep = { filePath: dependency.id, symbols: new Set(), }; } Object.values(dependency.symbols).forEach((depSymbol) => { // add the symbol and its dependencies to the filesToKeep map this.addSymbolAndDependencies(dependency.id, depSymbol, symbolsToKeep); }); } return symbolsToKeep; } /** * Extracts files in memory */ private extractFilesInMemory( symbolsToKeep: Map< string, { filePath: string; symbols: Set; } >, ) { const extractedFiles = new Map(); // Process each file to keep for (const { filePath, symbols } of symbolsToKeep.values()) { // Get the file from originalFiles using the exact file path const file = this.originalFiles.get(filePath); if (!file) { throw new Error(`Could not find file ${filePath} for extraction`); } // Get the full file content const fileContent = file.rootNode.text; // Get exported symbols const exports = this.exportExtractor.getSymbols(filePath); const indexesToRemove: { startIndex: number; endIndex: number }[] = []; for (const symbol of exports.symbols) { if (!symbols.has(symbol.id)) { for (const node of symbol.nodes) { indexesToRemove.push({ startIndex: node.startIndex, endIndex: node.endIndex, }); } } } const cleanedContent = removeIndexesFromSourceCode( fileContent, indexesToRemove, ); // Add the cleaned content to our extracted files extractedFiles.set(filePath, { path: filePath, content: cleanedContent, }); } return extractedFiles; } /** * Cleans error nodes from extracted files */ private cleanErrorNodes( extractedFiles: Map, ) { for (const [filePath, fileData] of extractedFiles.entries()) { let sourceCode = fileData.content; // Need to remove error nodes one at a time, as fixing one error // might make previously invalid code valid while (true) { // Parse the file content const tree = this.parser.parse(sourceCode); // Find error nodes const captures = this.errorNodeQuery.captures(tree.rootNode); if (captures.length === 0) { break; } const firstCapture = captures[0]; sourceCode = sourceCode.substring(0, firstCapture.node.startIndex) + sourceCode.substring(firstCapture.node.endIndex); } extractedFiles.set(filePath, { path: filePath, content: sourceCode, }); } } /** * Removes invalid imports from the extracted files * Invalid imports are those that used to resolve to internal symbols or modules * but no longer do after extraction. */ private cleanImports( extractedFiles: Map, ) { const extractedParsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const { path, content } of extractedFiles.values()) { const tree = this.parser.parse(content, undefined, { bufferSize: content.length + 10, }); extractedParsedFiles.set(path, { path, rootNode: tree.rootNode }); } const extractedFilesExportExtractor = new PythonExportExtractor( this.parser, extractedParsedFiles, ); const extractedFilesImportExtractor = new PythonImportExtractor( this.parser, extractedParsedFiles, ); const extractedFilesModuleResolver = new PythonModuleResolver( new Set(extractedParsedFiles.keys()), this.moduleResolver.pythonVersion, ); const extractedFilesItemResolver = new PythonItemResolver( extractedFilesExportExtractor, extractedFilesImportExtractor, extractedFilesModuleResolver, ); const extractedFilesUsageResolver = new PythonUsageResolver( this.parser, extractedFilesExportExtractor, ); for (const { path, content } of extractedFiles.values()) { const originalFile = this.originalFiles.get(path) as { path: string; rootNode: Parser.SyntaxNode; }; const extractedFile = extractedParsedFiles.get(path) as { path: string; rootNode: Parser.SyntaxNode; }; // Get all imports in the file const originalImportStatements = this.importExtractor.getImportStatements( originalFile.path, ); const extractedFilesImportStatements = extractedFilesImportExtractor .getImportStatements( extractedFile.path, ); const indexesToRemove: { startIndex: number; endIndex: number }[] = []; // Check each import statement to see if it's still valid for ( const extractedFilesImportStatement of extractedFilesImportStatements ) { if ( extractedFilesImportStatement.type === NORMAL_IMPORT_STATEMENT_TYPE ) { const indexesToRemoveForImport: { startIndex: number; endIndex: number; }[] = []; // Handle normal imports (import foo, import foo.bar) for (const member of extractedFilesImportStatement.members) { // resolve the module from the original file const originalFileModule = this.moduleResolver .getModuleFromFilePath(originalFile.path); const resolvedOriginalModule = this.moduleResolver.resolveModule( originalFileModule, member.identifierNode.text, ); if (!resolvedOriginalModule) { // If the module doesn't resolve, it's an external module // Check if the import is used in original code const originalUsageNode = this.usageResolver.getUsageNode( originalFile.rootNode, originalImportStatements.map((s) => s.node), member.aliasNode?.text || member.identifierNode.text, ); // Check if the import is used in extracted code const extractedUsageNode = extractedFilesUsageResolver .getUsageNode( extractedFile.rootNode, extractedFilesImportStatements.map((s) => s.node), member.aliasNode?.text || member.identifierNode.text, ); // If the import was used in original code but not in extracted code, remove it if ( originalUsageNode.length > 0 && extractedUsageNode.length === 0 ) { const startIndex = member.node.startIndex; let endIndex = member.node.endIndex; if ( member.node.nextSibling && member.node.nextSibling.type === "," ) { endIndex = member.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); } continue; } // resolve the module from the extracted file const extractedFileModule = extractedFilesModuleResolver .getModuleFromFilePath( extractedFile.path, ); const resolvedExtractedFileModule = extractedFilesModuleResolver .resolveModule( extractedFileModule, member.identifierNode.text, ); if (!resolvedExtractedFileModule) { // the module does not resolve anymore, we remove it const startIndex = member.node.startIndex; let endIndex = member.node.endIndex; if ( member.node.nextSibling && member.node.nextSibling.type === "," ) { endIndex = member.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); continue; } // check if used become unused. if so, we should remove it const originalUsageNode = this.usageResolver.getUsageNode( originalFile.rootNode, originalImportStatements.map((s) => s.node), member.aliasNode?.text || member.identifierNode.text, ); const extractedUsageNode = extractedFilesUsageResolver.getUsageNode( extractedFile.rootNode, extractedFilesImportStatements.map((s) => s.node), member.aliasNode?.text || member.identifierNode.text, ); if ( originalUsageNode.length > 0 && extractedUsageNode.length === 0 ) { // the import was used in original code but not in extracted code, we remove it const startIndex = member.node.startIndex; let endIndex = member.node.endIndex; if ( member.node.nextSibling && member.node.nextSibling.type === "," ) { endIndex = member.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); } } if (indexesToRemoveForImport.length > 0) { if ( indexesToRemoveForImport.length === extractedFilesImportStatement.members.length ) { // remove the whole import indexesToRemove.push({ startIndex: extractedFilesImportStatement.node.startIndex, endIndex: extractedFilesImportStatement.node.endIndex, }); } else { // remove the members that are not kept indexesToRemove.push(...indexesToRemoveForImport); } } } else if ( extractedFilesImportStatement.type === FROM_IMPORT_STATEMENT_TYPE ) { // Handle from imports (from foo import bar) const member = extractedFilesImportStatement.members[0]; const originalFileModule = this.moduleResolver.getModuleFromFilePath( originalFile.path, ); const resolvedOriginalModule = this.moduleResolver.resolveModule( originalFileModule, member.identifierNode.text, ); // If the module doesn't resolve in the original files, it's external if (!resolvedOriginalModule) { if (member.isWildcardImport) { // if wildcard, we skip it, no way to check if it's used or not continue; } // Check each item in the import if used or not if (!member.items) { throw new Error( `Could not find items for import ${member.identifierNode.text} in ${originalFile.path}`, ); } const indexesToRemoveForImport: { startIndex: number; endIndex: number; }[] = []; for (const item of member.items) { // Check if the imported item is used in original code const originalUsageNode = this.usageResolver.getUsageNode( originalFile.rootNode, originalImportStatements.map((s) => s.node), item.aliasNode?.text || item.identifierNode.text, ); // Check if the imported item is used in extracted code const extractedUsageNode = extractedFilesUsageResolver .getUsageNode( extractedFile.rootNode, extractedFilesImportStatements.map((s) => s.node), item.aliasNode?.text || item.identifierNode.text, ); // If the item was used in original code but not in extracted code, remove it if ( originalUsageNode.length > 0 && extractedUsageNode.length === 0 ) { const startIndex = item.node.startIndex; let endIndex = item.node.endIndex; if ( item.node.nextSibling && item.node.nextSibling.type === "," ) { endIndex = item.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); } } if (indexesToRemoveForImport.length > 0) { if (indexesToRemoveForImport.length === member.items.length) { // remove the whole import indexesToRemove.push({ startIndex: extractedFilesImportStatement.node.startIndex, endIndex: extractedFilesImportStatement.node.endIndex, }); } else { // remove the items that are not kept indexesToRemove.push(...indexesToRemoveForImport); } } continue; } // Check if the module can be resolved in the extracted files const extractedFileModule = extractedFilesModuleResolver .getModuleFromFilePath( extractedFile.path, ); const resolvedExtractedModule = extractedFilesModuleResolver .resolveModule( extractedFileModule, member.identifierNode.text, ); // If the module doesn't resolve anymore, remove the entire import if (!resolvedExtractedModule) { indexesToRemove.push({ startIndex: extractedFilesImportStatement.node.startIndex, endIndex: extractedFilesImportStatement.node.endIndex, }); continue; } // if the module resolve we need to check each item in the import if (member.isWildcardImport) { // if wildcard, we skip it continue; } // if the member is not a wildcard, we need to check if each items const indexesToRemoveForImport: { startIndex: number; endIndex: number; }[] = []; if (!member.items) { throw new Error( `Could not find items for import ${member.identifierNode.text} in ${originalFile.path}`, ); } for (const item of member.items) { // resolve the item from the original file const originalItem = this.itemResolver.resolveItem( resolvedOriginalModule, item.identifierNode.text, ); if (!originalItem) { // if the item doesn't resolve in the original file, it's external - keep it // Check if the imported item is used in original code const originalUsageNode = this.usageResolver.getUsageNode( originalFile.rootNode, originalImportStatements.map((s) => s.node), item.aliasNode?.text || item.identifierNode.text, ); // Check if the imported item is used in extracted code const extractedUsageNode = extractedFilesUsageResolver .getUsageNode( extractedFile.rootNode, extractedFilesImportStatements.map((s) => s.node), item.aliasNode?.text || item.identifierNode.text, ); // If the item was used in original code but not in extracted code, remove it if ( originalUsageNode.length > 0 && extractedUsageNode.length === 0 ) { const startIndex = item.node.startIndex; let endIndex = item.node.endIndex; if ( item.node.nextSibling && item.node.nextSibling.type === "," ) { endIndex = item.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); } continue; } const extractedItem = extractedFilesItemResolver.resolveItem( resolvedExtractedModule, item.identifierNode.text, ); if (!extractedItem) { // if the item doesn't resolve we need to remove it const startIndex = item.node.startIndex; let endIndex = item.node.endIndex; if (item.node.nextSibling && item.node.nextSibling.type === ",") { endIndex = item.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); continue; } // Check if the imported item is used in original code const originalUsageNode = this.usageResolver.getUsageNode( originalFile.rootNode, originalImportStatements.map((s) => s.node), item.aliasNode?.text || item.identifierNode.text, ); // Check if the imported item is used in extracted code const extractedUsageNode = extractedFilesUsageResolver.getUsageNode( extractedFile.rootNode, extractedFilesImportStatements.map((s) => s.node), item.aliasNode?.text || item.identifierNode.text, ); // If the item was used in original code but not in extracted code, remove it if ( originalUsageNode.length > 0 && extractedUsageNode.length === 0 ) { const startIndex = item.node.startIndex; let endIndex = item.node.endIndex; if (item.node.nextSibling && item.node.nextSibling.type === ",") { endIndex = item.node.nextSibling.endIndex; } indexesToRemoveForImport.push({ startIndex, endIndex, }); } } if (indexesToRemoveForImport.length > 0) { if (indexesToRemoveForImport.length === member.items.length) { // remove the whole import indexesToRemove.push({ startIndex: extractedFilesImportStatement.node.startIndex, endIndex: extractedFilesImportStatement.node.endIndex, }); } else { // remove the items that are not kept indexesToRemove.push(...indexesToRemoveForImport); } } } } // Remove the invalid imports from the file content if (indexesToRemove.length > 0) { const newContent = removeIndexesFromSourceCode( content, indexesToRemove, ); // Update the file in the map extractedFiles.set(path, { path, content: newContent }); } } } } ================================================ FILE: src/languagePlugins/python/symbolExtractor/types.ts ================================================ ================================================ FILE: src/languagePlugins/python/usageResolver/index.test.ts ================================================ import { beforeEach, describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import type Parser from "tree-sitter"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; import { PythonExportExtractor } from "../exportExtractor/index.ts"; import { PythonModuleResolver } from "../moduleResolver/index.ts"; import { PythonUsageResolver } from "./index.ts"; import { PythonImportExtractor } from "../importExtractor/index.ts"; import type { PythonModule } from "../moduleResolver/types.ts"; import type { InternalUsage } from "./types.ts"; import type { PythonSymbol } from "../exportExtractor/types.ts"; describe("USageResolver, getUsageNode", () => { let parser: Parser; let exportExtractor: PythonExportExtractor; let resolver: PythonUsageResolver; let files: Map; beforeEach(() => { parser = pythonParser; files = new Map(); exportExtractor = new PythonExportExtractor(parser, files); resolver = new PythonUsageResolver(parser, exportExtractor); }); test("should return the correct usage of a symbol that is a leaf and root node", () => { const targetNode = parser.parse(` foo() `).rootNode; // as a leaf node const usageNode = resolver.getUsageNode(targetNode, [], "foo"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo"); }); test("should return the correct usage of a chain attribute that is both a leaf and root node", () => { const targetNode = parser.parse(` foo.bar() `).rootNode; const usageNode = resolver.getUsageNode(targetNode, [], "foo.bar"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo.bar"); }); test("should correctly identify parts of a complex attribute chain", () => { const targetNode = parser.parse(` foo.f.o() `).rootNode; // Test 'foo' - should be root but not leaf let usageNode = resolver.getUsageNode(targetNode, [], "foo"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo"); // Test 'foo.f' - should be root but not leaf usageNode = resolver.getUsageNode(targetNode, [], "foo.f"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo.f"); }); test("should correctly identify parts of a complex attribute chain with brackets", () => { const targetNode = parser.parse(` foo.bar.baz()[qux] `).rootNode; // Test 'foo' - should be root but not leaf let usageNode = resolver.getUsageNode(targetNode, [], "foo"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo"); // Test 'bar' - should be neither root nor leaf usageNode = resolver.getUsageNode(targetNode, [], "bar"); expect(usageNode.length).toBe(0); // Test 'baz' - should be neither root nor leaf usageNode = resolver.getUsageNode(targetNode, [], "baz"); expect(usageNode.length).toBe(0); // Text 'foo.bar.baz' - should be root and leaf usageNode = resolver.getUsageNode(targetNode, [], "foo.bar.baz"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo.bar.baz"); // Test 'qux' - should be leaf and a root usageNode = resolver.getUsageNode(targetNode, [], "qux"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("qux"); }); test("should handle method calls with arguments", () => { const targetNode = parser.parse(` foo.bar(1, x.y.z) `).rootNode; // Test 'foo' should be root but not leaf let usageNode = resolver.getUsageNode(targetNode, [], "foo"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("foo"); // Test 'bar' should not resolve as a leaf node or root node usageNode = resolver.getUsageNode(targetNode, [], "bar"); expect(usageNode.length).toBe(0); // Test 'x' in x.y.z should be root but not leaf usageNode = resolver.getUsageNode(targetNode, [], "x"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("x"); // Test 'z' in x.y.z should not resolve as a leaf node and root node usageNode = resolver.getUsageNode(targetNode, [], "z"); expect(usageNode.length).toBe(0); // Test 'x.y.z' should be root and leaf usageNode = resolver.getUsageNode(targetNode, [], "x.y.z"); expect(usageNode.length).toBe(1); expect(usageNode[0].text).toBe("x.y.z"); }); }); describe("UsageResolver", () => { let parser: Parser; let exportExtractor: PythonExportExtractor; let resolver: PythonUsageResolver; let files: Map; beforeEach(() => { parser = pythonParser; // Create test files files = new Map([ // Basic module with symbols [ "module_a.py", { path: "module_a.py", rootNode: parser.parse(` def function_a(): return "Hello from function A" def function_b(): return "Hello from function B" CONSTANT_A = 42 `).rootNode, }, ], // Module that imports and partially uses symbols [ "module_b.py", { path: "module_b.py", rootNode: parser.parse(` from module_a import function_a, function_b as renamed_b, CONSTANT_A import external_module def use_imports(): result = function_a() another = renamed_b() external_module.do_something() return result `).rootNode, }, ], // Module that imports symbols with ambiguous aliasing [ "module_c.py", { path: "module_c.py", rootNode: parser.parse(` from module_a import function_a as function_b, function_b as function_a def use_ambiguous_imports(): result_a = function_a() result_b = function_b() return result_a, result_b `).rootNode, }, ], // Module with direct module references [ "module_with_submodules.py", { path: "module_with_submodules.py", rootNode: parser.parse(` import module_a def use_module(): # Direct module reference module_a.function_a() module_a.function_b() `).rootNode, }, ], // Package root [ "package/__init__.py", { path: "package/__init__.py", rootNode: parser.parse(` from .submod import submod_func def init_func(): return "Init function" `).rootNode, }, ], // Package submodule [ "package/submod.py", { path: "package/submod.py", rootNode: parser.parse(` def submod_func(): return "Submodule function" `).rootNode, }, ], // Module using a package [ "use_package.py", { path: "use_package.py", rootNode: parser.parse(` import package from package import submod def use_package_func(): # Use the package directly package.init_func() # Use a submodule package.submod.submod_func() # Use imported submodule submod.submod_func() `).rootNode, }, ], // Nested package root [ "nested_pkg/__init__.py", { path: "nested_pkg/__init__.py", rootNode: parser.parse(` from .sub1 import sub1_func `).rootNode, }, ], // Nested package submodule [ "nested_pkg/sub1/__init__.py", { path: "nested_pkg/sub1/__init__.py", rootNode: parser.parse(` from .subsubmod import subsub_func def sub1_func(): return "Sub1 function" `).rootNode, }, ], // Nested package sub-submodule [ "nested_pkg/sub1/subsubmod.py", { path: "nested_pkg/sub1/subsubmod.py", rootNode: parser.parse(` def subsub_func(): return "Deep nested function" `).rootNode, }, ], // Module using nested package [ "use_nested_pkg.py", { path: "use_nested_pkg.py", rootNode: parser.parse(` import nested_pkg def use_nested_function(): # Use deeply nested module result = nested_pkg.sub1.subsub_func() return result `).rootNode, }, ], // Module with unused imports [ "unused_module_import.py", { path: "unused_module_import.py", rootNode: parser.parse(` import module_a def no_usage(): # No module usage return "No module usage" `).rootNode, }, ], // Re-export test modules [ "original.py", { path: "original.py", rootNode: parser.parse(` def original_func(): return "Original function" `).rootNode, }, ], [ "reexporter.py", { path: "reexporter.py", rootNode: parser.parse(` from original import original_func `).rootNode, }, ], [ "consumer.py", { path: "consumer.py", rootNode: parser.parse(` from reexporter import original_func def consumer_func(): return original_func() `).rootNode, }, ], ]); exportExtractor = new PythonExportExtractor(parser, files); resolver = new PythonUsageResolver(parser, exportExtractor); }); describe("resolveInternalUsageForSymbol", () => { test("should resolve internal symbol usage", () => { const targetFile = files.get("module_b.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const exportExtractor = new PythonExportExtractor(parser, files); const { symbols } = exportExtractor.getSymbols("module_a.py"); const moduleResolver = new PythonModuleResolver( new Set(files.keys()), "3.10", ); const moduleA = moduleResolver.getModuleFromFilePath( "module_a.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); const symbol = symbols.find( (s) => s.identifierNode.text === "function_a", ) as PythonSymbol; // Check for usage of module_a in module_b resolver.resolveInternalUsageForSymbol( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, symbol, symbol.identifierNode.text, internalUsageMap, ); // Verify results expect(internalUsageMap.size).toBe(1); expect(internalUsageMap.has(moduleA.path)).toBeTruthy(); expect(internalUsageMap.get(moduleA.path)?.symbols.size).toBe(1); expect(internalUsageMap.get(moduleA.path)?.symbols.get(symbol.id)).toBe( symbol, ); }); test("should resolve internal symbol usage with alias", () => { const targetFile = files.get("module_b.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const exportExtractor = new PythonExportExtractor(parser, files); const { symbols } = exportExtractor.getSymbols("module_a.py"); const moduleResolver = new PythonModuleResolver( new Set(files.keys()), "3.10", ); const moduleA = moduleResolver.getModuleFromFilePath( "module_a.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); const symbol = symbols.find( (s) => s.identifierNode.text === "function_b", ) as PythonSymbol; // Check for usage of module_a in module_b resolver.resolveInternalUsageForSymbol( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, symbol, "renamed_b", internalUsageMap, ); // Verify results expect(internalUsageMap.size).toBe(1); expect(internalUsageMap.has(moduleA.path)).toBeTruthy(); expect(internalUsageMap.get(moduleA.path)?.symbols.size).toBe(1); expect(internalUsageMap.get(moduleA.path)?.symbols.get(symbol.id)).toBe( symbol, ); }); test("should not resolve internal symbol usage if unused (only within node to exclude)", () => { const targetFile = files.get("module_b.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const exportExtractor = new PythonExportExtractor(parser, files); const { symbols } = exportExtractor.getSymbols("module_a.py"); const moduleResolver = new PythonModuleResolver( new Set(files.keys()), "3.10", ); const moduleA = moduleResolver.getModuleFromFilePath( "module_a.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); const symbol = symbols.find( (s) => s.identifierNode.text === "CONSTANT_A", ) as PythonSymbol; // Check for usage of module_a in module_b resolver.resolveInternalUsageForSymbol( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, symbol, "CONSTANT_A", internalUsageMap, ); // Verify results expect(internalUsageMap.size).toBe(0); }); test("should resolve internal symbol usage with ambiguous alias", () => { const targetFile = files.get("module_c.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const exportExtractor = new PythonExportExtractor(parser, files); const { symbols } = exportExtractor.getSymbols("module_a.py"); const moduleResolver = new PythonModuleResolver( new Set(files.keys()), "3.10", ); const moduleA = moduleResolver.getModuleFromFilePath( "module_a.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); const symbolFunctionA = symbols.find( (s) => s.identifierNode.text === "function_a", ) as PythonSymbol; const symbolFunctionB = symbols.find( (s) => s.identifierNode.text === "function_b", ) as PythonSymbol; // Check for usage of function_a in module_c resolver.resolveInternalUsageForSymbol( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, symbolFunctionA, "function_b", // alias for function_a internalUsageMap, ); expect(internalUsageMap.size).toBe(1); expect(internalUsageMap.has(moduleA.path)).toBeTruthy(); expect(internalUsageMap.get(moduleA.path)?.symbols.size).toBe(1); expect( internalUsageMap.get(moduleA.path)?.symbols.get(symbolFunctionA.id), ).toBe(symbolFunctionA); internalUsageMap.clear(); // Check for usage of function_b in module_c resolver.resolveInternalUsageForSymbol( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, symbolFunctionB, "function_a", // alias for function_b internalUsageMap, ); expect(internalUsageMap.size).toBe(1); expect(internalUsageMap.has(moduleA.path)).toBeTruthy(); expect(internalUsageMap.get(moduleA.path)?.symbols.size).toBe(1); expect( internalUsageMap.get(moduleA.path)?.symbols.get(symbolFunctionB.id), ).toBe(symbolFunctionB); }); }); describe("resolveInternalUsageForModule", () => { let moduleResolver: PythonModuleResolver; beforeEach(() => { moduleResolver = new PythonModuleResolver(new Set(files.keys()), "3.10"); }); test("should resolve usage of module and its symbols", () => { const targetFile = files.get("module_with_submodules.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const moduleA = moduleResolver.getModuleFromFilePath( "module_a.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); // Check for usage of module_a and its symbols resolver.resolveInternalUsageForModule( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, "module_a", internalUsageMap, ); // Verify results expect(internalUsageMap.size).toBe(1); expect(internalUsageMap.has(moduleA.path)).toBeTruthy(); // Get the symbols from module_a to verify const exportResult = exportExtractor.getSymbols("module_a.py"); const functionA = exportResult.symbols.find( (s) => s.identifierNode.text === "function_a", ) as PythonSymbol; const functionB = exportResult.symbols.find( (s) => s.identifierNode.text === "function_b", ) as PythonSymbol; // Should have found both function_a and function_b expect(internalUsageMap.get(moduleA.path)?.symbols.size).toBe(2); expect( internalUsageMap.get(moduleA.path)?.symbols.get(functionA.id), ).toBe(functionA); expect( internalUsageMap.get(moduleA.path)?.symbols.get(functionB.id), ).toBe(functionB); }); test("should resolve usage of package and its submodules", () => { const targetFile = files.get("use_package.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const packageModule = moduleResolver.getModuleFromFilePath( "package/__init__.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); // Check for usage of package and its submodules resolver.resolveInternalUsageForModule( targetFile.rootNode, importStatements.map((i) => i.node), packageModule, "package", internalUsageMap, ); // Verify results expect(internalUsageMap.size).toBeGreaterThan(0); expect(internalUsageMap.has(packageModule.path)).toBeTruthy(); // Get the init module function const initExportResult = exportExtractor.getSymbols( "package/__init__.py", ); const initFunc = initExportResult.symbols.find( (s) => s.identifierNode.text === "init_func", ) as PythonSymbol; // Check that the init_func is found expect( internalUsageMap.get(packageModule.path)?.symbols.get(initFunc.id), ).toBe(initFunc); // Since submod is a submodule of package, we need to find it in the children const submodPath = "package/submod.py"; const hasSubmod = Array.from(internalUsageMap.keys()).some( (key) => key === submodPath, ); expect(hasSubmod).toBeTruthy(); // Get the submodule function const submodExportResult = exportExtractor.getSymbols( "package/submod.py", ); const submodFunc = submodExportResult.symbols.find( (s) => s.identifierNode.text === "submod_func", ) as PythonSymbol; // Check that the submod_func is found expect(internalUsageMap.get(submodPath)?.symbols.get(submodFunc.id)).toBe( submodFunc, ); }); test("should not detect usage of module that isn't used", () => { const targetFile = files.get("unused_module_import.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const moduleA = moduleResolver.getModuleFromFilePath( "module_a.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); // Check for usage of module_a (which isn't actually used) resolver.resolveInternalUsageForModule( targetFile.rootNode, importStatements.map((i) => i.node), moduleA, "module_a", internalUsageMap, ); // Verify the module is tracked but no symbols are used expect(internalUsageMap.size).toBe(0); }); }); test("should track re-exporting modules", () => { const targetFile = files.get("consumer.py") as { path: string; rootNode: Parser.SyntaxNode; }; const importExtractor = new PythonImportExtractor(parser, files); const importStatements = importExtractor.getImportStatements( targetFile.path, ); const exportExtractor = new PythonExportExtractor(parser, files); const { symbols: originalSymbols } = exportExtractor.getSymbols( "original.py", ); const moduleResolver = new PythonModuleResolver( new Set(files.keys()), "3.10", ); const originalModule = moduleResolver.getModuleFromFilePath( "original.py", ) as PythonModule; const reexporterModule = moduleResolver.getModuleFromFilePath( "reexporter.py", ) as PythonModule; // Setup a map to collect results const internalUsageMap = new Map(); const symbol = originalSymbols.find( (s) => s.identifierNode.text === "original_func", ) as PythonSymbol; // Check for usage of original.py in consumer.py with reexporter.py as the re-exporting module resolver.resolveInternalUsageForSymbol( targetFile.rootNode, importStatements.map((i) => i.node), originalModule, symbol, symbol.identifierNode.text, internalUsageMap, reexporterModule, ); // Verify results expect(internalUsageMap.size).toBe(1); expect(internalUsageMap.has(originalModule.path)).toBeTruthy(); // Check symbol tracking const usage = internalUsageMap.get(originalModule.path); expect(usage?.symbols.size).toBe(1); expect(usage?.symbols.get(symbol.id)).toBe(symbol); // Check re-exporting module tracking expect(usage?.reExportingModules).toBeDefined(); expect(usage?.reExportingModules?.size).toBe(1); expect(usage?.reExportingModules?.has(reexporterModule.path)).toBeTruthy(); expect(usage?.reExportingModules?.get(reexporterModule.path)).toBe( reexporterModule, ); }); }); ================================================ FILE: src/languagePlugins/python/usageResolver/index.ts ================================================ /** * Python Usage Resolver * * This module provides functionality to analyze and track how Python modules and symbols are used * within Python source code. It helps identify which imported modules and their exported symbols * are actually referenced in the code, which is essential for dependency analysis and dead code detection. * * The implementation uses Tree-sitter for parsing Python code into an Abstract Syntax Tree (AST), * then traverses the AST to find references to modules and symbols. * * Key features: * - Tracks usage of internal modules (within the project) * - Tracks usage of external modules (from standard library or third-party) * - Distinguishes between module imports and symbol imports * - Handles aliased imports correctly * - Detects re-exports of imported symbols */ import { PYTHON_NAMESPACE_MODULE_TYPE, type PythonModule, } from "../moduleResolver/types.ts"; import type { PythonExportExtractor } from "../exportExtractor/index.ts"; import Parser from "tree-sitter"; import type { PythonSymbol } from "../exportExtractor/types.ts"; import type { ExternalUsage, InternalUsage } from "./types.ts"; /** * UsageResolver analyzes Python code to identify module and symbol references. * It tracks which imported modules and symbols are actually used within a file. * * This class provides essential information for: * - Dependency analysis (what modules depend on what) * - Dead code detection (which imports are unused) * - Refactoring safety (understanding the impact of changes) */ export class PythonUsageResolver { /** * Tree-sitter parser for Python code * Used to parse and analyze Python ASTs */ private parser: Parser; /** * Export extractor for retrieving symbols from modules * Used to get the symbols defined in each module */ private exportExtractor: PythonExportExtractor; /** * Tree-sitter query for finding identifiers and attributes in code * Used to locate references to modules and symbols */ private query: Parser.Query; /** * Creates a new UsageResolver instance * @param parser - Tree-sitter parser for Python code analysis * @param exportExtractor - Utility for extracting exported symbols from Python modules */ constructor(parser: Parser, exportExtractor: PythonExportExtractor) { this.parser = parser; this.exportExtractor = exportExtractor; this.query = new Parser.Query( this.parser.getLanguage(), ` ((identifier) @id) ((attribute) @attr) `, ); } /** * Finds all nodes in the AST that match the specified reference name * * @param targetNode - Root node to search within * @param nodesToExclude - Nodes to ignore during the search (like import statements) * @param refToLookFor - The name/reference to search for in the code * @returns Array of matching syntax nodes */ public getUsageNode( targetNode: Parser.SyntaxNode, nodesToExclude: Parser.SyntaxNode[], refToLookFor: string, ) { let captures = this.query.captures(targetNode); // Filter out nodes that are inside excluded nodes captures = captures.filter(({ node }) => { // First filter by text match if (node.text !== refToLookFor) { return false; } const isNodeInsideAnyExclude = this.isNodeInsideAnyExclude( node, nodesToExclude, ); if (isNodeInsideAnyExclude) { return false; } let isRoot = true; const parent = node.parent; if (parent && parent.type === "attribute") { const parentObjChild = parent.childForFieldName("object"); if (!parentObjChild || parentObjChild.id !== node.id) { isRoot = false; } } if (isRoot) { return true; } return false; }); const nodes = captures.map(({ node }) => node); return nodes; } /** * Determines whether a node is contained within any of the excluded nodes * * @param node - Node to check * @param nodesToExclude - List of nodes that should be excluded * @returns True if the node is inside an excluded region, false otherwise */ private isNodeInsideAnyExclude( node: Parser.SyntaxNode, nodesToExclude: Parser.SyntaxNode[], ): boolean { return nodesToExclude.some( (excludeNode) => node.startIndex >= excludeNode.startIndex && node.endIndex <= excludeNode.endIndex, ); } /** * Records usage of a specific symbol from a module * * @param targetNode - AST node to search within * @param nodesToExclude - Nodes to exclude from search * @param module - Module containing the symbol * @param symbol - Symbol being checked for usage * @param lookupRef - Reference name to search for (often includes module alias) * @param internalUsageMap - Map to record usage information * @param reExportingModule - Optional module that re-exports this symbol */ public resolveInternalUsageForSymbol( /* The node to search for usage. eg a function or class */ targetNode: Parser.SyntaxNode, /* Nodes to exclude from the search. eg import statements */ nodesToExclude: Parser.SyntaxNode[], /* Internal python module to resolve usage for */ module: PythonModule, /* Internal python symbol to resolve usage for */ symbol: PythonSymbol, /* potential alias. Or name of the module */ lookupRef: string, /* Map of internal usage results, used for recursions */ internalUsageMap: Map, /* Optional module that re-exports this symbol */ reExportingModule?: PythonModule, ) { const usageNodes = this.getUsageNode(targetNode, nodesToExclude, lookupRef); if (usageNodes.length > 0) { if (!internalUsageMap.has(module.path)) { internalUsageMap.set(module.path, { module, symbols: new Map(), reExportingModules: reExportingModule ? new Map() : undefined, }); } const internalUsage = internalUsageMap.get(module.path) as { module: PythonModule; symbols: Map; reExportingModules?: Map; }; if (!internalUsage.symbols.has(symbol.id)) { internalUsage.symbols.set(symbol.id, symbol); } // Add re-exporting module as a dependency if provided if (reExportingModule) { if (!internalUsage.reExportingModules) { internalUsage.reExportingModules = new Map(); } if (!internalUsage.reExportingModules.has(reExportingModule.path)) { internalUsage.reExportingModules.set( reExportingModule.path, reExportingModule, ); } } } } /** * Analyzes code for usage of a module and its symbols * * This method serves as the main entry point for analyzing how modules and their symbols are used in Python code. * It performs several key tasks: * * 1. Checks for direct references to the module itself * 2. Checks for references to symbols (functions, classes, variables) defined within the module * 3. Recursively checks for usage of submodules and their symbols * * This multi-layered analysis ensures complete tracking of module dependencies, * whether they're used directly or through nested references. * * @param targetNode - AST node to search within * @param nodesToExclude - Nodes to exclude from search (e.g. import statements) * @param module - Module to check for usage * @param lookupRef - Reference name to look for (potentially an alias) * @param internalUsageMap - Map to record module and symbol usage */ public resolveInternalUsageForModule( /* The node to search for usage. eg a function or class */ targetNode: Parser.SyntaxNode, /* Nodes to exclude from the search. eg import statements */ nodesToExclude: Parser.SyntaxNode[], /* Internal python module to resolve usage for */ module: PythonModule, /* potential alias. Or name of the module */ lookupRef: string, /* Map of internal usage results, used for recursions */ internalUsageMap: Map, ) { const symbols: PythonSymbol[] = []; const moduleUsageNodes = this.getUsageNode( targetNode, nodesToExclude, lookupRef, ); if (moduleUsageNodes.length > 0) { // Register module usage even without specific symbols if (!internalUsageMap.has(module.path)) { internalUsageMap.set(module.path, { module, symbols: new Map(), }); } } // namespace module are not file, so they do not have symbols if (module.type !== PYTHON_NAMESPACE_MODULE_TYPE) { const exports = this.exportExtractor.getSymbols(module.path); symbols.push(...exports.symbols); } // check for usage of module.symbol symbols.forEach((symbol) => { const symbolLookupRef = `${lookupRef}.${symbol.identifierNode.text}`; this.resolveInternalUsageForSymbol( targetNode, nodesToExclude, module, symbol, symbolLookupRef, internalUsageMap, ); }); // check for usage of submodules module.children.forEach((subModule) => { const subModuleLookupRef = `${lookupRef}.${subModule.name}`; this.resolveInternalUsageForModule( targetNode, nodesToExclude, subModule, subModuleLookupRef, internalUsageMap, ); }); } /** * Resolves external usage for a module item, tracking both direct references * and attribute access patterns * * This method analyzes how external Python modules (like standard library or third-party packages) * are used in the code. It performs two key tasks: * * 1. Identifies direct references to the module itself * 2. Tracks attribute access patterns (like numpy.array, requests.get, etc.) * * The method maintains a hierarchical structure of usage, which helps in understanding * not just which modules are imported, but specifically which parts of those modules * are actually being used in the code. * * @param targetNode - AST node to search within * @param nodesToExclude - Nodes to exclude from search * @param itemName - Name of the external module/item to check * @param lookupRef - Reference name to look for (potentially an alias) * @param externalUsageMap - Map to record external usage */ public resolveExternalUsageForItem( targetNode: Parser.SyntaxNode, nodesToExclude: Parser.SyntaxNode[], item: { moduleName: string; itemName?: string; }, lookupRef: string, externalUsageMap: Map, ) { const usageNodes = this.getUsageNode(targetNode, nodesToExclude, lookupRef); if (usageNodes.length > 0) { // Initialize entry for base module if (!externalUsageMap.has(item.moduleName)) { externalUsageMap.set(item.moduleName, { moduleName: item.moduleName, itemNames: new Set(), }); } // Add the item name to the usage map if (item.itemName) { externalUsageMap.get(item.moduleName)?.itemNames.add(item.itemName); } else { // try resolving symbols fromt the usage node usageNodes.forEach((usageNode) => { const symbol = this.resolveExternalUsageSymbolFromUsage(usageNode); if (symbol) { externalUsageMap.get(item.moduleName)?.itemNames.add(symbol); } }); } } } private resolveExternalUsageSymbolFromUsage(usageNode: Parser.SyntaxNode) { if (usageNode.parent && usageNode.parent.type === "attribute") { const attributeName = usageNode.parent.childForFieldName("attribute"); if (attributeName) { return attributeName.text; } } } } ================================================ FILE: src/languagePlugins/python/usageResolver/types.ts ================================================ import type { PythonModule } from "../moduleResolver/types.ts"; import type { PythonSymbol } from "../exportExtractor/types.ts"; /** * Represents usage information for a Python module, containing the module reference * and a map of symbols used from that module. * * This is used to track how internal project modules are used within the codebase, * including which specific symbols are referenced from each module. */ export interface InternalUsage { /** The Python module being used */ module: PythonModule; /** Map of symbol IDs to their corresponding PythonSymbol objects that are used in the code */ symbols: Map; /** * Optional information about re-exporting modules (modules that re-export this module's symbols) * This helps track indirect dependencies through re-exports */ reExportingModules?: Map; } /** * Represents usage information for an external Python module (e.g., from standard library or third-party packages). * * Unlike InternalUsage, this only tracks the module name and which symbols are used, * since we don't have access to the full AST information for external modules. */ export interface ExternalUsage { /** The name of the external module (e.g., 'os', 'numpy', etc.) */ moduleName: string; /** Set of specific items (functions, classes, variables) used from this module */ itemNames: Set; } ================================================ FILE: src/manifest/auditManifest/index.ts ================================================ import type { DependencyManifest } from "../dependencyManifest/types.ts"; import { metricCharacterCount, metricCodeCharacterCount, metricCodeLineCount, metricCyclomaticComplexity, metricDependencyCount, metricDependentCount, metricLinesCount, } from "../dependencyManifest/types.ts"; import type { AuditConfig, AuditManifest, FileAuditManifest, SymbolAuditManifest, } from "./types.ts"; function getSeverityLevel( value: number, targetValue = 0, ): 1 | 2 | 3 | 4 | 5 { if (value > targetValue * 10) { return 5; } else if (value > targetValue * 5) { return 4; } else if (value > targetValue * 2) { return 3; } else if (value > targetValue * 1.5) { return 2; } else { return 1; } } export function generateAuditManifest( dependencyManifest: DependencyManifest, config: AuditConfig, ): AuditManifest { const auditManifest: AuditManifest = {}; for (const fileDependencyManifest of Object.values(dependencyManifest)) { const fileAudit: FileAuditManifest = { id: fileDependencyManifest.id, alerts: {}, symbols: {}, }; const fm = fileDependencyManifest.metrics; if (fm.codeCharacterCount > config.file.maxCodeChar) { fileAudit.alerts[metricCodeCharacterCount] = { metric: metricCodeCharacterCount, severity: getSeverityLevel( fm.codeCharacterCount, config.file.maxCodeChar, ), message: { short: "File too large", long: `File exceeds maximum character limit (${fm.codeCharacterCount}/${config.file.maxCodeChar})`, }, }; } if (fm.characterCount > config.file.maxChar) { fileAudit.alerts[metricCharacterCount] = { metric: metricCharacterCount, severity: getSeverityLevel(fm.characterCount, config.file.maxChar), message: { short: "File too large", long: `File exceeds maximum character limit (${fm.characterCount}/${config.file.maxChar})`, }, }; } if (fm.codeLineCount > config.file.maxCodeLine) { fileAudit.alerts[metricCodeLineCount] = { metric: metricCodeLineCount, severity: getSeverityLevel(fm.codeLineCount, config.file.maxCodeLine), message: { short: "Too many lines", long: `File exceeds maximum line count (${fm.codeLineCount}/${config.file.maxCodeLine})`, }, }; } if (fm.linesCount > config.file.maxLine) { fileAudit.alerts[metricLinesCount] = { metric: metricLinesCount, severity: getSeverityLevel(fm.linesCount, config.file.maxLine), message: { short: "Too many lines", long: `File exceeds maximum line count (${fm.linesCount}/${config.file.maxLine})`, }, }; } if (fm.dependencyCount > config.file.maxDependency) { fileAudit.alerts[metricDependencyCount] = { metric: metricDependencyCount, severity: getSeverityLevel( fm.dependencyCount, config.file.maxDependency, ), message: { short: "Too many dependencies", long: `File exceeds maximum dependency count (${fm.dependencyCount}/${config.file.maxDependency})`, }, }; } if (fm.dependentCount > config.file.maxDependent) { fileAudit.alerts[metricDependentCount] = { metric: metricDependentCount, severity: getSeverityLevel(fm.dependentCount, config.file.maxDependent), message: { short: "Too many dependents", long: `File exceeds maximum dependent count (${fm.dependentCount}/${config.file.maxDependent})`, }, }; } if (fm.cyclomaticComplexity > config.file.maxCyclomaticComplexity) { fileAudit.alerts[metricCyclomaticComplexity] = { metric: metricCyclomaticComplexity, severity: getSeverityLevel( fm.cyclomaticComplexity, config.file.maxCyclomaticComplexity, ), message: { short: "Too complex", long: `File exceeds maximum cyclomatic complexity (${fm.cyclomaticComplexity}/${config.file.maxCyclomaticComplexity})`, }, }; } for (const symbol of Object.values(fileDependencyManifest.symbols)) { const symbolAudit: SymbolAuditManifest = { id: symbol.id, alerts: {}, }; const sm = symbol.metrics; if (sm.codeCharacterCount > config.symbol.maxCodeChar) { symbolAudit.alerts[metricCodeCharacterCount] = { metric: metricCodeCharacterCount, severity: getSeverityLevel( sm.codeCharacterCount, config.symbol.maxCodeChar, ), message: { short: "Symbol too large", long: `Symbol exceeds maximum character limit (${sm.codeCharacterCount}/${config.symbol.maxCodeChar})`, }, }; } if (sm.characterCount > config.symbol.maxChar) { symbolAudit.alerts[metricCharacterCount] = { metric: metricCharacterCount, severity: getSeverityLevel(sm.characterCount, config.symbol.maxChar), message: { short: "Symbol too large", long: `Symbol exceeds maximum character limit (${sm.characterCount}/${config.symbol.maxChar})`, }, }; } if (sm.codeLineCount > config.symbol.maxCodeLine) { symbolAudit.alerts[metricCodeLineCount] = { metric: metricCodeLineCount, severity: getSeverityLevel( sm.codeLineCount, config.symbol.maxCodeLine, ), message: { short: "Symbol too long", long: `Symbol exceeds maximum line count (${sm.codeLineCount}/${config.symbol.maxCodeLine})`, }, }; } if (sm.linesCount > config.symbol.maxLine) { symbolAudit.alerts[metricLinesCount] = { metric: metricLinesCount, severity: getSeverityLevel(sm.linesCount, config.symbol.maxLine), message: { short: "Symbol too long", long: `Symbol exceeds maximum line count (${sm.linesCount}/${config.symbol.maxLine})`, }, }; } if (sm.dependencyCount > config.symbol.maxDependency) { symbolAudit.alerts[metricDependencyCount] = { metric: metricDependencyCount, severity: getSeverityLevel( sm.dependencyCount, config.symbol.maxDependency, ), message: { short: "Too many dependencies", long: `Symbol exceeds maximum dependency count (${sm.dependencyCount}/${config.symbol.maxDependency})`, }, }; } if (sm.dependentCount > config.symbol.maxDependent) { symbolAudit.alerts[metricDependentCount] = { metric: metricDependentCount, severity: getSeverityLevel( sm.dependentCount, config.symbol.maxDependent, ), message: { short: "Too many dependents", long: `Symbol exceeds maximum dependent count (${sm.dependentCount}/${config.symbol.maxDependent})`, }, }; } if (sm.cyclomaticComplexity > config.symbol.maxCyclomaticComplexity) { symbolAudit.alerts[metricCyclomaticComplexity] = { metric: metricCyclomaticComplexity, severity: getSeverityLevel( sm.cyclomaticComplexity, config.symbol.maxCyclomaticComplexity, ), message: { short: "Symbol too complex", long: `Symbol exceeds maximum cyclomatic complexity (${sm.cyclomaticComplexity}/${config.symbol.maxCyclomaticComplexity})`, }, }; } fileAudit.symbols[symbol.id] = symbolAudit; } auditManifest[fileDependencyManifest.id] = fileAudit; } return auditManifest; } ================================================ FILE: src/manifest/auditManifest/types.ts ================================================ import type { Metric } from "../dependencyManifest/types.ts"; export type AuditAlert = { metric: Metric; severity: number; message: { short: string; long: string; }; }; export type SymbolAuditManifest = { id: string; alerts: Record; }; export type FileAuditManifest = { id: string; alerts: Record; symbols: Record; }; export type AuditManifest = Record; export interface AuditConfig { file: { maxCodeChar: number; maxChar: number; maxCodeLine: number; maxLine: number; maxDependency: number; maxDependent: number; maxCyclomaticComplexity: number; }; symbol: { maxCodeChar: number; maxChar: number; maxCodeLine: number; maxLine: number; maxDependency: number; maxDependent: number; maxCyclomaticComplexity: number; }; } export const defaultAuditConfig: AuditConfig = { file: { maxCodeChar: 1000, maxChar: 1000, maxCodeLine: 100, maxLine: 100, maxDependency: 100, maxDependent: 100, maxCyclomaticComplexity: 100, }, symbol: { maxCodeChar: 100, maxChar: 100, maxCodeLine: 10, maxLine: 10, maxDependency: 10, maxDependent: 10, maxCyclomaticComplexity: 10, }, }; ================================================ FILE: src/manifest/dependencyManifest/c/index.ts ================================================ import { type DependencyManifest, metricCharacterCount, metricCodeCharacterCount, metricCodeLineCount, metricCyclomaticComplexity, metricDependencyCount, metricDependentCount, metricLinesCount, type SymbolDependencyManifest, type SymbolType, } from "../types.ts"; import { CDependencyFormatter } from "../../../languagePlugins/c/dependencyFormatting/index.ts"; import { CMetricsAnalyzer } from "../../../languagePlugins/c/metrics/index.ts"; import type Parser from "tree-sitter"; import { cLanguage, cParser } from "../../../helpers/treeSitter/parsers.ts"; import { CWarningManager } from "../../../languagePlugins/c/warnings/index.ts"; import type { localConfigSchema } from "../../../cli/middlewares/napiConfig.ts"; import type z from "zod"; export function generateCDependencyManifest( files: Map, napiConfig: z.infer, ): DependencyManifest { console.time("generateCDependencyManifest"); console.info("Processing project..."); const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const [filePath, { content: fileContent }] of files) { try { const rootNode = cParser.parse(fileContent, undefined, { bufferSize: fileContent.length + 10, }).rootNode; parsedFiles.set(filePath, { path: filePath, rootNode }); } catch (e) { console.error(`Failed to parse ${filePath}, skipping`); console.error(e); } } const warningManager = new CWarningManager(parsedFiles); if (warningManager.diagnostics.length > 0) { console.warn(`⚠️ ${warningManager.diagnostics.length} warnings found:`); for (const warning of warningManager.diagnostics) { console.warn(warning.message); } } const includeDirs = napiConfig[cLanguage]?.includedirs ?? []; const formatter = new CDependencyFormatter(parsedFiles, includeDirs); const metricsAnalyzer = new CMetricsAnalyzer(); const manifest: DependencyManifest = {}; const filecount = parsedFiles.size; let i = 0; for (const [, { path }] of parsedFiles) { console.info(`Processing ${path} (${++i}/${filecount})`); const fm = formatter.formatFile(path); const cSyms = fm.symbols; const symbols: Record = {}; for (const [symName, symbol] of Object.entries(cSyms)) { const symType = symbol.type; const dependencies = symbol.dependencies; const metrics = metricsAnalyzer.analyzeNode(symbol.node); symbols[symName] = { id: symName, type: symType as SymbolType, positions: [{ start: { index: symbol.node.startIndex, row: symbol.node.startPosition.row, column: symbol.node.startPosition.column, }, end: { index: symbol.node.endIndex, row: symbol.node.endPosition.row, column: symbol.node.endPosition.column, }, }], description: "", metrics: { [metricCharacterCount]: metrics.characterCount, [metricCodeCharacterCount]: metrics.codeCharacterCount, [metricLinesCount]: metrics.linesCount, [metricCodeLineCount]: metrics.codeLinesCount, [metricDependencyCount]: Object.keys(dependencies).length, [metricDependentCount]: 0, [metricCyclomaticComplexity]: metrics.cyclomaticComplexity, }, dependencies: dependencies, dependents: {}, }; } const metrics = metricsAnalyzer.analyzeNode(fm.rootNode); manifest[path] = { id: fm.id, filePath: fm.filePath, language: cLanguage, metrics: { [metricCharacterCount]: metrics.characterCount, [metricCodeCharacterCount]: metrics.codeCharacterCount, [metricLinesCount]: metrics.linesCount, [metricCodeLineCount]: metrics.codeLinesCount, [metricDependencyCount]: Object.keys(fm.dependencies).length, [metricDependentCount]: 0, [metricCyclomaticComplexity]: metrics.cyclomaticComplexity, }, dependencies: fm.dependencies, symbols: symbols, dependents: {}, }; } console.info("Populating dependents..."); i = 0; for (const fm of Object.values(manifest)) { const path = fm.filePath; console.info(`Populating dependents for ${path} (${++i}/${filecount})`); for (const symbol of Object.values(fm.symbols)) { for (const dpncy of Object.values(symbol.dependencies)) { for (const depsymbol of Object.values(dpncy.symbols)) { const otherFile = manifest[dpncy.id]; const otherSymbol = otherFile.symbols[depsymbol]; if (!otherSymbol.dependents[fm.id]) { otherSymbol.dependents[fm.id] = { id: fm.id, symbols: {}, }; } otherSymbol.dependents[fm.id].symbols[symbol.id] = symbol.id; fm.metrics[metricDependentCount]++; symbol.metrics[metricDependentCount]++; } } } } console.timeEnd("generateCDependencyManifest"); return manifest; } ================================================ FILE: src/manifest/dependencyManifest/csharp/index.test.ts ================================================ import { describe, test } from "@std/testing/bdd"; import { expect } from "@std/expect"; import { generateCSharpDependencyManifest } from "./index.ts"; import { join } from "@std/path"; import { csharpFilesFolder, getCSharpFilesMap, getCsprojFilesMap, } from "../../../languagePlugins/csharp/testFiles/index.ts"; describe("generateCSharpDependencymanifest", () => { const parsedfiles = getCSharpFilesMap(); const csprojFiles = getCsprojFilesMap(); const files = new Map(); for (const [filePath, { path, rootNode }] of parsedfiles) { files.set(filePath, { path, content: rootNode.text }); } for (const [filePath, { path, content }] of csprojFiles) { files.set(filePath, { path, content }); } const manifest = generateCSharpDependencyManifest(files); const burgers = join(csharpFilesFolder, "2Namespaces1File.cs"); const models = join(csharpFilesFolder, "Models.cs"); const namespaced = join(csharpFilesFolder, "Namespaced.cs"); const nested = join(csharpFilesFolder, "Nested.cs"); const program = join(csharpFilesFolder, "Program.cs"); const semiNamespaced = join(csharpFilesFolder, "SemiNamespaced.cs"); const usage = join(csharpFilesFolder, "Usage.cs"); test("Correctly identifies files", () => { expect(Object.keys(manifest).length).toBe(9); }); test("Resolves exports", () => { expect(Object.keys(manifest[burgers].symbols).length).toBe(6); expect(Object.keys(manifest[models].symbols).length).toBe(5); expect(Object.keys(manifest[namespaced].symbols).length).toBe(1); expect(Object.keys(manifest[nested].symbols).length).toBe(3); expect(Object.keys(manifest[program].symbols).length).toBe(1); expect(Object.keys(manifest[semiNamespaced].symbols).length).toBe(3); expect(Object.keys(manifest[usage].symbols).length).toBe(1); }); test("Resolves dependencies", () => { expect(Object.keys(manifest[burgers].dependencies).length).toBe(3); expect(Object.keys(manifest[models].dependencies).length).toBe(1); expect(Object.keys(manifest[namespaced].dependencies).length).toBe(1); expect(Object.keys(manifest[nested].dependencies).length).toBe(1); expect(Object.keys(manifest[program].dependencies).length).toBe(6); expect(Object.keys(manifest[semiNamespaced].dependencies).length).toBe(2); expect(Object.keys(manifest[usage].dependencies).length).toBe(4); }); test("Resolves dependents", () => { expect( Object.keys(manifest[burgers].symbols["MyApp.BeefBurger.Bun"].dependents) .length, ).toBe(1); expect( Object.keys(manifest[burgers].symbols["ChickenBurger.Bun"].dependents) .length, ).toBe(1); expect( Object.keys( manifest[namespaced].symbols["MyNamespace.MyClass"].dependents, ).length, ).toBe(1); expect( Object.keys( manifest[semiNamespaced].symbols["HalfNamespace.Gordon"].dependents, ).length, ).toBe(2); expect( Object.keys(manifest[semiNamespaced].symbols["Freeman"].dependents) .length, ).toBe(2); expect( Object.keys( manifest[nested].symbols["OuterNamespace.OuterInnerClass"].dependents, ).length, ).toBe(1); expect( Object.keys( manifest[nested].symbols["OuterNamespace.InnerNamespace.InnerClass"] .dependents, ).length, ).toBe(1); expect( Object.keys( manifest[models].symbols["MyApp.Models.OrderStatus"].dependents, ).length, ).toBe(1); }); }); ================================================ FILE: src/manifest/dependencyManifest/csharp/index.ts ================================================ import { type DependencyManifest, metricCharacterCount, metricCodeCharacterCount, metricCodeLineCount, metricCyclomaticComplexity, metricDependencyCount, metricDependentCount, metricLinesCount, type SymbolDependencyManifest, type SymbolType, } from "../types.ts"; import { CSharpDependencyFormatter, type CSharpFile, } from "../../../languagePlugins/csharp/dependencyFormatting/index.ts"; import type Parser from "tree-sitter"; import { csharpLanguage, csharpParser, } from "../../../helpers/treeSitter/parsers.ts"; import { CSharpMetricsAnalyzer } from "../../../languagePlugins/csharp/metricsAnalyzer/index.ts"; /** * Generates a dependency manifest for C# files. * @param files - A map of file paths to their corresponding syntax nodes. * @returns A dependency manifest for the C# files. */ export function generateCSharpDependencyManifest( files: Map, ): DependencyManifest { console.time("generateCSharpDependencyManifest"); console.info("Processing project..."); const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); const csprojFiles = new Map(); // Filter out csproj files and parse C# files for (const [filePath, { content: fileContent }] of files) { if (filePath.endsWith(".csproj")) { csprojFiles.set(filePath, { path: filePath, content: fileContent }); } else { try { const rootNode = csharpParser.parse(fileContent, undefined, { bufferSize: fileContent.length + 10, }).rootNode; parsedFiles.set(filePath, { path: filePath, rootNode }); } catch (e) { console.error(`Failed to parse ${filePath}, skipping`); console.error(e); } } } const formatter = new CSharpDependencyFormatter(parsedFiles, csprojFiles); const analyzer = new CSharpMetricsAnalyzer(); const manifest: DependencyManifest = {}; const filecount = parsedFiles.size; let i = 0; for (const [, { path }] of parsedFiles) { console.info(`Processing ${path} (${++i}/${filecount})`); const fm = formatter.formatFile(path) as CSharpFile; const csharpSyms = fm.symbols; const symbols: Record = {}; for (const [symbolName, symbol] of Object.entries(csharpSyms)) { const metrics = analyzer.analyzeNode(symbol.node); symbols[symbolName] = { id: symbolName, type: symbol.type as SymbolType, positions: [{ start: { index: symbol.node.startIndex, row: symbol.node.startPosition.row, column: symbol.node.startPosition.column, }, end: { index: symbol.node.endIndex, row: symbol.node.endPosition.row, column: symbol.node.endPosition.column, }, }], description: "", metrics: { [metricCharacterCount]: symbol.characterCount, [metricCodeCharacterCount]: metrics.codeCharacterCount, [metricLinesCount]: symbol.lineCount, [metricCodeLineCount]: metrics.codeLinesCount, [metricDependencyCount]: Object.keys(symbol.dependencies).length, [metricDependentCount]: 0, // TODO: fix this [metricCyclomaticComplexity]: metrics.cyclomaticComplexity, }, dependencies: symbol.dependencies, dependents: {}, }; } const filemetrics = analyzer.analyzeNode(fm.rootNode); manifest[path] = { id: fm.id, filePath: fm.filepath, language: csharpLanguage, metrics: { [metricCharacterCount]: fm.characterCount, [metricCodeCharacterCount]: filemetrics.codeCharacterCount, [metricLinesCount]: fm.lineCount, [metricCodeLineCount]: filemetrics.codeLinesCount, [metricDependencyCount]: Object.keys(fm.dependencies).length, [metricDependentCount]: 0, // TODO: fix this [metricCyclomaticComplexity]: filemetrics.cyclomaticComplexity, }, dependencies: fm.dependencies, symbols, dependents: {}, //TODO fix this }; // Delete isNamespace from dependencies for (const dep of Object.values(fm.dependencies)) { delete dep.isNamespace; } } console.info("Populating dependents..."); i = 0; // Populate dependents for (const fm of Object.values(manifest)) { const path = fm.filePath; console.info(`Populating dependents for ${path} (${++i}/${filecount})`); for (const symbol of Object.values(fm.symbols)) { for (const dpncy of Object.values(symbol.dependencies)) { for (const depsymbol of Object.values(dpncy.symbols)) { const otherFile = manifest[dpncy.id]; const otherSymbol = otherFile.symbols[depsymbol]; if (!otherSymbol.dependents[fm.id]) { otherSymbol.dependents[fm.id] = { id: fm.id, symbols: {}, }; } otherSymbol.dependents[fm.id].symbols[symbol.id] = symbol.id; fm.metrics[metricDependentCount]++; symbol.metrics[metricDependentCount]++; } } } } console.timeEnd("generateCSharpDependencyManifest"); return manifest; } ================================================ FILE: src/manifest/dependencyManifest/index.ts ================================================ import { generatePythonDependencyManifest } from "./python/index.ts"; import { generateCSharpDependencyManifest } from "./csharp/index.ts"; import { generateCDependencyManifest } from "./c/index.ts"; import type { localConfigSchema } from "../../cli/middlewares/napiConfig.ts"; import type z from "zod"; import type { DependencyManifest, SymbolDependencyManifest } from "./types.ts"; import { cLanguage, csharpLanguage, javaLanguage, pythonLanguage, } from "../../helpers/treeSitter/parsers.ts"; import { generateJavaDependencyManifest } from "./java/index.ts"; import { generateSymbolDescriptions } from "./labeling/index.ts"; import type { globalConfigSchema } from "../../cli/middlewares/globalConfig.ts"; import { ANTHROPIC_PROVIDER, GOOGLE_PROVIDER, OPENAI_PROVIDER, } from "./labeling/model.ts"; const handlerMap: Record< string, ( files: Map, napiConfig: z.infer, ) => DependencyManifest > = { [pythonLanguage]: generatePythonDependencyManifest, [csharpLanguage]: generateCSharpDependencyManifest, [cLanguage]: generateCDependencyManifest, [javaLanguage]: generateJavaDependencyManifest, }; export class UnsupportedLanguageError extends Error { constructor(language: string) { const supportedLanguages = Object.keys(handlerMap).join(", "); super( `Unsupported language: ${language}. Supported languages: ${supportedLanguages}`, ); } } export async function generateDependencyManifest( files: Map, napiConfig: z.infer, globalConfig: z.infer, labelingApiKey: string | undefined, ): Promise { const languageName = napiConfig.language; const handler = handlerMap[languageName]; if (!handler) { throw new UnsupportedLanguageError(languageName); } const depMap = handler(files, napiConfig); // Sort the keys of the dependency map and consider them all as lowercase const sortedKeys = Object.keys(depMap).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }) ); // Create a new object with sorted keys const sortedDepMap: DependencyManifest = {}; for (const key of sortedKeys) { sortedDepMap[key] = depMap[key]; // Sort the symbols within each file manifest and consider them all as lowercase const sortedSymbols = Object.keys(depMap[key].symbols).sort((a, b) => a.localeCompare(b, undefined, { sensitivity: "base" }) ); // Then put the symbols in a new object with sorted keys // in their original case const sortedSymbolsMap: Record = {}; for (const symbolKey of sortedSymbols) { sortedSymbolsMap[symbolKey] = depMap[key].symbols[symbolKey]; } // Assign the sorted symbols back to the file manifest sortedDepMap[key].symbols = sortedSymbolsMap; } if (napiConfig.labeling) { let apiKey: string | undefined; if (labelingApiKey) { apiKey = labelingApiKey; } else { if (napiConfig.labeling.modelProvider === GOOGLE_PROVIDER) { apiKey = globalConfig.labeling?.apiKeys.google; } if (napiConfig.labeling.modelProvider === OPENAI_PROVIDER) { apiKey = globalConfig.labeling?.apiKeys.openai; } if (napiConfig.labeling.modelProvider === ANTHROPIC_PROVIDER) { apiKey = globalConfig.labeling?.apiKeys.anthropic; } } if (!apiKey) { console.warn( "No API key found for the selected model provider. Please run `napi set apiKey` to set an API key.", ); return sortedDepMap; } const labeledDependencyManifest = await generateSymbolDescriptions( files, sortedDepMap, apiKey, napiConfig.labeling.modelProvider, napiConfig.labeling.maxConcurrency, ); return labeledDependencyManifest; } else { return sortedDepMap; } } ================================================ FILE: src/manifest/dependencyManifest/java/index.ts ================================================ import { type DependencyManifest, metricCharacterCount, metricCodeCharacterCount, metricCodeLineCount, metricCyclomaticComplexity, metricDependencyCount, metricDependentCount, metricLinesCount, type SymbolDependencyManifest, type SymbolType, } from "../types.ts"; import { JavaDependencyFormatter } from "../../../languagePlugins/java/dependencyFormatting/index.ts"; // import { JavaMetricsAnalyzer } from "../../../languagePlugins/java/metrics/index.ts"; import { javaLanguage } from "../../../helpers/treeSitter/parsers.ts"; import { JavaMetricsAnalyzer } from "../../../languagePlugins/java/metrics/index.ts"; export function generateJavaDependencyManifest( files: Map, ): DependencyManifest { console.time("generateJavaDependencyManifest"); console.info("Processing project..."); const formatter = new JavaDependencyFormatter(files); const metricsAnalyzer = new JavaMetricsAnalyzer(); const manifest: DependencyManifest = {}; const newfiles = formatter.mapper.files; const filecount = newfiles.size; let i = 0; for (const [, { path }] of newfiles) { console.info(`Processing ${path} (${++i}/${filecount})`); const fm = formatter.formatFile(path); const cSyms = fm.symbols; const symbols: Record = {}; for (const [symName, symbol] of Object.entries(cSyms)) { const symType = symbol.type; const dependencies = symbol.dependencies; const metrics = metricsAnalyzer.analyzeNode(symbol.node); symbols[symName] = { id: symName, type: symType as SymbolType, positions: [{ start: { index: symbol.node.startIndex, row: symbol.node.startPosition.row, column: symbol.node.startPosition.column, }, end: { index: symbol.node.endIndex, row: symbol.node.endPosition.row, column: symbol.node.endPosition.column, }, }], description: "", metrics: { [metricCharacterCount]: metrics.characterCount, [metricCodeCharacterCount]: metrics.codeCharacterCount, [metricLinesCount]: metrics.linesCount, [metricCodeLineCount]: metrics.codeLinesCount, [metricDependencyCount]: Object.keys(dependencies).length, [metricDependentCount]: 0, [metricCyclomaticComplexity]: metrics.cyclomaticComplexity, }, dependencies: dependencies, dependents: {}, }; } const metrics = metricsAnalyzer.analyzeNode(fm.rootNode); manifest[path] = { id: fm.id, filePath: fm.filePath, language: javaLanguage, metrics: { [metricCharacterCount]: metrics.characterCount, [metricCodeCharacterCount]: metrics.codeCharacterCount, [metricLinesCount]: metrics.linesCount, [metricCodeLineCount]: metrics.codeLinesCount, [metricDependencyCount]: Object.keys(fm.dependencies).length, [metricDependentCount]: 0, [metricCyclomaticComplexity]: metrics.cyclomaticComplexity, }, dependencies: fm.dependencies, symbols: symbols, dependents: {}, }; } console.info("Populating dependents..."); i = 0; for (const fm of Object.values(manifest)) { const path = fm.filePath; console.info(`Populating dependents for ${path} (${++i}/${filecount})`); for (const symbol of Object.values(fm.symbols)) { for (const dpncy of Object.values(symbol.dependencies)) { for (const depsymbol of Object.values(dpncy.symbols)) { const otherFile = manifest[dpncy.id]; const otherSymbol = otherFile.symbols[depsymbol]; if (!otherSymbol.dependents[fm.id]) { otherSymbol.dependents[fm.id] = { id: fm.id, symbols: {}, }; } otherSymbol.dependents[fm.id].symbols[symbol.id] = symbol.id; fm.metrics[metricDependentCount]++; symbol.metrics[metricDependentCount]++; } } } } console.timeEnd("generateJavaDependencyManifest"); return manifest; } ================================================ FILE: src/manifest/dependencyManifest/labeling/graph.ts ================================================ import { Annotation, StateGraph } from "@langchain/langgraph"; import type { DependencyManifest } from "../types.ts"; import type { GroupLayer, SymbolRef } from "./types.ts"; import { symbolRefToKey } from "./grouping.ts"; import { type AIMessage, HumanMessage, SystemMessage, } from "@langchain/core/messages"; import { z } from "zod"; import type { BaseChatModel } from "@langchain/core/language_models/chat_models"; function getSymbolDependencyContextMessages( state: typeof workflowState.State, symbolRef: SymbolRef, ) { const symbolDependencyManifest = state.dependencyManifest[symbolRef.fileId].symbols[symbolRef.symbolId]; const messages: HumanMessage[] = []; for ( const fileDependency of Object.values( symbolDependencyManifest.dependencies, ) ) { // First check if external if (fileDependency.isExternal) { const symbols = Object.values(fileDependency.symbols); if (symbols.length === 0) { messages.push( new HumanMessage( `External dependency from the following source: ${fileDependency.id}`, ), ); } else { messages.push( new HumanMessage( `External dependency from the following source: ${fileDependency.id} with the following symbols: ${symbols.join(", ")}`, ), ); } continue; } const filedependencyManifest = state.dependencyManifest[fileDependency.id]; for (const symbol of Object.values(fileDependency.symbols)) { const symbolDependencyManifest = state.dependencyManifest[fileDependency.id].symbols[symbol]; // Then check if we have the symbol in the labelManifest if (filedependencyManifest) { const symbolDependencyManifest = filedependencyManifest.symbols[symbol]; if (symbolDependencyManifest.description) { messages.push( new HumanMessage( `Dependency from the following source: ${fileDependency.id} with the following symbol: ${symbolDependencyManifest.id} (${symbolDependencyManifest.type}) Here is a brief description of this symbol: ${symbolDependencyManifest.description}`, ), ); continue; } } // Last, check if we have the symbol in the notYetProcessedSymbolDescriptionMap const key = symbolRefToKey({ fileId: fileDependency.id, symbolId: symbol, }); const description = state.notYetProcessedSymbolDescriptionMap.get(key); if (description) { messages.push( new HumanMessage( `Dependency from the following source: ${fileDependency.id} with the following symbol: ${symbolDependencyManifest.id} (${symbolDependencyManifest.type}) Here is a brief description of this symbol: ${description}`, ), ); } } } if (messages.length >= 1) { messages.unshift( new HumanMessage( "Next messages is a list of dependencies to the symbol you need to process as well as some information about each of them:", ), ); } return messages; } function generateContentsMessages( state: typeof workflowState.State, symbolRef: SymbolRef, ) { const file = state.files.get(symbolRef.fileId); if (!file) { throw new Error(`File not found: ${symbolRef.fileId}`); } const symbolManifest = state.dependencyManifest[symbolRef.fileId].symbols[symbolRef.symbolId]; const contents: string[] = []; for (const position of symbolManifest.positions) { const lines = file.content.split("\n"); const symbolLines: string[] = []; // get content between startLine and endLine for (let i = position.start.row; i <= position.end.row; i++) { symbolLines.push(lines[i]); } const content = symbolLines.join("\n"); contents.push(content); } const messages: HumanMessage[] = []; messages.push( new HumanMessage( `Here is the symbolRef of the symbol that you need to process: { fileId: ${symbolRef.fileId}, symbolId: ${symbolRef.symbolId} }`, ), ); if (contents.length === 0) { messages.push( new HumanMessage( "The symbol that you need to process has no content.", ), ); } else { messages.push( new HumanMessage( `The symbol that you need to process has content. Here is the content (${contents.length} parts):`, ), ); for (const [index, content] of contents.entries()) { messages.push( new HumanMessage( `Part ${index + 1} of ${contents.length}: ${content}`, ), ); } } return messages; } const workflowState = Annotation.Root({ files: Annotation>, dependencyManifest: Annotation, groupLayer: Annotation, notYetProcessedSymbolDescriptionMap: Annotation>, model: Annotation, results: Annotation<{ symbolRef: SymbolRef; description: string }[]>, }); export function createGroupSymbolLabelingWorkflow( files: Map, dependencyManifest: DependencyManifest, groupLayer: GroupLayer, model: BaseChatModel, ) { function initNode(_state: typeof workflowState.State) { return { files, dependencyManifest, groupLayer, notYetProcessedSymbolDescriptionMap: new Map(), model, results: [], }; } async function generateDescriptionsForYetToBeProcessedSymbols( state: typeof workflowState.State, ) { if (state.groupLayer.symbolRefsToProcess.length === 0) { // no symbol yet to process // we have all the context needed in the labelManifest return state; } const messagesBatch: AIMessage[][] = []; for (const symbolRef of state.groupLayer.symbolRefsToProcess) { const messages = generateDescriptionMessagesForSymbol( state, symbolRef, ); messagesBatch.push(messages); } const schema = z.object({ symbolRef: z.object({ fileId: z.string().describe("The id of the file."), symbolId: z.string().describe("The id of the symbol."), }), description: z.string().max(500).describe( "A short description of what the symbol is doing.", ), }); const results = await (model as BaseChatModel).withStructuredOutput(schema) .batch(messagesBatch) as z.infer[]; for (const result of results) { const key = symbolRefToKey(result.symbolRef); const description = result.description; state.notYetProcessedSymbolDescriptionMap.set(key, description); } return state; } function generateDescriptionMessagesForSymbol( state: typeof workflowState.State, symbolRef: SymbolRef, ) { const messages: AIMessage[] = []; messages.push( new SystemMessage( "You are a helpful assistant that generates a short description of what the symbol (class, function, etc.) is doing.", ), ); const symbolDependencyMessages = getSymbolDependencyContextMessages( state, symbolRef, ); for (const message of symbolDependencyMessages) { messages.push(message); } const symbolContentsMessages = generateContentsMessages( state, symbolRef, ); for (const message of symbolContentsMessages) { messages.push(message); } return messages; } async function generateLabels( state: typeof workflowState.State, ) { const messagesBatch: AIMessage[][] = []; for (const symbolRef of state.groupLayer.symbolRefsToProcess) { const messages = generateMessagesForLabelingSymbol(state, symbolRef); messagesBatch.push(messages); } const schema = z.object({ symbolRef: z.object({ fileId: z.string().describe("The id of the file."), symbolId: z.string().describe("The id of the symbol."), }), description: z.string().describe( "A business focused description of what the symbol is doing (max 500 char).", ), }); const results = await (model as BaseChatModel).withStructuredOutput(schema) .batch(messagesBatch) as z.infer[]; for (const result of results) { state.results.push(result); } return state; } function generateMessagesForLabelingSymbol( state: typeof workflowState.State, symbolRef: SymbolRef, ) { const messages: AIMessage[] = []; messages.push( new SystemMessage( "You are a helpful assistant that generates labels for a symbol (class, function, etc.).", ), ); const symbolDependencyMessages = getSymbolDependencyContextMessages( state, symbolRef, ); for (const message of symbolDependencyMessages) { messages.push(message); } const symbolContentsMessages = generateContentsMessages( state, symbolRef, ); for (const message of symbolContentsMessages) { messages.push(message); } return messages; } const workflow = new StateGraph(workflowState).addNode("init", initNode) // nodes .addNode( "generateDescriptionsForYetToBeProcessedSymbols", generateDescriptionsForYetToBeProcessedSymbols, ) .addNode("generateLabels", generateLabels) // edges .addEdge("__start__", "init") .addEdge("init", "generateDescriptionsForYetToBeProcessedSymbols") .addEdge( "generateDescriptionsForYetToBeProcessedSymbols", "generateLabels", ) .addEdge("generateLabels", "__end__"); return workflow.compile(); } ================================================ FILE: src/manifest/dependencyManifest/labeling/grouping.ts ================================================ import type { DependencyManifest } from "../types.ts"; import type { GroupLayer, SymbolRef } from "./types.ts"; // ============================================================================= // CONSTANTS AND CONFIGURATION // ============================================================================= /** * The separator used to join the fileId and symbolId in the key. * This is a URL-safe separator that won't conflict with file paths. */ const JOINT_SYMBOL_SEPARATOR = "::"; // ============================================================================= // SYMBOL KEY MANAGEMENT UTILITIES // ============================================================================= /** * Converts a SymbolRef into a unique string key for efficient lookups. * Uses URL encoding to handle special characters in file paths and symbol names. * * @param ref - The symbol reference to convert * @returns A unique string key for the symbol */ export function symbolRefToKey(ref: SymbolRef): string { const urlEncodedFileId = encodeURIComponent(ref.fileId); const urlEncodedSymbolId = encodeURIComponent(ref.symbolId); return `${urlEncodedFileId}${JOINT_SYMBOL_SEPARATOR}${urlEncodedSymbolId}`; } /** * Converts a symbol key back into a SymbolRef. * This is the inverse operation of symbolRefToKey. * * @param key - The string key to convert back * @returns The original SymbolRef */ export function keyToSymbolRef(key: string): SymbolRef { const [urlEncodedFileId, urlEncodedSymbolId] = key.split( JOINT_SYMBOL_SEPARATOR, ); const fileId = decodeURIComponent(urlEncodedFileId); const symbolId = decodeURIComponent(urlEncodedSymbolId); return { fileId, symbolId }; } // ============================================================================= // DEPENDENCY ANALYSIS FUNCTIONS // ============================================================================= /** * Gets all unprocessed internal dependencies for a given symbol. * This function filters out: * - External dependencies (outside the codebase) * - Self-dependencies (symbol depending on itself) * - Already processed dependencies * * @param symbolRef - The symbol to analyze * @param manifest - The complete dependency manifest * @param processedSymbols - Set of already processed symbol keys * @returns Array of dependency symbol keys that haven't been processed yet */ function getUnprocessedDependencies( symbolRef: SymbolRef, manifest: DependencyManifest, processedSymbols: Set, ): string[] { const currentSymbolKey = symbolRefToKey(symbolRef); const symbolManifest = manifest[symbolRef.fileId] ?.symbols[symbolRef.symbolId]; if (!symbolManifest) return []; const dependencies: string[] = []; // Iterate through all files this symbol depends on for ( const [depFileId, depInfo] of Object.entries(symbolManifest.dependencies) ) { // Skip external dependencies - we only care about internal code dependencies if (depInfo.isExternal) continue; // Check each symbol within the dependency file for (const depSymbolId of Object.keys(depInfo.symbols)) { // Verify the dependency symbol actually exists in the manifest if (manifest[depFileId]?.symbols[depSymbolId]) { const depKey = symbolRefToKey({ fileId: depFileId, symbolId: depSymbolId, }); // Skip self-dependencies and already processed symbols if (currentSymbolKey !== depKey && !processedSymbols.has(depKey)) { dependencies.push(depKey); } } } } return dependencies; } // ============================================================================= // STRONGLY CONNECTED COMPONENTS (SCC) ALGORITHM // ============================================================================= /** * Tarjan's strongly connected components algorithm. * This finds groups of symbols that form dependency cycles. * * A strongly connected component is a maximal set of vertices such that * for every pair of vertices u and v, there is a directed path from u to v * and a directed path from v to u. * * Time complexity: O(V + E) where V is vertices and E is edges * * https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm#The_algorithm_in_pseudocode * * @param graph - Adjacency list representation of the dependency graph * @returns Array of sets, each containing symbol keys that form an SCC */ function stronglyConnectedComponents( graph: Map>, ): Array> { // Tarjan's algorithm state const indices = new Map(); // Discovery time of each vertex const lowlinks = new Map(); // Lowest reachable discovery time const onStack = new Set(); // Vertices currently on the stack const stack: string[] = []; // Stack for the algorithm const components: Array> = []; // Resulting SCCs let index = 0; // Global discovery time counter /** * Recursive function that performs the depth-first search for Tarjan's algorithm. * This is the core of the SCC detection logic. */ function strongConnect(v: string): void { // Initialize the vertex indices.set(v, index); lowlinks.set(v, index); index++; stack.push(v); onStack.add(v); // Check all neighbors (dependencies) const neighbors = graph.get(v) || new Set(); for (const w of neighbors) { if (!indices.has(w)) { // Neighbor w has not yet been visited; recurse on it strongConnect(w); lowlinks.set(v, Math.min(lowlinks.get(v)!, lowlinks.get(w)!)); } else if (onStack.has(w)) { // Neighbor w is in the stack and hence in the current SCC lowlinks.set(v, Math.min(lowlinks.get(v)!, indices.get(w)!)); } } // If v is a root node, pop the stack and create an SCC if (lowlinks.get(v) === indices.get(v)) { const component = new Set(); let w: string; do { w = stack.pop()!; onStack.delete(w); component.add(w); } while (w !== v); components.push(component); } } // Start DFS from each unvisited vertex for (const v of graph.keys()) { if (!indices.has(v)) { strongConnect(v); } } return components; } /** * Builds a dependency graph from remaining symbols and finds SCCs. * This creates the graph representation needed for SCC analysis. * * @param remainingSymbols - Map of symbol keys to SymbolRefs that haven't been processed * @param manifest - The complete dependency manifest * @param processedSymbols - Set of already processed symbol keys * @returns Array of SCCs (sets of symbol keys that form cycles) */ function findStronglyConnectedComponents( remainingSymbols: Map, manifest: DependencyManifest, processedSymbols: Set, ): Array> { const graph = new Map>(); // Build the dependency graph for remaining symbols only for (const [symbolKey, symbolRef] of remainingSymbols) { const dependencies = getUnprocessedDependencies( symbolRef, manifest, processedSymbols, ); // Only include dependencies that are also in remainingSymbols // This ensures we only analyze cycles among unprocessed symbols const filteredDeps = dependencies.filter((dep) => remainingSymbols.has(dep) ); graph.set(symbolKey, new Set(filteredDeps)); } return stronglyConnectedComponents(graph); } // ============================================================================= // CYCLE BREAKING STRATEGIES // ============================================================================= /** * Selects the best symbol from a set based on dependency count. * "Best" means the symbol with the fewest unprocessed dependencies, * which makes it a good candidate for breaking cycles with minimal impact. * * @param symbolKeys - Set of symbol keys to choose from * @param remainingSymbols - Map of remaining symbols * @param manifest - The dependency manifest * @param processedSymbols - Set of processed symbols * @returns The best SymbolRef or null if none found */ function selectBestSymbol( symbolKeys: Set, remainingSymbols: Map, manifest: DependencyManifest, processedSymbols: Set, ): SymbolRef | null { let bestSymbol: SymbolRef | null = null; let minDeps = Infinity; for (const symbolKey of symbolKeys) { const symbolRef = remainingSymbols.get(symbolKey); if (!symbolRef) continue; const depCount = getUnprocessedDependencies(symbolRef, manifest, processedSymbols).length; if (depCount < minDeps) { minDeps = depCount; bestSymbol = symbolRef; } } return bestSymbol; } /** * Selects optimal symbols to break dependency cycles using SCC analysis. * * This function implements a two-phase strategy: * 1. Break major cycles by selecting one representative from each large SCC * 2. Add all remaining independent symbols that don't depend on selected ones * * The goal is to maximize the number of symbols that can be processed * while minimizing the total notYetProcessedDependencySymbolRefs across all layers. * * @param remainingSymbols - Map of unprocessed symbols * @param manifest - The dependency manifest * @param processedSymbols - Set of processed symbols * @returns Object containing selected symbols and their dependencies */ function selectCycleBreakers( remainingSymbols: Map, manifest: DependencyManifest, processedSymbols: Set, ): { symbols: SymbolRef[]; dependencies: SymbolRef[] } { const sccs = findStronglyConnectedComponents( remainingSymbols, manifest, processedSymbols, ); const selectedSymbols: SymbolRef[] = []; const selectedKeys = new Set(); // Phase 1: Break major cycles (large SCCs with 3+ symbols) // These represent significant circular dependencies that need to be broken const largeSCCs = sccs.filter((scc) => scc.size >= 3); for (const scc of largeSCCs) { // Select the symbol with minimum dependencies to minimize impact const bestSymbol = selectBestSymbol( scc, remainingSymbols, manifest, processedSymbols, ); if (bestSymbol) { selectedSymbols.push(bestSymbol); selectedKeys.add(symbolRefToKey(bestSymbol)); } } // Phase 2: Add all qualifying independent symbols (small SCCs) // Small SCCs (size < 3) are typically independent symbols or simple mutual dependencies const smallSCCs = sccs.filter((scc) => scc.size < 3); const candidates = smallSCCs .flatMap((scc) => Array.from(scc)) .map((symbolKey) => { const symbolRef = remainingSymbols.get(symbolKey); if (!symbolRef) return null; return { key: symbolKey, ref: symbolRef, deps: getUnprocessedDependencies(symbolRef, manifest, processedSymbols) .length, }; }) .filter(Boolean) .sort((a, b) => a!.deps - b!.deps); // Sort by dependency count (prefer fewer dependencies) // Greedily add independent symbols that don't depend on already selected ones for (const candidate of candidates) { // Only add if it doesn't depend on any already selected symbols // This ensures symbols in the same batch can be processed in parallel const dependencies = getUnprocessedDependencies( candidate!.ref, manifest, processedSymbols, ); const dependsOnSelected = dependencies.some((depKey) => selectedKeys.has(depKey) ); if (!dependsOnSelected) { selectedSymbols.push(candidate!.ref); selectedKeys.add(candidate!.key); } } // Phase 3: Calculate all dependencies for the selected batch // These will become the notYetProcessedDependencySymbolRefs for this layer const allDependencies = new Set(); for (const symbolRef of selectedSymbols) { const deps = getUnprocessedDependencies( symbolRef, manifest, processedSymbols, ); deps.forEach((dep) => allDependencies.add(dep)); } return { symbols: selectedSymbols, dependencies: Array.from(allDependencies).map(keyToSymbolRef), }; } // ============================================================================= // INDEPENDENT SYMBOL DETECTION // ============================================================================= /** * Finds symbols that have no unprocessed dependencies. * These symbols can be processed immediately without waiting for other symbols. * This is the optimal case - no cycle breaking needed. * * @param remainingSymbols - Map of unprocessed symbols * @param manifest - The dependency manifest * @param processedSymbols - Set of processed symbols * @returns Array of symbols that can be processed independently */ function findIndependentSymbols( remainingSymbols: Map, manifest: DependencyManifest, processedSymbols: Set, ): SymbolRef[] { return Array.from(remainingSymbols.values()).filter( (symbolRef) => getUnprocessedDependencies(symbolRef, manifest, processedSymbols) .length === 0, ); } // ============================================================================= // BATCH PROCESSING UTILITIES // ============================================================================= /** * Marks a batch of symbols as processed by updating the tracking sets. * This is a utility function to keep the main algorithm clean. * * @param symbols - Array of symbols to mark as processed * @param remainingSymbols - Map to remove symbols from * @param processedSymbols - Set to add symbols to */ function processBatch( symbols: SymbolRef[], remainingSymbols: Map, processedSymbols: Set, ): void { for (const symbolRef of symbols) { const key = symbolRefToKey(symbolRef); remainingSymbols.delete(key); processedSymbols.add(key); } } // ============================================================================= // MAIN ALGORITHM // ============================================================================= /** * Generates group layers for dependency-aware parallel processing. * * This is the main algorithm that creates a series of layers where: * - Symbols in each layer can be processed in parallel * - Symbols in layer N only depend on symbols from layers 0 to N-1 * - The algorithm handles circular dependencies using SCC analysis * * Algorithm Overview: * 1. Start with all symbols as "remaining" * 2. While there are remaining symbols: * a. Find symbols with no dependencies → process them (optimal case) * b. If no independent symbols exist → use SCC-based cycle breaking * 3. Each iteration creates a new layer * * The algorithm prioritizes processing independent symbols first (no dependencies), * and only resorts to cycle breaking when necessary. This minimizes the number * of layers and maximizes parallelism. * * @param manifest - The complete dependency manifest for the codebase * @returns Array of GroupLayers representing the processing order */ export function generateGroupLayers( manifest: DependencyManifest, ): GroupLayer[] { const groupLayers: GroupLayer[] = []; const processedSymbols = new Set(); // Symbols we've already processed const remainingSymbols = new Map(); // Symbols still to process // Initialize: all symbols start as "remaining" for (const [fileId, fileManifest] of Object.entries(manifest)) { for (const symbolId of Object.keys(fileManifest.symbols)) { const ref = { fileId, symbolId }; remainingSymbols.set(symbolRefToKey(ref), ref); } } let level = 0; // Main processing loop: continue until all symbols are processed while (remainingSymbols.size > 0) { // Strategy 1: Look for symbols with no dependencies (optimal case) const independentSymbols = findIndependentSymbols( remainingSymbols, manifest, processedSymbols, ); if (independentSymbols.length > 0) { // Found independent symbols - process them all in this layer groupLayers.push({ level, symbolRefsToProcess: independentSymbols, notYetProcessedDependencySymbolRefs: [], // No dependencies since they're independent }); processBatch(independentSymbols, remainingSymbols, processedSymbols); } else { // Strategy 2: No independent symbols - must break cycles using SCC analysis const result = selectCycleBreakers( remainingSymbols, manifest, processedSymbols, ); groupLayers.push({ level, symbolRefsToProcess: result.symbols, notYetProcessedDependencySymbolRefs: result.dependencies, }); processBatch(result.symbols, remainingSymbols, processedSymbols); } level++; } return groupLayers; } ================================================ FILE: src/manifest/dependencyManifest/labeling/index.ts ================================================ import type { DependencyManifest } from "../types.ts"; import { getModel, type ModelProvider } from "./model.ts"; import { generateGroupLayers } from "./grouping.ts"; import { createGroupSymbolLabelingWorkflow } from "./graph.ts"; export async function generateSymbolDescriptions( files: Map, dependencyManifest: DependencyManifest, apiKey: string, modelProvider: ModelProvider, maxConcurrency?: number, ): Promise { console.info("Generating descriptions for symbols..."); const groups = generateGroupLayers(dependencyManifest); console.info(`✅ Successfully generated ${groups.length} independent groups`); const model = getModel(modelProvider, apiKey, maxConcurrency); console.info("Starting symbol labeling..."); for (const [index, group] of groups.entries()) { const workflow = createGroupSymbolLabelingWorkflow( files, dependencyManifest, group, model, ); const state = await workflow.invoke({}); for (const result of state.results) { dependencyManifest[result.symbolRef.fileId].symbols[ result.symbolRef.symbolId ].description = result.description; } console.info( `✅ Successfully processed group ${ index + 1 } of ${groups.length} groups. ${group.symbolRefsToProcess.length} symbols processed`, ); } return dependencyManifest; } ================================================ FILE: src/manifest/dependencyManifest/labeling/model.ts ================================================ import { ChatGoogleGenerativeAI } from "@langchain/google-genai"; import type { BaseChatModel } from "@langchain/core/language_models/chat_models"; import { ChatOpenAI } from "@langchain/openai"; import { ChatAnthropic } from "@langchain/anthropic"; export const GOOGLE_PROVIDER = "google"; export const OPENAI_PROVIDER = "openai"; export const ANTHROPIC_PROVIDER = "anthropic"; export type ModelProvider = | typeof GOOGLE_PROVIDER | typeof OPENAI_PROVIDER | typeof ANTHROPIC_PROVIDER; export function getModel( provider: ModelProvider, apiKey: string, maxConcurrency?: number, ): BaseChatModel { // // Later we will support multiple model and will get them from the config if (provider === OPENAI_PROVIDER) { return new ChatOpenAI({ apiKey, model: "o3-mini", maxConcurrency: maxConcurrency ? maxConcurrency : Infinity, }) as unknown as BaseChatModel; } if (provider === GOOGLE_PROVIDER) { return new ChatGoogleGenerativeAI({ apiKey, model: "gemini-2.5-flash-lite-preview-06-17", maxConcurrency: maxConcurrency ? maxConcurrency : Infinity, }) as BaseChatModel; } if (provider === ANTHROPIC_PROVIDER) { return new ChatAnthropic({ apiKey, model: "claude-3-5-sonnet-latest", maxConcurrency: maxConcurrency ? maxConcurrency : Infinity, }) as unknown as BaseChatModel; } throw new Error(`Unsupported model provider: ${provider}`); } ================================================ FILE: src/manifest/dependencyManifest/labeling/types.ts ================================================ /** * Reference to a specific symbol within a file. * Used to uniquely identify symbols across the entire codebase. */ export type SymbolRef = { /** The unique identifier of the file containing the symbol */ fileId: string; /** The unique identifier of the symbol within the file */ symbolId: string; }; /** * Represents a layer of symbols that can be processed in parallel. * The key insight is that symbols in layer N only depend on symbols from layers 0 to N-1. * This allows for efficient parallel processing while respecting dependency order. */ export type GroupLayer = { /** * The dependency level (0 to n). * Symbols at level n depend on symbols of groups from levels 0 through n-1. */ level: number; /** * The symbolRefs to process in this layer. * These symbolRefs can be processed in parallel. They do not depend on each other. */ symbolRefsToProcess: SymbolRef[]; /** * Dependency symbolRefs that some symbolRefsToProcess depend on. * These symbolRefs will be process in a later layer. * We have no information about them. * This should be as little as possible. Best effort is made to create the groups * that have the least notYetProcessedDependencySymbolRefs as possible. Ideally none. */ notYetProcessedDependencySymbolRefs: SymbolRef[]; }; ================================================ FILE: src/manifest/dependencyManifest/python/index.ts ================================================ import type Parser from "tree-sitter"; import { type DependencyInfo, type DependencyManifest, type DependentInfo, type FileDependencyManifest, metricCharacterCount, metricCodeCharacterCount, metricCodeLineCount, metricCyclomaticComplexity, metricDependencyCount, metricDependentCount, metricLinesCount, type SymbolDependencyManifest, } from "../types.ts"; import { PythonExportExtractor } from "../../../languagePlugins/python/exportExtractor/index.ts"; import { pythonParser } from "../../../helpers/treeSitter/parsers.ts"; import { PythonModuleResolver } from "../../../languagePlugins/python/moduleResolver/index.ts"; import { PythonUsageResolver } from "../../../languagePlugins/python/usageResolver/index.ts"; import { PythonDependencyResolver } from "../../../languagePlugins/python/dependencyResolver/index.ts"; import { PythonItemResolver } from "../../../languagePlugins/python/itemResolver/index.ts"; import { PythonImportExtractor } from "../../../languagePlugins/python/importExtractor/index.ts"; import type { localConfigSchema } from "../../../cli/middlewares/napiConfig.ts"; import type z from "zod"; import { PythonMetricsAnalyzer } from "../../../languagePlugins/python/metricAnalyzer/index.ts"; /** * Builds dependent relationships in the manifest by traversing all dependencies */ function generateDependentsForManifest( manifest: DependencyManifest, ): DependencyManifest { // Go through each file in the manifest for (const [fileId, fileManifest] of Object.entries(manifest)) { // Process file-level dependencies first for ( const [depFileId, depInfo] of Object.entries( fileManifest.dependencies, ) ) { // Only proceed if it's an internal dependency and the target file actually exists in our manifest if (!depInfo.isExternal && manifest[depFileId]) { const depFile = manifest[depFileId]; // Add file-level dependent relationship if (!depFile.dependents[fileId]) { const dependent: DependentInfo = { id: fileId, symbols: {}, }; depFile.dependents[fileId] = dependent; } // For each symbol name that we reference from the dependency at file level for (const usedSymbolName of Object.keys(depInfo.symbols)) { // Check if that symbol actually exists in the target file if (depFile.symbols[usedSymbolName]) { const targetSymbol = depFile.symbols[usedSymbolName]; // Ensure there's a record for the dependent file if (!targetSymbol.dependents[fileId]) { targetSymbol.dependents[fileId] = { id: fileId, symbols: {}, }; } // Record that the file as a whole depends on 'usedSymbolName' in depFile depFile.dependents[fileId].symbols[usedSymbolName] = usedSymbolName; } } } } // For each symbol in the file for (const [symbolId, symbolData] of Object.entries(fileManifest.symbols)) { // For each dependency that this symbol uses for ( const [depFileId, depInfo] of Object.entries( symbolData.dependencies, ) ) { // Only proceed if it's an internal dependency and the target file actually exists in our manifest if (!depInfo.isExternal && manifest[depFileId]) { const depFile = manifest[depFileId]; // Add file-level dependent relationship if (!depFile.dependents[fileId]) { depFile.dependents[fileId] = { id: fileId, symbols: {}, }; } // For each symbol name that we reference from the dependency for (const usedSymbolName of Object.keys(depInfo.symbols)) { // Check if that symbol actually exists in the target file if (depFile.symbols[usedSymbolName]) { const targetSymbol = depFile.symbols[usedSymbolName]; // Ensure there's a record for the dependent file if (!targetSymbol.dependents[fileId]) { const dependent: DependentInfo = { id: fileId, symbols: {}, }; targetSymbol.dependents[fileId] = dependent; } // Record that 'symbolId' in this file depends on 'usedSymbolName' in depFile targetSymbol.dependents[fileId].symbols[symbolId] = symbolId; // Update the file-level dependents with this symbol relationship depFile.dependents[fileId].symbols[symbolId] = symbolId; } } } } } } // Update dependency and dependent counts in metrics for (const fileManifest of Object.values(manifest)) { // Update file-level metrics fileManifest.metrics[metricDependencyCount] = Object.keys( fileManifest.dependencies, ).length; fileManifest.metrics[metricDependentCount] = Object.keys( fileManifest.dependents, ).length; // Update symbol-level metrics for (const symbolData of Object.values(fileManifest.symbols)) { symbolData.metrics[metricDependencyCount] = Object.keys( symbolData.dependencies, ).length; symbolData.metrics[metricDependentCount] = Object.keys( symbolData.dependents, ).length; } } return manifest; } /** * Generates a dependency manifest for Python files */ export function generatePythonDependencyManifest( files: Map, napiConfig: z.infer, ): DependencyManifest { const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const [filePath, { content: fileContent }] of files) { try { const rootNode = pythonParser.parse(fileContent, undefined, { bufferSize: fileContent.length + 10, }).rootNode; parsedFiles.set(filePath, { path: filePath, rootNode }); } catch (e) { console.error(`Failed to parse ${filePath}, skipping`); console.error(e); } } console.time("generatePythonDependencyManifest"); const parser = pythonParser; const pythonVersion = napiConfig.python?.version; if (!pythonVersion) { throw new Error( "Python version is required in the .napirc file (audit.pythonVersion).", ); } console.time("generatePythonDependencyManifest:initialization"); console.info("Initializing Python export resolver..."); const exportExtractor = new PythonExportExtractor(parser, parsedFiles); console.info("Initializing Python import resolver..."); const importExtractor = new PythonImportExtractor(parser, parsedFiles); console.info("Initializing Python module resolver..."); const moduleResolver = new PythonModuleResolver( new Set(parsedFiles.keys()), pythonVersion, ); console.info("Initializing Python item resolver..."); const itemResolver = new PythonItemResolver( exportExtractor, importExtractor, moduleResolver, ); console.info("Initializing Python usage resolver..."); const usageResolver = new PythonUsageResolver(parser, exportExtractor); console.info("Initializing Python dependency resolver..."); const complexityAnalyzer = new PythonMetricsAnalyzer(parser); const dependencyResolver = new PythonDependencyResolver( parsedFiles, exportExtractor, importExtractor, itemResolver, usageResolver, moduleResolver, complexityAnalyzer, ); console.timeEnd("generatePythonDependencyManifest:initialization"); console.time("generatePythonDependencyManifest:processing"); console.info("Generating Python dependency manifest..."); let manifest: DependencyManifest = {}; let i = 0; for (const file of files.values()) { const fileDependencies = dependencyResolver.getFileDependencies(file.path); const dependencies: Record = {}; for (const dep of fileDependencies.dependencies.values()) { const symbols: Record = {}; dep.symbols.forEach((symbol) => { symbols[symbol] = symbol; }); dependencies[dep.id] = { id: dep.id, isExternal: dep.isExternal, symbols, }; } const symbols: Record = {}; for (const symbol of fileDependencies.symbols.values()) { const dependencies: Record = {}; // Process symbol dependencies from all sources at once for (const dep of symbol.dependencies.values()) { const symbols: Record = {}; dep.symbols.forEach((symbol) => { symbols[symbol] = symbol; }); dependencies[dep.id] = { id: dep.id, isExternal: dep.isExternal, symbols, }; } symbols[symbol.id] = { id: symbol.id, type: symbol.type, positions: symbol.positions, description: "", metrics: { [metricLinesCount]: symbol.metrics.linesCount, [metricCodeLineCount]: symbol.metrics.codeLineCount, [metricCharacterCount]: symbol.metrics.characterCount, [metricCodeCharacterCount]: symbol.metrics.codeCharacterCount, [metricDependencyCount]: symbol.dependencies.size, [metricDependentCount]: 0, // Will be computed later [metricCyclomaticComplexity]: symbol.metrics.cyclomaticComplexity, }, dependencies, dependents: {}, // Will be computed later }; } const fileManifest: FileDependencyManifest = { id: file.path, filePath: file.path, metrics: { [metricLinesCount]: fileDependencies.metrics.codeLineCount, [metricCodeLineCount]: fileDependencies.metrics.codeLineCount, [metricCharacterCount]: fileDependencies.metrics.characterCount, [metricCodeCharacterCount]: fileDependencies.metrics.codeCharacterCount, [metricDependencyCount]: fileDependencies.dependencies.size, [metricDependentCount]: 0, // Will be computed later [metricCyclomaticComplexity]: fileDependencies.metrics.cyclomaticComplexity, }, language: parser.getLanguage().name, dependencies, dependents: {}, // Will be computed later symbols, }; manifest[file.path] = fileManifest; console.info(`✅ Processed ${i + 1}/${files.size}: ${file.path}`); i++; } console.info( `Generated Python dependency manifest for ${parsedFiles.size} files`, ); console.timeEnd("generatePythonDependencyManifest:processing"); console.time("generatePythonDependencyManifest:dependents"); console.info("Generating Python dependents..."); manifest = generateDependentsForManifest(manifest); console.info("Generated Python dependents"); console.timeEnd("generatePythonDependencyManifest:dependents"); console.info("Python dependency manifest generated"); console.timeEnd("generatePythonDependencyManifest"); return manifest; } ================================================ FILE: src/manifest/dependencyManifest/types.ts ================================================ /** Identifies the "class" instance type. */ export const classSymbolType = "class"; /** Identifies the "struct" instance type. */ export const structSymbolType = "struct"; /** Identifies the "enum" instance type. */ export const enumSymbolType = "enum"; /** Identifies the "union" instance type. */ export const unionSymbolType = "union"; /** Identifies the "typedef" instance type. */ export const typedefSymbolType = "typedef"; /** Identifies the "interface" instance type. */ export const interfaceSymbolType = "interface"; /** Identifies the "record" instance type. */ export const recordSymbolType = "record"; /** Identifies the "delegate" instance type. */ export const delegateSymbolType = "delegate"; /** Identifies the "function" instance type. */ export const functionSymbolType = "function"; /** Identifies the "variable" instance type. */ export const variableSymbolType = "variable"; /** Possible categories of an instance (symbol) within a file: * - class * - struct * - enum * - function * - variable * - interface * - record * - delegate */ export type SymbolType = | typeof classSymbolType | typeof functionSymbolType | typeof variableSymbolType | typeof structSymbolType | typeof enumSymbolType | typeof unionSymbolType | typeof typedefSymbolType | typeof interfaceSymbolType | typeof recordSymbolType | typeof delegateSymbolType; export const metricLinesCount = "linesCount"; export const metricCodeLineCount = "codeLineCount"; export const metricCharacterCount = "characterCount"; export const metricCodeCharacterCount = "codeCharacterCount"; export const metricDependencyCount = "dependencyCount"; export const metricDependentCount = "dependentCount"; export const metricCyclomaticComplexity = "cyclomaticComplexity"; export type Metric = | typeof metricLinesCount | typeof metricCodeLineCount | typeof metricCharacterCount | typeof metricCodeCharacterCount | typeof metricDependencyCount | typeof metricDependentCount | typeof metricCyclomaticComplexity; /** Represents a single dependency. For example, if File A depends on File B, `DependencyInfo` captures how A uses B's symbols. */ export interface DependencyInfo { /** A unique identifier for the dependency, often matching the file path if internal, * or a package/module name if external. */ id: string; /** Whether this dependency is an external library/package (true) vs. an internal file (false). */ isExternal: boolean; /** A map of symbol names used from this dependency (key → value). * Example: * { * "foo": "foo", * "bar": "bar" * } * For Python-only (current scope), both key and value can be the same string, * indicating the file references a symbol by that name. */ symbols: Record; } /** Represents a single "dependent": something that depends on the file or symbol in question. * For instance, if File X depends on File Y, in Y's "dependents" you'd have an * entry for X describing the symbols X uses from Y. */ export interface DependentInfo { /** A unique identifier for the dependent, typically the file path or module name. */ id: string; /** A map of symbol names that this dependent references from the file or symbol. * Example: * { * "myClass": "myClass", * "someFunc": "someFunc" * } */ symbols: Record; } /** Represents a single named symbol (class, function, or variable) declared in a file. * Symbols can have their own dependencies and also be depended upon by others. */ export interface SymbolDependencyManifest { /** Unique name for this symbol (e.g., the class name or function name). */ id: string; /** The type of this symbol: "class", "function", or "variable". */ type: SymbolType; /** The start position of the symbol. */ positions: { start: { index: number; row: number; column: number; }; end: { index: number; row: number; column: number; }; }[]; /** Metrics for the symbol. */ metrics: { /** The number of lines in the symbol. */ [metricLinesCount]: number; /** The number of lines in the symbol. */ [metricCodeLineCount]: number; /** The number of characters in the symbol. */ [metricCharacterCount]: number; /** The number of characters in the symbol. */ [metricCodeCharacterCount]: number; /** The number of dependencies on the symbol. */ [metricDependencyCount]: number; /** The number of dependents on the symbol. */ [metricDependentCount]: number; /** The cyclomatic complexity of the symbol. */ [metricCyclomaticComplexity]: number; }; /** A short description of the symbol. */ description: string; /** Other modules/files on which this symbol depends. * Keyed by the dependency's unique ID (often a file path). */ dependencies: Record; /** A reverse map of modules/files (or symbols) that depend on this symbol. */ dependents: Record; } /** Represents a single file in the project, including: * - file-level dependencies * - file-level dependents * - symbols declared in the file */ export interface FileDependencyManifest { /** A unique identifier for the file, often the file path. */ id: string; /** The path or name of the file (e.g. "src/app/main.py"). */ filePath: string; /** The programming language of the file. It may be extended in the future to handle other languages. */ language: string; /** The metrics for the file. */ metrics: { /** The number of lines in the symbol. */ [metricLinesCount]: number; /** The number of lines in the symbol. */ [metricCodeLineCount]: number; /** The number of characters in the symbol. */ [metricCharacterCount]: number; /** The number of characters in the symbol. */ [metricCodeCharacterCount]: number; /** The number of dependencies on the symbol. */ [metricDependencyCount]: number; /** The number of dependents on the symbol. */ [metricDependentCount]: number; /** The cyclomatic complexity of the symbol. */ [metricCyclomaticComplexity]: number; }; /** Dependencies at the file level. If this file imports other files/packages, * each one appears here (with the associated symbols used). */ dependencies: Record; /** Dependents at the file level. If this file is used by other files/packages, * each one appears here (with the associated symbols used). */ dependents: Record; /** Symbols (classes, functions, variables) declared in this file, keyed by symbol name (ID). * Each symbol may also have dependencies and dependents of its own. */ symbols: Record; } /** A global structure mapping each file's unique ID (often its file path) * to its `FileManifest`. This collectively represents the project's * dependency graph or manifest. */ export type DependencyManifest = Record; ================================================ FILE: src/scripts/generate_python_stdlib_list/output.json ================================================ { "2.7": [ "AL", "BaseHTTPServer", "Bastion", "CGIHTTPServer", "Canvas", "Carbon.AE", "Carbon.AH", "Carbon.App", "Carbon.Appearance", "Carbon.CF", "Carbon.CG", "Carbon.CarbonEvents", "Carbon.CarbonEvt", "Carbon.Cm", "Carbon.Components", "Carbon.ControlAccessor", "Carbon.Controls", "Carbon.CoreFounation", "Carbon.CoreGraphics", "Carbon.Ctl", "Carbon.Dialogs", "Carbon.Dlg", "Carbon.Drag", "Carbon.Dragconst", "Carbon.Events", "Carbon.Evt", "Carbon.File", "Carbon.Files", "Carbon.Fm", "Carbon.Folder", "Carbon.Folders", "Carbon.Fonts", "Carbon.Help", "Carbon.IBCarbon", "Carbon.IBCarbonRuntime", "Carbon.Icns", "Carbon.Icons", "Carbon.Launch", "Carbon.LaunchServices", "Carbon.List", "Carbon.Lists", "Carbon.MacHelp", "Carbon.MediaDescr", "Carbon.Menu", "Carbon.Menus", "Carbon.Mlte", "Carbon.OSA", "Carbon.OSAconst", "Carbon.QDOffscreen", "Carbon.Qd", "Carbon.Qdoffs", "Carbon.Qt", "Carbon.QuickDraw", "Carbon.QuickTime", "Carbon.Res", "Carbon.Resources", "Carbon.Scrap", "Carbon.Snd", "Carbon.Sound", "Carbon.TE", "Carbon.TextEdit", "Carbon.Win", "Carbon.Windows", "ColorPicker", "ConfigParser", "Cookie", "DEVICE", "Dialog", "DocXMLRPCServer", "EasyDialogs", "FL", "FileDialog", "FixTk", "FrameWork", "GL", "HTMLParser", "MacOS", "MimeWriter", "MiniAEFrame", "Nav", "PixMapWrapper", "Queue", "SUNAUDIODEV", "ScrolledText", "SimpleDialog", "SimpleHTTPServer", "SimpleXMLRPCServer", "SocketServer", "StringIO", "Tix", "Tkconstants", "Tkdnd", "Tkinter", "UserDict", "UserList", "UserString", "W", "_LWPCookieJar", "_MozillaCookieJar", "__builtin__", "__future__", "__main__", "_abcoll", "_ast", "_bisect", "_bsddb", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_csv", "_ctypes", "_ctypes_test", "_curses", "_curses_panel", "_elementtree", "_functools", "_hashlib", "_heapq", "_hotshot", "_io", "_json", "_locale", "_lsprof", "_md5", "_multibytecodec", "_multiprocessing", "_osx_support", "_pyio", "_random", "_sha", "_sha256", "_sha512", "_socket", "_sqlite3", "_sre", "_ssl", "_strptime", "_struct", "_symtable", "_sysconfigdata", "_testcapi", "_threading_local", "_tkinter", "_warnings", "_weakref", "_weakrefset", "_winreg", "abc", "aepack", "aetools", "aetypes", "aifc", "al", "antigravity", "anydbm", "applesingle", "argparse", "array", "ast", "asynchat", "asyncore", "atexit", "audiodev", "audioop", "autoGIL", "base64", "bdb", "binascii", "binhex", "bisect", "bsddb", "bsddb.db", "bsddb.dbobj", "bsddb.dbrecio", "bsddb.dbshelve", "bsddb.dbtables", "bsddb.dbutils", "bsddb.test", "bsddb.test.test_all", "bsddb.test.test_associate", "bsddb.test.test_basics", "bsddb.test.test_compare", "bsddb.test.test_compat", "bsddb.test.test_cursor_pget_bug", "bsddb.test.test_db", "bsddb.test.test_dbenv", "bsddb.test.test_dbobj", "bsddb.test.test_dbshelve", "bsddb.test.test_dbtables", "bsddb.test.test_distributed_transactions", "bsddb.test.test_early_close", "bsddb.test.test_fileid", "bsddb.test.test_get_none", "bsddb.test.test_join", "bsddb.test.test_lock", "bsddb.test.test_misc", "bsddb.test.test_pickle", "bsddb.test.test_queue", "bsddb.test.test_recno", "bsddb.test.test_replication", "bsddb.test.test_sequence", "bsddb.test.test_thread", "buildtools", "bz2", "cPickle", "cProfile", "cStringIO", "calendar", "cd", "cfmfile", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "colorsys", "commands", "compileall", "compiler", "compiler.ast", "compiler.consts", "compiler.future", "compiler.misc", "compiler.pyassem", "compiler.pycodegen", "compiler.symbols", "compiler.syntax", "compiler.transformer", "compiler.visitor", "contextlib", "cookielib", "copy", "copy_reg", "crypt", "csv", "ctypes", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.runtests", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "curses.wrapper", "datetime", "dbhash", "dbm", "decimal", "difflib", "dircache", "dis", "distutils", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.emxccompiler", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.setuptools_build_ext", "distutils.tests.setuptools_extension", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_ccompiler", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_msvc9compiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "dl", "doctest", "dumbdbm", "dummy_thread", "dummy_threading", "email", "email._parseaddr", "email.base64mime", "email.charset", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.quoprimime", "email.test", "email.test.test_email", "email.test.test_email_codecs", "email.test.test_email_codecs_renamed", "email.test.test_email_renamed", "email.test.test_email_torture", "email.utils", "encodings", "encodings.__builtin__", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.codecs", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.encodings", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_u", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_centeuro", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.string_escape", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.unicode_internal", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "errno", "exceptions", "fcntl", "filecmp", "fileinput", "findertools", "fl", "flp", "fm", "fnmatch", "formatter", "fpectl", "fpformat", "fractions", "ftplib", "functools", "future_builtins", "gc", "gdbm", "genericpath", "gensuitemodule", "getopt", "getpass", "gettext", "gl", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "hotshot", "hotshot.log", "hotshot.stats", "hotshot.stones", "htmlentitydefs", "htmllib", "httplib", "ic", "icopen", "idlelib", "idlelib.AutoComplete", "idlelib.AutoCompleteWindow", "idlelib.AutoExpand", "idlelib.Bindings", "idlelib.CallTipWindow", "idlelib.CallTips", "idlelib.ClassBrowser", "idlelib.CodeContext", "idlelib.ColorDelegator", "idlelib.Debugger", "idlelib.Delegator", "idlelib.EditorWindow", "idlelib.FileList", "idlelib.FormatParagraph", "idlelib.GrepDialog", "idlelib.HyperParser", "idlelib.IOBinding", "idlelib.IdleHistory", "idlelib.MultiCall", "idlelib.MultiStatusBar", "idlelib.ObjectBrowser", "idlelib.OutputWindow", "idlelib.ParenMatch", "idlelib.PathBrowser", "idlelib.Percolator", "idlelib.PyParse", "idlelib.PyShell", "idlelib.RemoteDebugger", "idlelib.RemoteObjectBrowser", "idlelib.ReplaceDialog", "idlelib.RstripExtension", "idlelib.ScriptBinding", "idlelib.ScrolledList", "idlelib.SearchDialog", "idlelib.SearchDialogBase", "idlelib.SearchEngine", "idlelib.StackViewer", "idlelib.ToolTip", "idlelib.TreeWidget", "idlelib.UndoDelegator", "idlelib.WidgetRedirector", "idlelib.WindowList", "idlelib.ZoomHeight", "idlelib.aboutDialog", "idlelib.configDialog", "idlelib.configHandler", "idlelib.configHelpSourceEdit", "idlelib.configSectionNameDialog", "idlelib.dynOptionMenuWidget", "idlelib.help", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_calltips", "idlelib.idle_test.test_config_name", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_formatparagraph", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_helpabout", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_idlehistory", "idlelib.idle_test.test_io", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_rstrip", "idlelib.idle_test.test_searchdialogbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_widgetredir", "idlelib.idlever", "idlelib.keybindingDialog", "idlelib.macosxSupport", "idlelib.rpc", "idlelib.run", "idlelib.tabbedpages", "idlelib.textView", "ihooks", "imageop", "imaplib", "imgfile", "imghdr", "imp", "importlib", "imputil", "inspect", "io", "itertools", "jpeg", "json", "json._json", "json.decoder", "json.encoder", "json.json", "json.re", "json.scanner", "json.struct", "json.sys", "json.tests", "json.tests.test_check_circular", "json.tests.test_decode", "json.tests.test_default", "json.tests.test_dump", "json.tests.test_encode_basestring_ascii", "json.tests.test_fail", "json.tests.test_float", "json.tests.test_indent", "json.tests.test_pass1", "json.tests.test_pass2", "json.tests.test_pass3", "json.tests.test_recursion", "json.tests.test_scanstring", "json.tests.test_separators", "json.tests.test_speedups", "json.tests.test_tool", "json.tests.test_unicode", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "linuxaudiodev", "locale", "logging", "logging.config", "logging.handlers", "macerrors", "macostools", "macpath", "macresource", "macurl2path", "mailbox", "mailcap", "markupbase", "marshal", "math", "md5", "mhlib", "mimetools", "mimetypes", "mimify", "mmap", "modulefinder", "msilib", "msvcrt", "multifile", "multiprocessing", "multiprocessing.connection", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forking", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.sharedctypes", "multiprocessing.synchronize", "multiprocessing.util", "mutex", "netrc", "new", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "os2emxpath", "ossaudiodev", "parser", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "popen2", "poplib", "posix", "posixfile", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "quopri", "random", "re", "readline", "repr", "resource", "rexec", "rfc822", "rlcompleter", "robotparser", "runpy", "sched", "select", "sets", "sgmllib", "sha", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.py25tests", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statvfs", "string", "stringold", "stringprep", "strop", "struct", "subprocess", "sunau", "sunaudio", "sunaudiodev", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._mock_backport", "test.audiotests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.bad_coding3", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_nocaret", "test.bisect", "test.bisect_cmd", "test.curses_tests", "test.doctest_aliases", "test.double_const", "test.fork_wait", "test.gdb_sample", "test.infinite_reload", "test.inspect_fodder", "test.inspect_fodder2", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.mp_fork_bomb", "test.multibytecodec_support", "test.outstanding_bugs", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pystone", "test.pythoninfo", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.script_helper", "test.seq_tests", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.subprocessdata.sigchild_ignore", "test.support", "test.support.script_helper", "test.symlink_support", "test.test_MimeWriter", "test.test_SimpleHTTPServer", "test.test_StringIO", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__osx_support", "test.test_abc", "test.test_abstract_numbers", "test.test_aepack", "test.test_aifc", "test.test_al", "test.test_anydbm", "test.test_applesingle", "test.test_argparse", "test.test_array", "test.test_ascii_formatd", "test.test_ast", "test.test_asynchat", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_augassign", "test.test_base64", "test.test_bastion", "test.test_bdb", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_bsddb", "test.test_bsddb185", "test.test_bsddb3", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cd", "test.test_cfgparser", "test.test_cgi", "test.test_charmapcodec", "test.test_cl", "test.test_class", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_coercion", "test.test_collections", "test.test_colorsys", "test.test_commands", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_compiler", "test.test_complex", "test.test_complex_args", "test.test_contains", "test.test_contextlib", "test.test_cookie", "test.test_cookielib", "test.test_copy", "test.test_copy_reg", "test.test_cpickle", "test.test_cprofile", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_datetime", "test.test_dbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_dict", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dircache", "test.test_dis", "test.test_distutils", "test.test_dl", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dumbdbm", "test.test_dummy_thread", "test.test_dummy_threading", "test.test_email", "test.test_email_codecs", "test.test_email_renamed", "test.test_ensurepip", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_fcntl", "test.test_file", "test.test_file2k", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_float", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fpformat", "test.test_fractions", "test.test_frozen", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future1", "test.test_future2", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_future_builtins", "test.test_gc", "test.test_gdb", "test.test_gdbm", "test.test_generators", "test.test_genericpath", "test.test_genexps", "test.test_getargs", "test.test_getargs2", "test.test_getopt", "test.test_gettext", "test.test_gl", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_hotshot", "test.test_htmllib", "test.test_htmlparser", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imageop", "test.test_imaplib", "test.test_imgfile", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_import_magic", "test.test_importhooks", "test.test_importlib", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_linuxaudiodev", "test.test_list", "test.test_locale", "test.test_logging", "test.test_long", "test.test_long_future", "test.test_longexp", "test.test_macos", "test.test_macostools", "test.test_macpath", "test.test_macurl2path", "test.test_mailbox", "test.test_marshal", "test.test_math", "test.test_md5", "test.test_memoryio", "test.test_memoryview", "test.test_mhlib", "test.test_mimetools", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multifile", "test.test_multiprocessing", "test.test_mutants", "test.test_mutex", "test.test_netrc", "test.test_new", "test.test_nis", "test.test_nntplib", "test.test_normalization", "test.test_ntpath", "test.test_old_mailbox", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_parser", "test.test_pdb", "test.test_peepholer", "test.test_pep247", "test.test_pep277", "test.test_pep352", "test.test_pickle", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgimport", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_popen2", "test.test_poplib", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pwd", "test.test_py3kwarn", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_random", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_repr", "test.test_resource", "test.test_rfc822", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_scope", "test.test_scriptpackages", "test.test_select", "test.test_set", "test.test_setcomps", "test.test_sets", "test.test_sgmllib", "test.test_sha", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtplib", "test.test_smtpnet", "test.test_socket", "test.test_socketserver", "test.test_softspace", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_str", "test.test_strftime", "test.test_string", "test.test_stringprep", "test.test_strop", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subprocess", "test.test_sunau", "test.test_sunaudiodev", "test.test_sundry", "test.test_support", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_test_support", "test.test_textwrap", "test.test_thread", "test.test_threaded_import", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tk", "test.test_tokenize", "test.test_tools", "test.test_trace", "test.test_traceback", "test.test_transformer", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_turtle", "test.test_typechecks", "test.test_types", "test.test_ucn", "test.test_unary", "test.test_undocumented_details", "test.test_unicode", "test.test_unicode_file", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_univnewlines2k", "test.test_unpack", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_uu", "test.test_uuid", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_whichdb", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmllib", "test.test_xmlrpc", "test.test_xpickle", "test.test_xrange", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.testall", "test.testcodec", "test.tf_inherit_check", "test.threaded_import_hangers", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.warning_tests", "test.win_console_handler", "test.xmltests", "textwrap", "this", "thread", "threading", "time", "timeit", "tkColorChooser", "tkCommonDialog", "tkFileDialog", "tkFont", "tkMessageBox", "tkSimpleDialog", "toaiff", "token", "tokenize", "trace", "traceback", "ttk", "tty", "turtle", "types", "unicodedata", "unittest", "unittest.__main__", "unittest.case", "unittest.loader", "unittest.main", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.util", "urllib", "urllib2", "urlparse", "user", "uu", "uuid", "videoreader", "warnings", "wave", "weakref", "webbrowser", "whichdb", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmllib", "xmlrpclib", "xxsubtype", "zipfile", "zipimport", "zlib" ], "3.2": [ "__future__", "__main__", "_dummy_thread", "_struct", "_thread", "abc", "aifc", "argparse", "array", "ast", "asynchat", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "colorsys", "compileall", "concurrent.futures", "configparser", "contextlib", "copy", "copyreg", "crypt", "csv", "ctypes", "curses", "curses.ascii", "curses.panel", "curses.textpad", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.emxccompiler", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "doctest", "dummy_threading", "email", "email.charset", "email.encoders", "email.errors", "email.generator", "email.header", "email.iterators", "email.message", "email.mime", "email.parser", "email.utils", "encodings", "encodings.idna", "encodings.mbcs", "encodings.utf_8_sig", "errno", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fpectl", "fractions", "ftplib", "functools", "gc", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http.client", "http.cookiejar", "http.cookies", "http.server", "imaplib", "imghdr", "imp", "importlib", "importlib.abc", "importlib.machinery", "importlib.util", "inspect", "io", "itertools", "json", "keyword", "lib2to3", "linecache", "locale", "logging", "logging.config", "logging.handlers", "macpath", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.dummy", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.sharedctypes", "netrc", "nis", "nntplib", "numbers", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "select", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "ssl", "stat", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.support", "textwrap", "threading", "time", "timeit", "tkinter", "tkinter.scrolledtext", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tty", "turtle", "types", "unicodedata", "unittest", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.minidom", "xml.dom.pulldom", "xml.etree.ElementTree", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.model", "xml.sax", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc.client", "xmlrpc.server", "zipfile", "zipimport", "zlib" ], "3.3": [ "__future__", "__main__", "_dummy_thread", "_struct", "_thread", "abc", "aifc", "argparse", "array", "ast", "asynchat", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent.futures", "configparser", "contextlib", "copy", "copyreg", "crypt", "csv", "ctypes", "curses", "curses.ascii", "curses.panel", "curses.textpad", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.emxccompiler", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "doctest", "dummy_threading", "email", "email.charset", "email.encoders", "email.errors", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.parser", "email.policy", "email.utils", "encodings", "encodings.idna", "encodings.mbcs", "encodings.utf_8_sig", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fpectl", "fractions", "ftplib", "functools", "gc", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http.client", "http.cookiejar", "http.cookies", "http.server", "imaplib", "imghdr", "imp", "importlib", "importlib.abc", "importlib.machinery", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "keyword", "lib2to3", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "macpath", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.dummy", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.sharedctypes", "netrc", "nis", "nntplib", "numbers", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "select", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "ssl", "stat", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.support", "textwrap", "threading", "time", "timeit", "tkinter", "tkinter.scrolledtext", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tty", "turtle", "types", "unicodedata", "unittest", "unittest.mock", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.minidom", "xml.dom.pulldom", "xml.etree.ElementTree", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.model", "xml.sax", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc.client", "xmlrpc.server", "zipfile", "zipimport", "zlib" ], "3.4": [ "__future__", "__main__", "_ast", "_bisect", "_bootlocale", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_crypt", "_csv", "_ctypes", "_ctypes_test", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_dummy_thread", "_elementtree", "_frozen_importlib", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_pickle", "_posixsubprocess", "_pyio", "_random", "_sha1", "_sha256", "_sha512", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_string", "_strptime", "_struct", "_symtable", "_sysconfigdata", "_testbuffer", "_testcapi", "_testimportmultiple", "_thread", "_threading_local", "_tkinter", "_tracemalloc", "_warnings", "_weakref", "_weakrefset", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.base_events", "asyncio.base_subprocess", "asyncio.compat", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.selector_events", "asyncio.sslproto", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.test_utils", "asyncio.transports", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.__main__", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_cygwinccompiler", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_extension", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_log", "distutils.tests.test_msvc9compiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "dummy_threading", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp65001", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_u", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_centeuro", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.unicode_internal", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fpectl", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.AutoComplete", "idlelib.AutoCompleteWindow", "idlelib.AutoExpand", "idlelib.Bindings", "idlelib.CallTipWindow", "idlelib.CallTips", "idlelib.ClassBrowser", "idlelib.CodeContext", "idlelib.ColorDelegator", "idlelib.Debugger", "idlelib.Delegator", "idlelib.EditorWindow", "idlelib.FileList", "idlelib.FormatParagraph", "idlelib.GrepDialog", "idlelib.HyperParser", "idlelib.IOBinding", "idlelib.IdleHistory", "idlelib.MultiCall", "idlelib.MultiStatusBar", "idlelib.ObjectBrowser", "idlelib.OutputWindow", "idlelib.ParenMatch", "idlelib.PathBrowser", "idlelib.Percolator", "idlelib.PyParse", "idlelib.PyShell", "idlelib.RemoteDebugger", "idlelib.RemoteObjectBrowser", "idlelib.ReplaceDialog", "idlelib.RstripExtension", "idlelib.ScriptBinding", "idlelib.ScrolledList", "idlelib.SearchDialog", "idlelib.SearchDialogBase", "idlelib.SearchEngine", "idlelib.StackViewer", "idlelib.ToolTip", "idlelib.TreeWidget", "idlelib.UndoDelegator", "idlelib.WidgetRedirector", "idlelib.WindowList", "idlelib.ZoomHeight", "idlelib.__main__", "idlelib.aboutDialog", "idlelib.configDialog", "idlelib.configHandler", "idlelib.configHelpSourceEdit", "idlelib.configSectionNameDialog", "idlelib.dynOptionMenuWidget", "idlelib.help", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_calltips", "idlelib.idle_test.test_config_name", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_formatparagraph", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_idlehistory", "idlelib.idle_test.test_io", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_rstrip", "idlelib.idle_test.test_searchdialogbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_widgetredir", "idlelib.idlever", "idlelib.keybindingDialog", "idlelib.macosxSupport", "idlelib.rpc", "idlelib.run", "idlelib.tabbedpages", "idlelib.textView", "imaplib", "imghdr", "imp", "importlib", "importlib._bootstrap", "importlib.abc", "importlib.machinery", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_callable", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "macpath", "macurl2path", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.semaphore_tracker", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._test_multiprocessing", "test.audiotests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.badsyntax_3131", "test.badsyntax_future10", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_pep3120", "test.buffer_tests", "test.bytecode_helper", "test.coding20731", "test.curses_tests", "test.datetimetester", "test.dis_module", "test.doctest_aliases", "test.double_const", "test.encoded_modules", "test.encoded_modules.module_iso_8859_1", "test.encoded_modules.module_koi8_r", "test.final_a", "test.final_b", "test.fork_wait", "test.future_test1", "test.future_test2", "test.gdb_sample", "test.inspect_fodder", "test.inspect_fodder2", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.memory_watchdog", "test.mock_socket", "test.mp_fork_bomb", "test.multibytecodec_support", "test.outstanding_bugs", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pystone", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.script_helper", "test.seq_tests", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.subprocessdata.fd_status", "test.subprocessdata.input_reader", "test.subprocessdata.qcat", "test.subprocessdata.qgrep", "test.subprocessdata.sigchild_ignore", "test.support", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__opcode", "test.test__osx_support", "test.test_abc", "test.test_abstract_numbers", "test.test_aifc", "test.test_argparse", "test.test_array", "test.test_ast", "test.test_asynchat", "test.test_asyncio", "test.test_asyncio.__main__", "test.test_asyncio.echo", "test.test_asyncio.echo2", "test.test_asyncio.echo3", "test.test_asyncio.test_base_events", "test.test_asyncio.test_events", "test.test_asyncio.test_futures", "test.test_asyncio.test_locks", "test.test_asyncio.test_proactor_events", "test.test_asyncio.test_queues", "test.test_asyncio.test_selector_events", "test.test_asyncio.test_sslproto", "test.test_asyncio.test_streams", "test.test_asyncio.test_subprocess", "test.test_asyncio.test_tasks", "test.test_asyncio.test_transports", "test.test_asyncio.test_unix_events", "test.test_asyncio.test_windows_events", "test.test_asyncio.test_windows_utils", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_augassign", "test.test_base64", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cgi", "test.test_cgitb", "test.test_charmapcodec", "test.test_class", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_code_module", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_collections", "test.test_colorsys", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_complex", "test.test_concurrent_futures", "test.test_configparser", "test.test_contains", "test.test_contextlib", "test.test_copy", "test.test_copyreg", "test.test_cprofile", "test.test_crashers", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_datetime", "test.test_dbm", "test.test_dbm_dumb", "test.test_dbm_gnu", "test.test_dbm_ndbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_devpoll", "test.test_dict", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dis", "test.test_distutils", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dummy_thread", "test.test_dummy_threading", "test.test_dynamic", "test.test_dynamicclassattribute", "test.test_email", "test.test_email.__main__", "test.test_email.test__encoded_words", "test.test_email.test__header_value_parser", "test.test_email.test_asian_codecs", "test.test_email.test_contentmanager", "test.test_email.test_defect_handling", "test.test_email.test_email", "test.test_email.test_generator", "test.test_email.test_headerregistry", "test.test_email.test_inversion", "test.test_email.test_message", "test.test_email.test_parser", "test.test_email.test_pickleable", "test.test_email.test_policy", "test.test_email.test_utils", "test.test_email.torture_test", "test.test_ensurepip", "test.test_enum", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_faulthandler", "test.test_fcntl", "test.test_file", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_finalization", "test.test_float", "test.test_flufl", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fractions", "test.test_frame", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_gc", "test.test_gdb", "test.test_generators", "test.test_genericpath", "test.test_genexps", "test.test_getargs2", "test.test_getopt", "test.test_getpass", "test.test_gettext", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_html", "test.test_htmlparser", "test.test_http_cookiejar", "test.test_http_cookies", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imaplib", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_importlib", "test.test_importlib.__main__", "test.test_importlib.abc", "test.test_importlib.builtin", "test.test_importlib.builtin.__main__", "test.test_importlib.builtin.test_finder", "test.test_importlib.builtin.test_loader", "test.test_importlib.builtin.util", "test.test_importlib.extension", "test.test_importlib.extension.__main__", "test.test_importlib.extension.test_case_sensitivity", "test.test_importlib.extension.test_finder", "test.test_importlib.extension.test_loader", "test.test_importlib.extension.test_path_hook", "test.test_importlib.extension.util", "test.test_importlib.frozen", "test.test_importlib.frozen.__main__", "test.test_importlib.frozen.test_finder", "test.test_importlib.frozen.test_loader", "test.test_importlib.import_", "test.test_importlib.import_.__main__", "test.test_importlib.import_.test___loader__", "test.test_importlib.import_.test___package__", "test.test_importlib.import_.test_api", "test.test_importlib.import_.test_caching", "test.test_importlib.import_.test_fromlist", "test.test_importlib.import_.test_meta_path", "test.test_importlib.import_.test_packages", "test.test_importlib.import_.test_path", "test.test_importlib.import_.test_relative_imports", "test.test_importlib.import_.util", "test.test_importlib.namespace_pkgs.both_portions.foo.one", "test.test_importlib.namespace_pkgs.both_portions.foo.two", "test.test_importlib.namespace_pkgs.module_and_namespace_package.a_test", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo.one", "test.test_importlib.namespace_pkgs.portion1.foo.one", "test.test_importlib.namespace_pkgs.portion2.foo.two", "test.test_importlib.namespace_pkgs.project1.parent.child.one", "test.test_importlib.namespace_pkgs.project2.parent.child.two", "test.test_importlib.namespace_pkgs.project3.parent.child.three", "test.test_importlib.regrtest", "test.test_importlib.source", "test.test_importlib.source.__main__", "test.test_importlib.source.test_case_sensitivity", "test.test_importlib.source.test_file_loader", "test.test_importlib.source.test_finder", "test.test_importlib.source.test_path_hook", "test.test_importlib.source.test_source_encoding", "test.test_importlib.source.util", "test.test_importlib.test_abc", "test.test_importlib.test_api", "test.test_importlib.test_locks", "test.test_importlib.test_namespace_pkgs", "test.test_importlib.test_spec", "test.test_importlib.test_util", "test.test_importlib.test_windows", "test.test_importlib.util", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_ipaddress", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_json.__main__", "test.test_json.test_decode", "test.test_json.test_default", "test.test_json.test_dump", "test.test_json.test_encode_basestring_ascii", "test.test_json.test_enum", "test.test_json.test_fail", "test.test_json.test_float", "test.test_json.test_indent", "test.test_json.test_pass1", "test.test_json.test_pass2", "test.test_json.test_pass3", "test.test_json.test_recursion", "test.test_json.test_scanstring", "test.test_json.test_separators", "test.test_json.test_speedups", "test.test_json.test_tool", "test.test_json.test_unicode", "test.test_keyword", "test.test_keywordonlyarg", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_list", "test.test_listcomps", "test.test_locale", "test.test_logging", "test.test_long", "test.test_longexp", "test.test_lzma", "test.test_macpath", "test.test_macurl2path", "test.test_mailbox", "test.test_mailcap", "test.test_marshal", "test.test_math", "test.test_memoryio", "test.test_memoryview", "test.test_metaclass", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multiprocessing_fork", "test.test_multiprocessing_forkserver", "test.test_multiprocessing_main_handling", "test.test_multiprocessing_spawn", "test.test_netrc", "test.test_nis", "test.test_nntplib", "test.test_normalization", "test.test_ntpath", "test.test_numeric_tower", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_osx_env", "test.test_parser", "test.test_pathlib", "test.test_pdb", "test.test_peepholer", "test.test_pep247", "test.test_pep277", "test.test_pep292", "test.test_pep3120", "test.test_pep3131", "test.test_pep3151", "test.test_pep352", "test.test_pep380", "test.test_pickle", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgimport", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_poplib", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pulldom", "test.test_pwd", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_raise", "test.test_random", "test.test_range", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_reprlib", "test.test_resource", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_sched", "test.test_scope", "test.test_script_helper", "test.test_select", "test.test_selectors", "test.test_set", "test.test_setcomps", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtpd", "test.test_smtplib", "test.test_smtpnet", "test.test_sndhdr", "test.test_socket", "test.test_socketserver", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_statistics", "test.test_strftime", "test.test_string", "test.test_stringprep", "test.test_strlit", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subprocess", "test.test_sunau", "test.test_sundry", "test.test_super", "test.test_support", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_syslog", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_textwrap", "test.test_thread", "test.test_threaded_import", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tk", "test.test_tokenize", "test.test_trace", "test.test_traceback", "test.test_tracemalloc", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_typechecks", "test.test_types", "test.test_ucn", "test.test_unary", "test.test_unicode", "test.test_unicode_file", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_unpack", "test.test_unpack_ex", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllib_response", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_uu", "test.test_uuid", "test.test_venv", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_webbrowser", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_dom_minicompat", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmlrpc", "test.test_xmlrpc_net", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.testcodec", "test.tf_inherit_check", "test.threaded_import_hangers", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.warning_tests", "test.win_console_handler", "test.xmltests", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter._fix", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.runtktests", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_functions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.round_dance", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.wikipedia", "turtledemo.yinyang", "types", "unicodedata", "unittest", "unittest.__main__", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.model", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxlimited", "xxsubtype", "zipfile", "zipimport", "zlib" ], "3.5": [ "__future__", "__main__", "_ast", "_bisect", "_bootlocale", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_crypt", "_csv", "_ctypes", "_ctypes_test", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_dummy_thread", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_pickle", "_posixsubprocess", "_pydecimal", "_pyio", "_random", "_sha1", "_sha256", "_sha512", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_string", "_strptime", "_struct", "_symtable", "_sysconfigdata", "_testbuffer", "_testcapi", "_testimportmultiple", "_testmultiphase", "_thread", "_threading_local", "_tkinter", "_tracemalloc", "_warnings", "_weakref", "_weakrefset", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.base_events", "asyncio.base_subprocess", "asyncio.compat", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.selector_events", "asyncio.sslproto", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.test_utils", "asyncio.transports", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.__main__", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_cygwinccompiler", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_extension", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_log", "distutils.tests.test_msvc9compiler", "distutils.tests.test_msvccompiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "dummy_threading", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp65001", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_centeuro", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.unicode_internal", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fpectl", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.AutoComplete", "idlelib.AutoCompleteWindow", "idlelib.AutoExpand", "idlelib.Bindings", "idlelib.CallTipWindow", "idlelib.CallTips", "idlelib.ClassBrowser", "idlelib.CodeContext", "idlelib.ColorDelegator", "idlelib.Debugger", "idlelib.Delegator", "idlelib.EditorWindow", "idlelib.FileList", "idlelib.FormatParagraph", "idlelib.GrepDialog", "idlelib.HyperParser", "idlelib.IOBinding", "idlelib.IdleHistory", "idlelib.MultiCall", "idlelib.MultiStatusBar", "idlelib.ObjectBrowser", "idlelib.OutputWindow", "idlelib.ParenMatch", "idlelib.PathBrowser", "idlelib.Percolator", "idlelib.PyParse", "idlelib.PyShell", "idlelib.RemoteDebugger", "idlelib.RemoteObjectBrowser", "idlelib.ReplaceDialog", "idlelib.RstripExtension", "idlelib.ScriptBinding", "idlelib.ScrolledList", "idlelib.SearchDialog", "idlelib.SearchDialogBase", "idlelib.SearchEngine", "idlelib.StackViewer", "idlelib.ToolTip", "idlelib.TreeWidget", "idlelib.UndoDelegator", "idlelib.WidgetRedirector", "idlelib.WindowList", "idlelib.ZoomHeight", "idlelib.__main__", "idlelib.aboutDialog", "idlelib.configDialog", "idlelib.configHandler", "idlelib.configHelpSourceEdit", "idlelib.configSectionNameDialog", "idlelib.dynOptionMenuWidget", "idlelib.help", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_calltips", "idlelib.idle_test.test_config_help", "idlelib.idle_test.test_config_name", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_formatparagraph", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_idlehistory", "idlelib.idle_test.test_io", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_replacedialog", "idlelib.idle_test.test_rstrip", "idlelib.idle_test.test_searchdialog", "idlelib.idle_test.test_searchdialogbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_undodelegator", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_widgetredir", "idlelib.idlever", "idlelib.keybindingDialog", "idlelib.macosxSupport", "idlelib.rpc", "idlelib.run", "idlelib.tabbedpages", "idlelib.textView", "imaplib", "imghdr", "imp", "importlib", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "macpath", "macurl2path", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.semaphore_tracker", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._test_multiprocessing", "test.audiotests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.badsyntax_3131", "test.badsyntax_async1", "test.badsyntax_async2", "test.badsyntax_async3", "test.badsyntax_async4", "test.badsyntax_async5", "test.badsyntax_async6", "test.badsyntax_async7", "test.badsyntax_async8", "test.badsyntax_future10", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_pep3120", "test.bisect", "test.bytecode_helper", "test.coding20731", "test.curses_tests", "test.datetimetester", "test.dis_module", "test.doctest_aliases", "test.double_const", "test.eintrdata.eintr_tester", "test.encoded_modules", "test.encoded_modules.module_iso_8859_1", "test.encoded_modules.module_koi8_r", "test.final_a", "test.final_b", "test.fork_wait", "test.future_test1", "test.future_test2", "test.gdb_sample", "test.imp_dummy", "test.inspect_fodder", "test.inspect_fodder2", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.memory_watchdog", "test.mock_socket", "test.mod_generics_cache", "test.mp_fork_bomb", "test.mp_preload", "test.multibytecodec_support", "test.outstanding_bugs", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pystone", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.seq_tests", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.subprocessdata.fd_status", "test.subprocessdata.input_reader", "test.subprocessdata.qcat", "test.subprocessdata.qgrep", "test.subprocessdata.sigchild_ignore", "test.support", "test.support.script_helper", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__opcode", "test.test__osx_support", "test.test_abc", "test.test_abstract_numbers", "test.test_aifc", "test.test_argparse", "test.test_array", "test.test_asdl_parser", "test.test_ast", "test.test_asynchat", "test.test_asyncio", "test.test_asyncio.__main__", "test.test_asyncio.echo", "test.test_asyncio.echo2", "test.test_asyncio.echo3", "test.test_asyncio.test_base_events", "test.test_asyncio.test_events", "test.test_asyncio.test_futures", "test.test_asyncio.test_locks", "test.test_asyncio.test_pep492", "test.test_asyncio.test_proactor_events", "test.test_asyncio.test_queues", "test.test_asyncio.test_selector_events", "test.test_asyncio.test_sslproto", "test.test_asyncio.test_streams", "test.test_asyncio.test_subprocess", "test.test_asyncio.test_tasks", "test.test_asyncio.test_transports", "test.test_asyncio.test_unix_events", "test.test_asyncio.test_windows_events", "test.test_asyncio.test_windows_utils", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_augassign", "test.test_base64", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cgi", "test.test_cgitb", "test.test_charmapcodec", "test.test_class", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_code_module", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_collections", "test.test_colorsys", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_complex", "test.test_concurrent_futures", "test.test_configparser", "test.test_contains", "test.test_contextlib", "test.test_copy", "test.test_copyreg", "test.test_coroutines", "test.test_cprofile", "test.test_crashers", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_datetime", "test.test_dbm", "test.test_dbm_dumb", "test.test_dbm_gnu", "test.test_dbm_ndbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_devpoll", "test.test_dict", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dis", "test.test_distutils", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dummy_thread", "test.test_dummy_threading", "test.test_dynamic", "test.test_dynamicclassattribute", "test.test_eintr", "test.test_email", "test.test_email.__main__", "test.test_email.test__encoded_words", "test.test_email.test__header_value_parser", "test.test_email.test_asian_codecs", "test.test_email.test_contentmanager", "test.test_email.test_defect_handling", "test.test_email.test_email", "test.test_email.test_generator", "test.test_email.test_headerregistry", "test.test_email.test_inversion", "test.test_email.test_message", "test.test_email.test_parser", "test.test_email.test_pickleable", "test.test_email.test_policy", "test.test_email.test_utils", "test.test_email.torture_test", "test.test_ensurepip", "test.test_enum", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_faulthandler", "test.test_fcntl", "test.test_file", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_finalization", "test.test_float", "test.test_flufl", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fractions", "test.test_frame", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_gc", "test.test_gdb", "test.test_generators", "test.test_genericpath", "test.test_genexps", "test.test_getargs2", "test.test_getopt", "test.test_getpass", "test.test_gettext", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_html", "test.test_htmlparser", "test.test_http_cookiejar", "test.test_http_cookies", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imaplib", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_import.__main__", "test.test_import.data.circular_imports.basic", "test.test_import.data.circular_imports.basic2", "test.test_import.data.circular_imports.indirect", "test.test_import.data.circular_imports.rebinding", "test.test_import.data.circular_imports.rebinding2", "test.test_import.data.circular_imports.subpackage", "test.test_import.data.circular_imports.subpkg.subpackage2", "test.test_import.data.circular_imports.subpkg.util", "test.test_import.data.circular_imports.util", "test.test_import.data.package2.submodule1", "test.test_import.data.package2.submodule2", "test.test_importlib", "test.test_importlib.__main__", "test.test_importlib.abc", "test.test_importlib.builtin", "test.test_importlib.builtin.__main__", "test.test_importlib.builtin.test_finder", "test.test_importlib.builtin.test_loader", "test.test_importlib.extension", "test.test_importlib.extension.__main__", "test.test_importlib.extension.test_case_sensitivity", "test.test_importlib.extension.test_finder", "test.test_importlib.extension.test_loader", "test.test_importlib.extension.test_path_hook", "test.test_importlib.frozen", "test.test_importlib.frozen.__main__", "test.test_importlib.frozen.test_finder", "test.test_importlib.frozen.test_loader", "test.test_importlib.import_", "test.test_importlib.import_.__main__", "test.test_importlib.import_.test___loader__", "test.test_importlib.import_.test___package__", "test.test_importlib.import_.test_api", "test.test_importlib.import_.test_caching", "test.test_importlib.import_.test_fromlist", "test.test_importlib.import_.test_meta_path", "test.test_importlib.import_.test_packages", "test.test_importlib.import_.test_path", "test.test_importlib.import_.test_relative_imports", "test.test_importlib.namespace_pkgs.both_portions.foo.one", "test.test_importlib.namespace_pkgs.both_portions.foo.two", "test.test_importlib.namespace_pkgs.module_and_namespace_package.a_test", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo.one", "test.test_importlib.namespace_pkgs.portion1.foo.one", "test.test_importlib.namespace_pkgs.portion2.foo.two", "test.test_importlib.namespace_pkgs.project1.parent.child.one", "test.test_importlib.namespace_pkgs.project2.parent.child.two", "test.test_importlib.namespace_pkgs.project3.parent.child.three", "test.test_importlib.regrtest", "test.test_importlib.source", "test.test_importlib.source.__main__", "test.test_importlib.source.test_case_sensitivity", "test.test_importlib.source.test_file_loader", "test.test_importlib.source.test_finder", "test.test_importlib.source.test_path_hook", "test.test_importlib.source.test_source_encoding", "test.test_importlib.test_abc", "test.test_importlib.test_api", "test.test_importlib.test_lazy", "test.test_importlib.test_locks", "test.test_importlib.test_namespace_pkgs", "test.test_importlib.test_spec", "test.test_importlib.test_util", "test.test_importlib.test_windows", "test.test_importlib.util", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_ipaddress", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_json.__main__", "test.test_json.test_decode", "test.test_json.test_default", "test.test_json.test_dump", "test.test_json.test_encode_basestring_ascii", "test.test_json.test_enum", "test.test_json.test_fail", "test.test_json.test_float", "test.test_json.test_indent", "test.test_json.test_pass1", "test.test_json.test_pass2", "test.test_json.test_pass3", "test.test_json.test_recursion", "test.test_json.test_scanstring", "test.test_json.test_separators", "test.test_json.test_speedups", "test.test_json.test_tool", "test.test_json.test_unicode", "test.test_keyword", "test.test_keywordonlyarg", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_list", "test.test_listcomps", "test.test_locale", "test.test_logging", "test.test_long", "test.test_longexp", "test.test_lzma", "test.test_macpath", "test.test_macurl2path", "test.test_mailbox", "test.test_mailcap", "test.test_marshal", "test.test_math", "test.test_memoryio", "test.test_memoryview", "test.test_metaclass", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multiprocessing_fork", "test.test_multiprocessing_forkserver", "test.test_multiprocessing_main_handling", "test.test_multiprocessing_spawn", "test.test_netrc", "test.test_nis", "test.test_nntplib", "test.test_normalization", "test.test_ntpath", "test.test_numeric_tower", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_osx_env", "test.test_parser", "test.test_pathlib", "test.test_pdb", "test.test_peepholer", "test.test_pep247", "test.test_pep277", "test.test_pep3120", "test.test_pep3131", "test.test_pep3151", "test.test_pep352", "test.test_pep380", "test.test_pep479", "test.test_pickle", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgimport", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_poplib", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pulldom", "test.test_pwd", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_raise", "test.test_random", "test.test_range", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_reprlib", "test.test_resource", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_sched", "test.test_scope", "test.test_script_helper", "test.test_select", "test.test_selectors", "test.test_set", "test.test_setcomps", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtpd", "test.test_smtplib", "test.test_smtpnet", "test.test_sndhdr", "test.test_socket", "test.test_socketserver", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_statistics", "test.test_strftime", "test.test_string", "test.test_stringprep", "test.test_strlit", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subprocess", "test.test_sunau", "test.test_sundry", "test.test_super", "test.test_support", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_syslog", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_textwrap", "test.test_thread", "test.test_threaded_import", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tix", "test.test_tk", "test.test_tokenize", "test.test_tools", "test.test_tools.__main__", "test.test_tools.test_fixcid", "test.test_tools.test_gprof2html", "test.test_tools.test_i18n", "test.test_tools.test_md5sum", "test.test_tools.test_pdeps", "test.test_tools.test_pindent", "test.test_tools.test_reindent", "test.test_tools.test_sundry", "test.test_tools.test_unparse", "test.test_trace", "test.test_traceback", "test.test_tracemalloc", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_turtle", "test.test_typechecks", "test.test_types", "test.test_typing", "test.test_ucn", "test.test_unary", "test.test_unicode", "test.test_unicode_file", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_unpack", "test.test_unpack_ex", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllib_response", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_uu", "test.test_uuid", "test.test_venv", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_warnings.__main__", "test.test_warnings.data.import_warning", "test.test_warnings.data.stacklevel", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_webbrowser", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_dom_minicompat", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmlrpc", "test.test_xmlrpc_net", "test.test_zipapp", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.testcodec", "test.tf_inherit_check", "test.threaded_import_hangers", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.win_console_handler", "test.xmltests", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.runtktests", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_functions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.wikipedia", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.model", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxlimited", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib" ], "3.6": [ "__future__", "__main__", "_ast", "_asyncio", "_bisect", "_blake2", "_bootlocale", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_crypt", "_csv", "_ctypes", "_ctypes_test", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_dummy_thread", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_pickle", "_posixsubprocess", "_pydecimal", "_pyio", "_random", "_sha1", "_sha256", "_sha3", "_sha512", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_string", "_strptime", "_struct", "_symtable", "_testbuffer", "_testcapi", "_testimportmultiple", "_testmultiphase", "_thread", "_threading_local", "_tkinter", "_tracemalloc", "_warnings", "_weakref", "_weakrefset", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.compat", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.selector_events", "asyncio.sslproto", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.test_utils", "asyncio.transports", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_cygwinccompiler", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_extension", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_log", "distutils.tests.test_msvc9compiler", "distutils.tests.test_msvccompiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "dummy_threading", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp65001", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_centeuro", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.unicode_internal", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fpectl", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib._pyclbr", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_paragraph", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_rstrip", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.paragraph", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.rstrip", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "imp", "importlib", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "macpath", "macurl2path", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.semaphore_tracker", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._test_multiprocessing", "test.ann_module", "test.ann_module2", "test.ann_module3", "test.audiotests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.badsyntax_3131", "test.badsyntax_future10", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_pep3120", "test.bisect", "test.bytecode_helper", "test.coding20731", "test.curses_tests", "test.datetimetester", "test.dis_module", "test.doctest_aliases", "test.double_const", "test.dtracedata.call_stack", "test.dtracedata.gc", "test.dtracedata.instance", "test.dtracedata.line", "test.eintrdata.eintr_tester", "test.encoded_modules", "test.encoded_modules.module_iso_8859_1", "test.encoded_modules.module_koi8_r", "test.final_a", "test.final_b", "test.fork_wait", "test.future_test1", "test.future_test2", "test.gdb_sample", "test.imp_dummy", "test.inspect_fodder", "test.inspect_fodder2", "test.libregrtest", "test.libregrtest.cmdline", "test.libregrtest.main", "test.libregrtest.refleak", "test.libregrtest.runtest", "test.libregrtest.runtest_mp", "test.libregrtest.save_env", "test.libregrtest.setup", "test.libregrtest.utils", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.memory_watchdog", "test.mock_socket", "test.mod_generics_cache", "test.mp_fork_bomb", "test.mp_preload", "test.multibytecodec_support", "test.outstanding_bugs", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pystone", "test.pythoninfo", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.seq_tests", "test.signalinterproctester", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.subprocessdata.fd_status", "test.subprocessdata.input_reader", "test.subprocessdata.qcat", "test.subprocessdata.qgrep", "test.subprocessdata.sigchild_ignore", "test.support", "test.support.script_helper", "test.support.testresult", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__opcode", "test.test__osx_support", "test.test_abc", "test.test_abstract_numbers", "test.test_aifc", "test.test_argparse", "test.test_array", "test.test_asdl_parser", "test.test_ast", "test.test_asyncgen", "test.test_asynchat", "test.test_asyncio", "test.test_asyncio.__main__", "test.test_asyncio.echo", "test.test_asyncio.echo2", "test.test_asyncio.echo3", "test.test_asyncio.test_base_events", "test.test_asyncio.test_events", "test.test_asyncio.test_futures", "test.test_asyncio.test_locks", "test.test_asyncio.test_pep492", "test.test_asyncio.test_proactor_events", "test.test_asyncio.test_queues", "test.test_asyncio.test_selector_events", "test.test_asyncio.test_sslproto", "test.test_asyncio.test_streams", "test.test_asyncio.test_subprocess", "test.test_asyncio.test_tasks", "test.test_asyncio.test_transports", "test.test_asyncio.test_unix_events", "test.test_asyncio.test_windows_events", "test.test_asyncio.test_windows_utils", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_augassign", "test.test_base64", "test.test_baseexception", "test.test_bdb", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cgi", "test.test_cgitb", "test.test_charmapcodec", "test.test_class", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_code_module", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_collections", "test.test_colorsys", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_complex", "test.test_concurrent_futures", "test.test_configparser", "test.test_contains", "test.test_contextlib", "test.test_copy", "test.test_copyreg", "test.test_coroutines", "test.test_cprofile", "test.test_crashers", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_datetime", "test.test_dbm", "test.test_dbm_dumb", "test.test_dbm_gnu", "test.test_dbm_ndbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_devpoll", "test.test_dict", "test.test_dict_version", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dis", "test.test_distutils", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dtrace", "test.test_dummy_thread", "test.test_dummy_threading", "test.test_dynamic", "test.test_dynamicclassattribute", "test.test_eintr", "test.test_email", "test.test_email.__main__", "test.test_email.test__encoded_words", "test.test_email.test__header_value_parser", "test.test_email.test_asian_codecs", "test.test_email.test_contentmanager", "test.test_email.test_defect_handling", "test.test_email.test_email", "test.test_email.test_generator", "test.test_email.test_headerregistry", "test.test_email.test_inversion", "test.test_email.test_message", "test.test_email.test_parser", "test.test_email.test_pickleable", "test.test_email.test_policy", "test.test_email.test_utils", "test.test_email.torture_test", "test.test_ensurepip", "test.test_enum", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_hierarchy", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_faulthandler", "test.test_fcntl", "test.test_file", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_finalization", "test.test_float", "test.test_flufl", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fractions", "test.test_frame", "test.test_fstring", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_gc", "test.test_gdb", "test.test_generator_stop", "test.test_generators", "test.test_genericpath", "test.test_genexps", "test.test_getargs2", "test.test_getopt", "test.test_getpass", "test.test_gettext", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_html", "test.test_htmlparser", "test.test_http_cookiejar", "test.test_http_cookies", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imaplib", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_import.__main__", "test.test_import.data.circular_imports.basic", "test.test_import.data.circular_imports.basic2", "test.test_import.data.circular_imports.indirect", "test.test_import.data.circular_imports.rebinding", "test.test_import.data.circular_imports.rebinding2", "test.test_import.data.circular_imports.subpackage", "test.test_import.data.circular_imports.subpkg.subpackage2", "test.test_import.data.circular_imports.subpkg.util", "test.test_import.data.circular_imports.util", "test.test_import.data.package", "test.test_import.data.package.submodule", "test.test_import.data.package2.submodule1", "test.test_import.data.package2.submodule2", "test.test_importlib", "test.test_importlib.__main__", "test.test_importlib.abc", "test.test_importlib.builtin", "test.test_importlib.builtin.__main__", "test.test_importlib.builtin.test_finder", "test.test_importlib.builtin.test_loader", "test.test_importlib.extension", "test.test_importlib.extension.__main__", "test.test_importlib.extension.test_case_sensitivity", "test.test_importlib.extension.test_finder", "test.test_importlib.extension.test_loader", "test.test_importlib.extension.test_path_hook", "test.test_importlib.frozen", "test.test_importlib.frozen.__main__", "test.test_importlib.frozen.test_finder", "test.test_importlib.frozen.test_loader", "test.test_importlib.import_", "test.test_importlib.import_.__main__", "test.test_importlib.import_.test___loader__", "test.test_importlib.import_.test___package__", "test.test_importlib.import_.test_api", "test.test_importlib.import_.test_caching", "test.test_importlib.import_.test_fromlist", "test.test_importlib.import_.test_meta_path", "test.test_importlib.import_.test_packages", "test.test_importlib.import_.test_path", "test.test_importlib.import_.test_relative_imports", "test.test_importlib.namespace_pkgs.both_portions.foo.one", "test.test_importlib.namespace_pkgs.both_portions.foo.two", "test.test_importlib.namespace_pkgs.module_and_namespace_package.a_test", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo.one", "test.test_importlib.namespace_pkgs.portion1.foo.one", "test.test_importlib.namespace_pkgs.portion2.foo.two", "test.test_importlib.namespace_pkgs.project1.parent.child.one", "test.test_importlib.namespace_pkgs.project2.parent.child.two", "test.test_importlib.namespace_pkgs.project3.parent.child.three", "test.test_importlib.source", "test.test_importlib.source.__main__", "test.test_importlib.source.test_case_sensitivity", "test.test_importlib.source.test_file_loader", "test.test_importlib.source.test_finder", "test.test_importlib.source.test_path_hook", "test.test_importlib.source.test_source_encoding", "test.test_importlib.test_abc", "test.test_importlib.test_api", "test.test_importlib.test_lazy", "test.test_importlib.test_locks", "test.test_importlib.test_namespace_pkgs", "test.test_importlib.test_spec", "test.test_importlib.test_util", "test.test_importlib.test_windows", "test.test_importlib.util", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_ipaddress", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_json.__main__", "test.test_json.test_decode", "test.test_json.test_default", "test.test_json.test_dump", "test.test_json.test_encode_basestring_ascii", "test.test_json.test_enum", "test.test_json.test_fail", "test.test_json.test_float", "test.test_json.test_indent", "test.test_json.test_pass1", "test.test_json.test_pass2", "test.test_json.test_pass3", "test.test_json.test_recursion", "test.test_json.test_scanstring", "test.test_json.test_separators", "test.test_json.test_speedups", "test.test_json.test_tool", "test.test_json.test_unicode", "test.test_keyword", "test.test_keywordonlyarg", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_list", "test.test_listcomps", "test.test_locale", "test.test_logging", "test.test_long", "test.test_longexp", "test.test_lzma", "test.test_macpath", "test.test_macurl2path", "test.test_mailbox", "test.test_mailcap", "test.test_marshal", "test.test_math", "test.test_memoryio", "test.test_memoryview", "test.test_metaclass", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multiprocessing_fork", "test.test_multiprocessing_forkserver", "test.test_multiprocessing_main_handling", "test.test_multiprocessing_spawn", "test.test_netrc", "test.test_nis", "test.test_nntplib", "test.test_normalization", "test.test_ntpath", "test.test_numeric_tower", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_osx_env", "test.test_parser", "test.test_pathlib", "test.test_pdb", "test.test_peepholer", "test.test_pickle", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgimport", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_poplib", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pulldom", "test.test_pwd", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_raise", "test.test_random", "test.test_range", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_repl", "test.test_reprlib", "test.test_resource", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_sched", "test.test_scope", "test.test_script_helper", "test.test_secrets", "test.test_select", "test.test_selectors", "test.test_set", "test.test_setcomps", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtpd", "test.test_smtplib", "test.test_smtpnet", "test.test_sndhdr", "test.test_socket", "test.test_socketserver", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_statistics", "test.test_strftime", "test.test_string", "test.test_string_literals", "test.test_stringprep", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subclassinit", "test.test_subprocess", "test.test_sunau", "test.test_sundry", "test.test_super", "test.test_support", "test.test_symbol", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_syslog", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_textwrap", "test.test_thread", "test.test_threaded_import", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tix", "test.test_tk", "test.test_tokenize", "test.test_tools", "test.test_tools.__main__", "test.test_tools.test_fixcid", "test.test_tools.test_gprof2html", "test.test_tools.test_i18n", "test.test_tools.test_md5sum", "test.test_tools.test_pdeps", "test.test_tools.test_pindent", "test.test_tools.test_reindent", "test.test_tools.test_sundry", "test.test_tools.test_unparse", "test.test_trace", "test.test_traceback", "test.test_tracemalloc", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_turtle", "test.test_typechecks", "test.test_types", "test.test_typing", "test.test_ucn", "test.test_unary", "test.test_unicode", "test.test_unicode_file", "test.test_unicode_file_functions", "test.test_unicode_identifiers", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_unpack", "test.test_unpack_ex", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllib_response", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_utf8source", "test.test_uu", "test.test_uuid", "test.test_venv", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_warnings.__main__", "test.test_warnings.data.import_warning", "test.test_warnings.data.stacklevel", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_webbrowser", "test.test_winconsoleio", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_dom_minicompat", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmlrpc", "test.test_xmlrpc_net", "test.test_yield_from", "test.test_zipapp", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.testcodec", "test.tf_inherit_check", "test.threaded_import_hangers", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.win_console_handler", "test.xmltests", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.runtktests", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_functions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.model", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxlimited", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib" ], "3.7": [ "__future__", "__main__", "_abc", "_ast", "_asyncio", "_bisect", "_blake2", "_bootlocale", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_contextvars", "_crypt", "_csv", "_ctypes", "_ctypes_test", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_dummy_thread", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_pickle", "_posixsubprocess", "_py_abc", "_pydecimal", "_pyio", "_queue", "_random", "_sha1", "_sha256", "_sha3", "_sha512", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_string", "_strptime", "_struct", "_symtable", "_testbuffer", "_testcapi", "_testimportmultiple", "_testmultiphase", "_thread", "_threading_local", "_tkinter", "_tracemalloc", "_uuid", "_warnings", "_weakref", "_weakrefset", "_xxtestfuzz", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.transports", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_cygwinccompiler", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_extension", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_log", "distutils.tests.test_msvc9compiler", "distutils.tests.test_msvccompiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "dummy_threading", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp65001", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_centeuro", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.unicode_internal", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_paragraph", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_rstrip", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.paragraph", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.rstrip", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "imp", "importlib", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.resources", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "macpath", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.semaphore_tracker", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.backup", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._test_multiprocessing", "test.ann_module", "test.ann_module2", "test.ann_module3", "test.audiotests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.bad_getattr", "test.bad_getattr2", "test.bad_getattr3", "test.badsyntax_3131", "test.badsyntax_future10", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_pep3120", "test.bisect", "test.bisect_cmd", "test.bytecode_helper", "test.coding20731", "test.curses_tests", "test.dataclass_module_1", "test.dataclass_module_1_str", "test.dataclass_module_2", "test.dataclass_module_2_str", "test.dataclass_textanno", "test.datetimetester", "test.dis_module", "test.doctest_aliases", "test.double_const", "test.dtracedata.call_stack", "test.dtracedata.gc", "test.dtracedata.instance", "test.dtracedata.line", "test.eintrdata.eintr_tester", "test.encoded_modules", "test.encoded_modules.module_iso_8859_1", "test.encoded_modules.module_koi8_r", "test.final_a", "test.final_b", "test.fork_wait", "test.future_test1", "test.future_test2", "test.gdb_sample", "test.good_getattr", "test.imp_dummy", "test.inspect_fodder", "test.inspect_fodder2", "test.libregrtest", "test.libregrtest.cmdline", "test.libregrtest.main", "test.libregrtest.refleak", "test.libregrtest.runtest", "test.libregrtest.runtest_mp", "test.libregrtest.save_env", "test.libregrtest.setup", "test.libregrtest.utils", "test.libregrtest.win_utils", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.memory_watchdog", "test.mock_socket", "test.mod_generics_cache", "test.mp_fork_bomb", "test.mp_preload", "test.multibytecodec_support", "test.outstanding_bugs", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pythoninfo", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.seq_tests", "test.signalinterproctester", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.subprocessdata.fd_status", "test.subprocessdata.input_reader", "test.subprocessdata.qcat", "test.subprocessdata.qgrep", "test.subprocessdata.sigchild_ignore", "test.support", "test.support.script_helper", "test.support.testresult", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__opcode", "test.test__osx_support", "test.test_abc", "test.test_abstract_numbers", "test.test_aifc", "test.test_argparse", "test.test_array", "test.test_asdl_parser", "test.test_ast", "test.test_asyncgen", "test.test_asynchat", "test.test_asyncio", "test.test_asyncio.__main__", "test.test_asyncio.echo", "test.test_asyncio.echo2", "test.test_asyncio.echo3", "test.test_asyncio.functional", "test.test_asyncio.test_base_events", "test.test_asyncio.test_buffered_proto", "test.test_asyncio.test_context", "test.test_asyncio.test_events", "test.test_asyncio.test_futures", "test.test_asyncio.test_locks", "test.test_asyncio.test_pep492", "test.test_asyncio.test_proactor_events", "test.test_asyncio.test_queues", "test.test_asyncio.test_runners", "test.test_asyncio.test_selector_events", "test.test_asyncio.test_server", "test.test_asyncio.test_sslproto", "test.test_asyncio.test_streams", "test.test_asyncio.test_subprocess", "test.test_asyncio.test_tasks", "test.test_asyncio.test_transports", "test.test_asyncio.test_unix_events", "test.test_asyncio.test_windows_events", "test.test_asyncio.test_windows_utils", "test.test_asyncio.utils", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_augassign", "test.test_base64", "test.test_baseexception", "test.test_bdb", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_c_locale_coercion", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cgi", "test.test_cgitb", "test.test_charmapcodec", "test.test_class", "test.test_clinic", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_code_module", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_collections", "test.test_colorsys", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_complex", "test.test_concurrent_futures", "test.test_configparser", "test.test_contains", "test.test_context", "test.test_contextlib", "test.test_contextlib_async", "test.test_copy", "test.test_copyreg", "test.test_coroutines", "test.test_cprofile", "test.test_crashers", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_dataclasses", "test.test_datetime", "test.test_dbm", "test.test_dbm_dumb", "test.test_dbm_gnu", "test.test_dbm_ndbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_devpoll", "test.test_dict", "test.test_dict_version", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dis", "test.test_distutils", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dtrace", "test.test_dummy_thread", "test.test_dummy_threading", "test.test_dynamic", "test.test_dynamicclassattribute", "test.test_eintr", "test.test_email", "test.test_email.__main__", "test.test_email.test__encoded_words", "test.test_email.test__header_value_parser", "test.test_email.test_asian_codecs", "test.test_email.test_contentmanager", "test.test_email.test_defect_handling", "test.test_email.test_email", "test.test_email.test_generator", "test.test_email.test_headerregistry", "test.test_email.test_inversion", "test.test_email.test_message", "test.test_email.test_parser", "test.test_email.test_pickleable", "test.test_email.test_policy", "test.test_email.test_utils", "test.test_email.torture_test", "test.test_embed", "test.test_ensurepip", "test.test_enum", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_hierarchy", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_faulthandler", "test.test_fcntl", "test.test_file", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_finalization", "test.test_float", "test.test_flufl", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fractions", "test.test_frame", "test.test_frozen", "test.test_fstring", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_gc", "test.test_gdb", "test.test_generator_stop", "test.test_generators", "test.test_genericclass", "test.test_genericpath", "test.test_genexps", "test.test_getargs2", "test.test_getopt", "test.test_getpass", "test.test_gettext", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_html", "test.test_htmlparser", "test.test_http_cookiejar", "test.test_http_cookies", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imaplib", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_import.__main__", "test.test_import.data.circular_imports.basic", "test.test_import.data.circular_imports.basic2", "test.test_import.data.circular_imports.binding", "test.test_import.data.circular_imports.binding2", "test.test_import.data.circular_imports.indirect", "test.test_import.data.circular_imports.rebinding", "test.test_import.data.circular_imports.rebinding2", "test.test_import.data.circular_imports.subpackage", "test.test_import.data.circular_imports.subpkg.subpackage2", "test.test_import.data.circular_imports.subpkg.util", "test.test_import.data.circular_imports.util", "test.test_import.data.package", "test.test_import.data.package.submodule", "test.test_import.data.package2.submodule1", "test.test_import.data.package2.submodule2", "test.test_importlib", "test.test_importlib.__main__", "test.test_importlib.abc", "test.test_importlib.builtin", "test.test_importlib.builtin.__main__", "test.test_importlib.builtin.test_finder", "test.test_importlib.builtin.test_loader", "test.test_importlib.data01", "test.test_importlib.data01.subdirectory", "test.test_importlib.data02", "test.test_importlib.data02.one", "test.test_importlib.data02.two", "test.test_importlib.data03", "test.test_importlib.data03.namespace.portion1", "test.test_importlib.data03.namespace.portion2", "test.test_importlib.extension", "test.test_importlib.extension.__main__", "test.test_importlib.extension.test_case_sensitivity", "test.test_importlib.extension.test_finder", "test.test_importlib.extension.test_loader", "test.test_importlib.extension.test_path_hook", "test.test_importlib.frozen", "test.test_importlib.frozen.__main__", "test.test_importlib.frozen.test_finder", "test.test_importlib.frozen.test_loader", "test.test_importlib.import_", "test.test_importlib.import_.__main__", "test.test_importlib.import_.test___loader__", "test.test_importlib.import_.test___package__", "test.test_importlib.import_.test_api", "test.test_importlib.import_.test_caching", "test.test_importlib.import_.test_fromlist", "test.test_importlib.import_.test_meta_path", "test.test_importlib.import_.test_packages", "test.test_importlib.import_.test_path", "test.test_importlib.import_.test_relative_imports", "test.test_importlib.namespace_pkgs.both_portions.foo.one", "test.test_importlib.namespace_pkgs.both_portions.foo.two", "test.test_importlib.namespace_pkgs.module_and_namespace_package.a_test", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo.one", "test.test_importlib.namespace_pkgs.portion1.foo.one", "test.test_importlib.namespace_pkgs.portion2.foo.two", "test.test_importlib.namespace_pkgs.project1.parent.child.one", "test.test_importlib.namespace_pkgs.project2.parent.child.two", "test.test_importlib.namespace_pkgs.project3.parent.child.three", "test.test_importlib.source", "test.test_importlib.source.__main__", "test.test_importlib.source.test_case_sensitivity", "test.test_importlib.source.test_file_loader", "test.test_importlib.source.test_finder", "test.test_importlib.source.test_path_hook", "test.test_importlib.source.test_source_encoding", "test.test_importlib.test_abc", "test.test_importlib.test_api", "test.test_importlib.test_lazy", "test.test_importlib.test_locks", "test.test_importlib.test_namespace_pkgs", "test.test_importlib.test_open", "test.test_importlib.test_path", "test.test_importlib.test_read", "test.test_importlib.test_resource", "test.test_importlib.test_spec", "test.test_importlib.test_util", "test.test_importlib.test_windows", "test.test_importlib.util", "test.test_importlib.zipdata01", "test.test_importlib.zipdata02", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_ipaddress", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_json.__main__", "test.test_json.test_decode", "test.test_json.test_default", "test.test_json.test_dump", "test.test_json.test_encode_basestring_ascii", "test.test_json.test_enum", "test.test_json.test_fail", "test.test_json.test_float", "test.test_json.test_indent", "test.test_json.test_pass1", "test.test_json.test_pass2", "test.test_json.test_pass3", "test.test_json.test_recursion", "test.test_json.test_scanstring", "test.test_json.test_separators", "test.test_json.test_speedups", "test.test_json.test_tool", "test.test_json.test_unicode", "test.test_keyword", "test.test_keywordonlyarg", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_list", "test.test_listcomps", "test.test_locale", "test.test_logging", "test.test_long", "test.test_longexp", "test.test_lzma", "test.test_macpath", "test.test_mailbox", "test.test_mailcap", "test.test_marshal", "test.test_math", "test.test_memoryio", "test.test_memoryview", "test.test_metaclass", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multiprocessing_fork", "test.test_multiprocessing_forkserver", "test.test_multiprocessing_main_handling", "test.test_multiprocessing_spawn", "test.test_netrc", "test.test_nis", "test.test_nntplib", "test.test_normalization", "test.test_ntpath", "test.test_numeric_tower", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_osx_env", "test.test_parser", "test.test_pathlib", "test.test_pdb", "test.test_peepholer", "test.test_pickle", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgimport", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_poplib", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pulldom", "test.test_pwd", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_raise", "test.test_random", "test.test_range", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_repl", "test.test_reprlib", "test.test_resource", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_sched", "test.test_scope", "test.test_script_helper", "test.test_secrets", "test.test_select", "test.test_selectors", "test.test_set", "test.test_setcomps", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtpd", "test.test_smtplib", "test.test_smtpnet", "test.test_sndhdr", "test.test_socket", "test.test_socketserver", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_statistics", "test.test_strftime", "test.test_string", "test.test_string_literals", "test.test_stringprep", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subclassinit", "test.test_subprocess", "test.test_sunau", "test.test_sundry", "test.test_super", "test.test_support", "test.test_symbol", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_syslog", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_textwrap", "test.test_thread", "test.test_threaded_import", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tix", "test.test_tk", "test.test_tokenize", "test.test_tools", "test.test_tools.__main__", "test.test_tools.test_fixcid", "test.test_tools.test_gprof2html", "test.test_tools.test_i18n", "test.test_tools.test_lll", "test.test_tools.test_md5sum", "test.test_tools.test_pdeps", "test.test_tools.test_pindent", "test.test_tools.test_reindent", "test.test_tools.test_sundry", "test.test_tools.test_unparse", "test.test_trace", "test.test_traceback", "test.test_tracemalloc", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_turtle", "test.test_typechecks", "test.test_types", "test.test_typing", "test.test_ucn", "test.test_unary", "test.test_unicode", "test.test_unicode_file", "test.test_unicode_file_functions", "test.test_unicode_identifiers", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_unpack", "test.test_unpack_ex", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllib_response", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_utf8_mode", "test.test_utf8source", "test.test_uu", "test.test_uuid", "test.test_venv", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_warnings.__main__", "test.test_warnings.data.import_warning", "test.test_warnings.data.stacklevel", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_webbrowser", "test.test_winconsoleio", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_dom_minicompat", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmlrpc", "test.test_xmlrpc_net", "test.test_xxtestfuzz", "test.test_yield_from", "test.test_zipapp", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.testcodec", "test.tf_inherit_check", "test.threaded_import_hangers", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.win_console_handler", "test.xmltests", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.runtktests", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_functions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsealable", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.errors.dom", "xml.parsers.expat.errors.etree", "xml.parsers.expat.errors.parsers", "xml.parsers.expat.errors.sax", "xml.parsers.expat.model", "xml.parsers.expat.model.dom", "xml.parsers.expat.model.etree", "xml.parsers.expat.model.parsers", "xml.parsers.expat.model.sax", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxlimited", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib" ], "3.8": [ "__future__", "__main__", "_abc", "_ast", "_asyncio", "_bisect", "_blake2", "_bootlocale", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_contextvars", "_crypt", "_csv", "_ctypes", "_ctypes_test", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_dummy_thread", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_pickle", "_posixshmem", "_posixsubprocess", "_py_abc", "_pydecimal", "_pyio", "_queue", "_random", "_sha1", "_sha256", "_sha3", "_sha512", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_statistics", "_string", "_strptime", "_struct", "_symtable", "_testbuffer", "_testcapi", "_testimportmultiple", "_testinternalcapi", "_testmultiphase", "_thread", "_threading_local", "_tkinter", "_tracemalloc", "_uuid", "_warnings", "_weakref", "_weakrefset", "_xxsubinterpreters", "_xxtestfuzz", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.__main__", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.exceptions", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.staggered", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.transports", "asyncio.trsock", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_cygwinccompiler", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_extension", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_log", "distutils.tests.test_msvc9compiler", "distutils.tests.test_msvccompiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "dummy_threading", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_centeuro", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.format", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_format", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_sidebar", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.sidebar", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "imp", "importlib", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.metadata", "importlib.resources", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.resource_tracker", "multiprocessing.shared_memory", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.backup", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._test_multiprocessing", "test.ann_module", "test.ann_module2", "test.ann_module3", "test.audiotests", "test.audit-tests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.bad_getattr", "test.bad_getattr2", "test.bad_getattr3", "test.badsyntax_3131", "test.badsyntax_future10", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_pep3120", "test.bisect_cmd", "test.bytecode_helper", "test.coding20731", "test.curses_tests", "test.dataclass_module_1", "test.dataclass_module_1_str", "test.dataclass_module_2", "test.dataclass_module_2_str", "test.dataclass_textanno", "test.datetimetester", "test.dis_module", "test.doctest_aliases", "test.double_const", "test.dtracedata.call_stack", "test.dtracedata.gc", "test.dtracedata.instance", "test.dtracedata.line", "test.eintrdata.eintr_tester", "test.encoded_modules", "test.encoded_modules.module_iso_8859_1", "test.encoded_modules.module_koi8_r", "test.final_a", "test.final_b", "test.fork_wait", "test.future_test1", "test.future_test2", "test.gdb_sample", "test.good_getattr", "test.imp_dummy", "test.inspect_fodder", "test.inspect_fodder2", "test.libregrtest", "test.libregrtest.cmdline", "test.libregrtest.main", "test.libregrtest.pgo", "test.libregrtest.refleak", "test.libregrtest.runtest", "test.libregrtest.runtest_mp", "test.libregrtest.save_env", "test.libregrtest.setup", "test.libregrtest.utils", "test.libregrtest.win_utils", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.memory_watchdog", "test.mock_socket", "test.mod_generics_cache", "test.mp_fork_bomb", "test.mp_preload", "test.multibytecodec_support", "test.outstanding_bugs", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pythoninfo", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.seq_tests", "test.signalinterproctester", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.subprocessdata.fd_status", "test.subprocessdata.input_reader", "test.subprocessdata.qcat", "test.subprocessdata.qgrep", "test.subprocessdata.sigchild_ignore", "test.support", "test.support.script_helper", "test.support.testresult", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__opcode", "test.test__osx_support", "test.test__xxsubinterpreters", "test.test_abc", "test.test_abstract_numbers", "test.test_aifc", "test.test_argparse", "test.test_array", "test.test_asdl_parser", "test.test_ast", "test.test_asyncgen", "test.test_asynchat", "test.test_asyncio", "test.test_asyncio.__main__", "test.test_asyncio.echo", "test.test_asyncio.echo2", "test.test_asyncio.echo3", "test.test_asyncio.functional", "test.test_asyncio.test_asyncio_waitfor", "test.test_asyncio.test_base_events", "test.test_asyncio.test_buffered_proto", "test.test_asyncio.test_context", "test.test_asyncio.test_events", "test.test_asyncio.test_futures", "test.test_asyncio.test_futures2", "test.test_asyncio.test_locks", "test.test_asyncio.test_pep492", "test.test_asyncio.test_proactor_events", "test.test_asyncio.test_protocols", "test.test_asyncio.test_queues", "test.test_asyncio.test_runners", "test.test_asyncio.test_selector_events", "test.test_asyncio.test_sendfile", "test.test_asyncio.test_server", "test.test_asyncio.test_sock_lowlevel", "test.test_asyncio.test_sslproto", "test.test_asyncio.test_streams", "test.test_asyncio.test_subprocess", "test.test_asyncio.test_tasks", "test.test_asyncio.test_transports", "test.test_asyncio.test_unix_events", "test.test_asyncio.test_windows_events", "test.test_asyncio.test_windows_utils", "test.test_asyncio.utils", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_audit", "test.test_augassign", "test.test_base64", "test.test_baseexception", "test.test_bdb", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_c_locale_coercion", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cgi", "test.test_cgitb", "test.test_charmapcodec", "test.test_class", "test.test_clinic", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_code_module", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_collections", "test.test_colorsys", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_complex", "test.test_concurrent_futures", "test.test_configparser", "test.test_contains", "test.test_context", "test.test_contextlib", "test.test_contextlib_async", "test.test_copy", "test.test_copyreg", "test.test_coroutines", "test.test_cprofile", "test.test_crashers", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_dataclasses", "test.test_datetime", "test.test_dbm", "test.test_dbm_dumb", "test.test_dbm_gnu", "test.test_dbm_ndbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_devpoll", "test.test_dict", "test.test_dict_version", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dis", "test.test_distutils", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dtrace", "test.test_dummy_thread", "test.test_dummy_threading", "test.test_dynamic", "test.test_dynamicclassattribute", "test.test_eintr", "test.test_email", "test.test_email.__main__", "test.test_email.test__encoded_words", "test.test_email.test__header_value_parser", "test.test_email.test_asian_codecs", "test.test_email.test_contentmanager", "test.test_email.test_defect_handling", "test.test_email.test_email", "test.test_email.test_generator", "test.test_email.test_headerregistry", "test.test_email.test_inversion", "test.test_email.test_message", "test.test_email.test_parser", "test.test_email.test_pickleable", "test.test_email.test_policy", "test.test_email.test_utils", "test.test_email.torture_test", "test.test_embed", "test.test_ensurepip", "test.test_enum", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_hierarchy", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_faulthandler", "test.test_fcntl", "test.test_file", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_finalization", "test.test_float", "test.test_flufl", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fractions", "test.test_frame", "test.test_frozen", "test.test_fstring", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_gc", "test.test_gdb", "test.test_generator_stop", "test.test_generators", "test.test_genericclass", "test.test_genericpath", "test.test_genexps", "test.test_getargs2", "test.test_getopt", "test.test_getpass", "test.test_gettext", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_html", "test.test_htmlparser", "test.test_http_cookiejar", "test.test_http_cookies", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imaplib", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_import.__main__", "test.test_import.data.circular_imports.basic", "test.test_import.data.circular_imports.basic2", "test.test_import.data.circular_imports.binding", "test.test_import.data.circular_imports.binding2", "test.test_import.data.circular_imports.from_cycle1", "test.test_import.data.circular_imports.from_cycle2", "test.test_import.data.circular_imports.indirect", "test.test_import.data.circular_imports.rebinding", "test.test_import.data.circular_imports.rebinding2", "test.test_import.data.circular_imports.source", "test.test_import.data.circular_imports.subpackage", "test.test_import.data.circular_imports.subpkg.subpackage2", "test.test_import.data.circular_imports.subpkg.util", "test.test_import.data.circular_imports.use", "test.test_import.data.circular_imports.util", "test.test_import.data.package", "test.test_import.data.package.submodule", "test.test_import.data.package2.submodule1", "test.test_import.data.package2.submodule2", "test.test_importlib", "test.test_importlib.__main__", "test.test_importlib.abc", "test.test_importlib.builtin", "test.test_importlib.builtin.__main__", "test.test_importlib.builtin.test_finder", "test.test_importlib.builtin.test_loader", "test.test_importlib.data", "test.test_importlib.data01", "test.test_importlib.data01.subdirectory", "test.test_importlib.data02", "test.test_importlib.data02.one", "test.test_importlib.data02.two", "test.test_importlib.data03", "test.test_importlib.data03.namespace.portion1", "test.test_importlib.data03.namespace.portion2", "test.test_importlib.extension", "test.test_importlib.extension.__main__", "test.test_importlib.extension.test_case_sensitivity", "test.test_importlib.extension.test_finder", "test.test_importlib.extension.test_loader", "test.test_importlib.extension.test_path_hook", "test.test_importlib.fixtures", "test.test_importlib.frozen", "test.test_importlib.frozen.__main__", "test.test_importlib.frozen.test_finder", "test.test_importlib.frozen.test_loader", "test.test_importlib.import_", "test.test_importlib.import_.__main__", "test.test_importlib.import_.test___loader__", "test.test_importlib.import_.test___package__", "test.test_importlib.import_.test_api", "test.test_importlib.import_.test_caching", "test.test_importlib.import_.test_fromlist", "test.test_importlib.import_.test_meta_path", "test.test_importlib.import_.test_packages", "test.test_importlib.import_.test_path", "test.test_importlib.import_.test_relative_imports", "test.test_importlib.namespace_pkgs.both_portions.foo.one", "test.test_importlib.namespace_pkgs.both_portions.foo.two", "test.test_importlib.namespace_pkgs.module_and_namespace_package.a_test", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo", "test.test_importlib.namespace_pkgs.not_a_namespace_pkg.foo.one", "test.test_importlib.namespace_pkgs.portion1.foo.one", "test.test_importlib.namespace_pkgs.portion2.foo.two", "test.test_importlib.namespace_pkgs.project1.parent.child.one", "test.test_importlib.namespace_pkgs.project2.parent.child.two", "test.test_importlib.namespace_pkgs.project3.parent.child.three", "test.test_importlib.source", "test.test_importlib.source.__main__", "test.test_importlib.source.test_case_sensitivity", "test.test_importlib.source.test_file_loader", "test.test_importlib.source.test_finder", "test.test_importlib.source.test_path_hook", "test.test_importlib.source.test_source_encoding", "test.test_importlib.stubs", "test.test_importlib.test_abc", "test.test_importlib.test_api", "test.test_importlib.test_lazy", "test.test_importlib.test_locks", "test.test_importlib.test_main", "test.test_importlib.test_metadata_api", "test.test_importlib.test_namespace_pkgs", "test.test_importlib.test_open", "test.test_importlib.test_path", "test.test_importlib.test_read", "test.test_importlib.test_resource", "test.test_importlib.test_spec", "test.test_importlib.test_util", "test.test_importlib.test_windows", "test.test_importlib.test_zip", "test.test_importlib.util", "test.test_importlib.zipdata01", "test.test_importlib.zipdata02", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_ipaddress", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_json.__main__", "test.test_json.test_decode", "test.test_json.test_default", "test.test_json.test_dump", "test.test_json.test_encode_basestring_ascii", "test.test_json.test_enum", "test.test_json.test_fail", "test.test_json.test_float", "test.test_json.test_indent", "test.test_json.test_pass1", "test.test_json.test_pass2", "test.test_json.test_pass3", "test.test_json.test_recursion", "test.test_json.test_scanstring", "test.test_json.test_separators", "test.test_json.test_speedups", "test.test_json.test_tool", "test.test_json.test_unicode", "test.test_keyword", "test.test_keywordonlyarg", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_list", "test.test_listcomps", "test.test_lltrace", "test.test_locale", "test.test_logging", "test.test_long", "test.test_longexp", "test.test_lzma", "test.test_mailbox", "test.test_mailcap", "test.test_marshal", "test.test_math", "test.test_memoryio", "test.test_memoryview", "test.test_metaclass", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multiprocessing_fork", "test.test_multiprocessing_forkserver", "test.test_multiprocessing_main_handling", "test.test_multiprocessing_spawn", "test.test_named_expressions", "test.test_netrc", "test.test_nis", "test.test_nntplib", "test.test_normalization", "test.test_ntpath", "test.test_numeric_tower", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_osx_env", "test.test_parser", "test.test_pathlib", "test.test_pdb", "test.test_peepholer", "test.test_pickle", "test.test_picklebuffer", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgimport", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_poplib", "test.test_positional_only_arg", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pulldom", "test.test_pwd", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_raise", "test.test_random", "test.test_range", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_repl", "test.test_reprlib", "test.test_resource", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_sched", "test.test_scope", "test.test_script_helper", "test.test_secrets", "test.test_select", "test.test_selectors", "test.test_set", "test.test_setcomps", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtpd", "test.test_smtplib", "test.test_smtpnet", "test.test_sndhdr", "test.test_socket", "test.test_socketserver", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_statistics", "test.test_strftime", "test.test_string", "test.test_string_literals", "test.test_stringprep", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subclassinit", "test.test_subprocess", "test.test_sunau", "test.test_sundry", "test.test_super", "test.test_support", "test.test_symbol", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_syslog", "test.test_tabnanny", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_textwrap", "test.test_thread", "test.test_threaded_import", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tix", "test.test_tk", "test.test_tokenize", "test.test_tools", "test.test_tools.__main__", "test.test_tools.test_fixcid", "test.test_tools.test_gprof2html", "test.test_tools.test_i18n", "test.test_tools.test_lll", "test.test_tools.test_md5sum", "test.test_tools.test_pathfix", "test.test_tools.test_pdeps", "test.test_tools.test_pindent", "test.test_tools.test_reindent", "test.test_tools.test_sundry", "test.test_tools.test_unparse", "test.test_trace", "test.test_traceback", "test.test_tracemalloc", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_turtle", "test.test_type_comments", "test.test_typechecks", "test.test_types", "test.test_typing", "test.test_ucn", "test.test_unary", "test.test_unicode", "test.test_unicode_file", "test.test_unicode_file_functions", "test.test_unicode_identifiers", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_unpack", "test.test_unpack_ex", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllib_response", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_utf8_mode", "test.test_utf8source", "test.test_uu", "test.test_uuid", "test.test_venv", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_warnings.__main__", "test.test_warnings.data.import_warning", "test.test_warnings.data.stacklevel", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_webbrowser", "test.test_winconsoleio", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_dom_minicompat", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmlrpc", "test.test_xmlrpc_net", "test.test_xxtestfuzz", "test.test_yield_from", "test.test_zipapp", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.testcodec", "test.tf_inherit_check", "test.threaded_import_hangers", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.win_console_handler", "test.xmltests", "test.ziptestdata.testdata_module_inside_zip", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.runtktests", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_colorchooser", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_simpledialog", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_functions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "typing.io", "typing.re", "unicodedata", "unittest", "unittest.__main__", "unittest.async_case", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_async_case", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testasync", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsealable", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.errors.dom", "xml.parsers.expat.errors.etree", "xml.parsers.expat.errors.parsers", "xml.parsers.expat.errors.sax", "xml.parsers.expat.model", "xml.parsers.expat.model.dom", "xml.parsers.expat.model.etree", "xml.parsers.expat.model.parsers", "xml.parsers.expat.model.sax", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxlimited", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib" ], "3.9": [ "__future__", "__main__", "__phello__.foo", "_abc", "_aix_support", "_ast", "_bootlocale", "_bootsubprocess", "_codecs", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_crypt", "_functools", "_hashlib", "_imp", "_io", "_locale", "_lsprof", "_markupbase", "_operator", "_osx_support", "_peg_parser", "_posixsubprocess", "_py_abc", "_pydecimal", "_pyio", "_random", "_signal", "_sitebuiltins", "_socket", "_sre", "_ssl", "_stat", "_string", "_strptime", "_symtable", "_sysconfigdata_x86_64_conda_cos6_linux_gnu", "_sysconfigdata_x86_64_conda_linux_gnu", "_thread", "_threading_local", "_tracemalloc", "_uuid", "_warnings", "_weakref", "_weakrefset", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.__main__", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.exceptions", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.staggered", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.threads", "asyncio.transports", "asyncio.trsock", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_msi", "distutils.command.bdist_packager", "distutils.command.bdist_rpm", "distutils.command.bdist_wininst", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.spawn", "distutils.sysconfig", "distutils.tests", "distutils.tests.support", "distutils.tests.test_archive_util", "distutils.tests.test_bdist", "distutils.tests.test_bdist_dumb", "distutils.tests.test_bdist_msi", "distutils.tests.test_bdist_rpm", "distutils.tests.test_bdist_wininst", "distutils.tests.test_build", "distutils.tests.test_build_clib", "distutils.tests.test_build_ext", "distutils.tests.test_build_py", "distutils.tests.test_build_scripts", "distutils.tests.test_check", "distutils.tests.test_clean", "distutils.tests.test_cmd", "distutils.tests.test_config", "distutils.tests.test_config_cmd", "distutils.tests.test_core", "distutils.tests.test_cygwinccompiler", "distutils.tests.test_dep_util", "distutils.tests.test_dir_util", "distutils.tests.test_dist", "distutils.tests.test_extension", "distutils.tests.test_file_util", "distutils.tests.test_filelist", "distutils.tests.test_install", "distutils.tests.test_install_data", "distutils.tests.test_install_headers", "distutils.tests.test_install_lib", "distutils.tests.test_install_scripts", "distutils.tests.test_log", "distutils.tests.test_msvc9compiler", "distutils.tests.test_msvccompiler", "distutils.tests.test_register", "distutils.tests.test_sdist", "distutils.tests.test_spawn", "distutils.tests.test_sysconfig", "distutils.tests.test_text_file", "distutils.tests.test_unixccompiler", "distutils.tests.test_upload", "distutils.tests.test_util", "distutils.tests.test_version", "distutils.tests.test_versionpredicate", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._bundled", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "formatter", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.format", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_format", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_sidebar", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.sidebar", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "imp", "importlib", "importlib._bootstrap", "importlib._bootstrap_external", "importlib._common", "importlib.abc", "importlib.machinery", "importlib.metadata", "importlib.resources", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib.libpython3", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.data.bom", "lib2to3.tests.data.crlf", "lib2to3.tests.data.different_encoding", "lib2to3.tests.data.false_encoding", "lib2to3.tests.data.fixers.bad_order", "lib2to3.tests.data.fixers.myfixes", "lib2to3.tests.data.fixers.myfixes.fix_explicit", "lib2to3.tests.data.fixers.myfixes.fix_first", "lib2to3.tests.data.fixers.myfixes.fix_last", "lib2to3.tests.data.fixers.myfixes.fix_parrot", "lib2to3.tests.data.fixers.myfixes.fix_preorder", "lib2to3.tests.data.fixers.no_fixer_cls", "lib2to3.tests.data.fixers.parrot_example", "lib2to3.tests.data.infinite_recursion", "lib2to3.tests.data.py2_test_grammar", "lib2to3.tests.data.py3_test_grammar", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.resource_tracker", "multiprocessing.shared_memory", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "parser", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.backup", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symbol", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "test", "test.__main__", "test._test_multiprocessing", "test._typed_dict_helper", "test.ann_module", "test.ann_module2", "test.ann_module3", "test.ann_module5", "test.ann_module6", "test.ann_module7", "test.audiotests", "test.audit-tests", "test.autotest", "test.bad_coding", "test.bad_coding2", "test.bad_getattr", "test.bad_getattr2", "test.bad_getattr3", "test.badsyntax_3131", "test.badsyntax_future10", "test.badsyntax_future3", "test.badsyntax_future4", "test.badsyntax_future5", "test.badsyntax_future6", "test.badsyntax_future7", "test.badsyntax_future8", "test.badsyntax_future9", "test.badsyntax_pep3120", "test.bisect_cmd", "test.coding20731", "test.curses_tests", "test.dataclass_module_1", "test.dataclass_module_1_str", "test.dataclass_module_2", "test.dataclass_module_2_str", "test.dataclass_textanno", "test.datetimetester", "test.dis_module", "test.doctest_aliases", "test.double_const", "test.encoded_modules", "test.encoded_modules.module_iso_8859_1", "test.encoded_modules.module_koi8_r", "test.final_a", "test.final_b", "test.fork_wait", "test.future_test1", "test.future_test2", "test.gdb_sample", "test.good_getattr", "test.imp_dummy", "test.inspect_fodder", "test.inspect_fodder2", "test.libregrtest", "test.libregrtest.cmdline", "test.libregrtest.main", "test.libregrtest.pgo", "test.libregrtest.refleak", "test.libregrtest.runtest", "test.libregrtest.runtest_mp", "test.libregrtest.save_env", "test.libregrtest.setup", "test.libregrtest.utils", "test.libregrtest.win_utils", "test.list_tests", "test.lock_tests", "test.make_ssl_certs", "test.mapping_tests", "test.memory_watchdog", "test.mock_socket", "test.mod_generics_cache", "test.mp_fork_bomb", "test.mp_preload", "test.multibytecodec_support", "test.pickletester", "test.profilee", "test.pyclbr_input", "test.pydoc_mod", "test.pydocfodder", "test.pythoninfo", "test.re_tests", "test.regrtest", "test.relimport", "test.reperf", "test.sample_doctest", "test.sample_doctest_no_docstrings", "test.sample_doctest_no_doctests", "test.seq_tests", "test.signalinterproctester", "test.sortperf", "test.ssl_servers", "test.ssltests", "test.string_tests", "test.support", "test.support.bytecode_helper", "test.support.hashlib_helper", "test.support.logging_helper", "test.support.script_helper", "test.support.socket_helper", "test.support.testresult", "test.support.warnings_helper", "test.test___all__", "test.test___future__", "test.test__locale", "test.test__opcode", "test.test__osx_support", "test.test__xxsubinterpreters", "test.test_abc", "test.test_abstract_numbers", "test.test_aifc", "test.test_argparse", "test.test_array", "test.test_asdl_parser", "test.test_ast", "test.test_asyncgen", "test.test_asynchat", "test.test_asyncio", "test.test_asyncio.__main__", "test.test_asyncio.echo", "test.test_asyncio.echo2", "test.test_asyncio.echo3", "test.test_asyncio.functional", "test.test_asyncio.test_base_events", "test.test_asyncio.test_buffered_proto", "test.test_asyncio.test_context", "test.test_asyncio.test_events", "test.test_asyncio.test_futures", "test.test_asyncio.test_futures2", "test.test_asyncio.test_locks", "test.test_asyncio.test_pep492", "test.test_asyncio.test_proactor_events", "test.test_asyncio.test_protocols", "test.test_asyncio.test_queues", "test.test_asyncio.test_runners", "test.test_asyncio.test_selector_events", "test.test_asyncio.test_sendfile", "test.test_asyncio.test_server", "test.test_asyncio.test_sock_lowlevel", "test.test_asyncio.test_sslproto", "test.test_asyncio.test_streams", "test.test_asyncio.test_subprocess", "test.test_asyncio.test_tasks", "test.test_asyncio.test_threads", "test.test_asyncio.test_transports", "test.test_asyncio.test_unix_events", "test.test_asyncio.test_waitfor", "test.test_asyncio.test_windows_events", "test.test_asyncio.test_windows_utils", "test.test_asyncio.utils", "test.test_asyncore", "test.test_atexit", "test.test_audioop", "test.test_audit", "test.test_augassign", "test.test_base64", "test.test_baseexception", "test.test_bdb", "test.test_bigaddrspace", "test.test_bigmem", "test.test_binascii", "test.test_binhex", "test.test_binop", "test.test_bisect", "test.test_bool", "test.test_buffer", "test.test_bufio", "test.test_builtin", "test.test_bytes", "test.test_bz2", "test.test_c_locale_coercion", "test.test_calendar", "test.test_call", "test.test_capi", "test.test_cgi", "test.test_cgitb", "test.test_charmapcodec", "test.test_check_c_globals", "test.test_class", "test.test_clinic", "test.test_cmath", "test.test_cmd", "test.test_cmd_line", "test.test_cmd_line_script", "test.test_code", "test.test_code_module", "test.test_codeccallbacks", "test.test_codecencodings_cn", "test.test_codecencodings_hk", "test.test_codecencodings_iso2022", "test.test_codecencodings_jp", "test.test_codecencodings_kr", "test.test_codecencodings_tw", "test.test_codecmaps_cn", "test.test_codecmaps_hk", "test.test_codecmaps_jp", "test.test_codecmaps_kr", "test.test_codecmaps_tw", "test.test_codecs", "test.test_codeop", "test.test_collections", "test.test_colorsys", "test.test_compare", "test.test_compile", "test.test_compileall", "test.test_complex", "test.test_concurrent_futures", "test.test_configparser", "test.test_contains", "test.test_context", "test.test_contextlib", "test.test_contextlib_async", "test.test_copy", "test.test_copyreg", "test.test_coroutines", "test.test_cprofile", "test.test_crashers", "test.test_crypt", "test.test_csv", "test.test_ctypes", "test.test_curses", "test.test_dataclasses", "test.test_datetime", "test.test_dbm", "test.test_dbm_dumb", "test.test_dbm_gnu", "test.test_dbm_ndbm", "test.test_decimal", "test.test_decorators", "test.test_defaultdict", "test.test_deque", "test.test_descr", "test.test_descrtut", "test.test_devpoll", "test.test_dict", "test.test_dict_version", "test.test_dictcomps", "test.test_dictviews", "test.test_difflib", "test.test_dis", "test.test_distutils", "test.test_doctest", "test.test_doctest2", "test.test_docxmlrpc", "test.test_dtrace", "test.test_dynamic", "test.test_dynamicclassattribute", "test.test_eintr", "test.test_email", "test.test_email.__main__", "test.test_email.test__encoded_words", "test.test_email.test__header_value_parser", "test.test_email.test_asian_codecs", "test.test_email.test_contentmanager", "test.test_email.test_defect_handling", "test.test_email.test_email", "test.test_email.test_generator", "test.test_email.test_headerregistry", "test.test_email.test_inversion", "test.test_email.test_message", "test.test_email.test_parser", "test.test_email.test_pickleable", "test.test_email.test_policy", "test.test_email.test_utils", "test.test_email.torture_test", "test.test_embed", "test.test_ensurepip", "test.test_enum", "test.test_enumerate", "test.test_eof", "test.test_epoll", "test.test_errno", "test.test_exception_hierarchy", "test.test_exception_variations", "test.test_exceptions", "test.test_extcall", "test.test_faulthandler", "test.test_fcntl", "test.test_file", "test.test_file_eintr", "test.test_filecmp", "test.test_fileinput", "test.test_fileio", "test.test_finalization", "test.test_float", "test.test_flufl", "test.test_fnmatch", "test.test_fork1", "test.test_format", "test.test_fractions", "test.test_frame", "test.test_frozen", "test.test_fstring", "test.test_ftplib", "test.test_funcattrs", "test.test_functools", "test.test_future", "test.test_future3", "test.test_future4", "test.test_future5", "test.test_gc", "test.test_gdb", "test.test_generator_stop", "test.test_generators", "test.test_genericalias", "test.test_genericclass", "test.test_genericpath", "test.test_genexps", "test.test_getargs2", "test.test_getopt", "test.test_getpass", "test.test_gettext", "test.test_glob", "test.test_global", "test.test_grammar", "test.test_graphlib", "test.test_grp", "test.test_gzip", "test.test_hash", "test.test_hashlib", "test.test_heapq", "test.test_hmac", "test.test_html", "test.test_htmlparser", "test.test_http_cookiejar", "test.test_http_cookies", "test.test_httplib", "test.test_httpservers", "test.test_idle", "test.test_imaplib", "test.test_imghdr", "test.test_imp", "test.test_import", "test.test_import.__main__", "test.test_importlib", "test.test_importlib.__main__", "test.test_importlib.abc", "test.test_importlib.builtin", "test.test_importlib.builtin.__main__", "test.test_importlib.builtin.test_finder", "test.test_importlib.builtin.test_loader", "test.test_importlib.data", "test.test_importlib.data01", "test.test_importlib.data01.subdirectory", "test.test_importlib.data02", "test.test_importlib.data02.one", "test.test_importlib.data02.two", "test.test_importlib.data03", "test.test_importlib.extension", "test.test_importlib.extension.__main__", "test.test_importlib.extension.test_case_sensitivity", "test.test_importlib.extension.test_finder", "test.test_importlib.extension.test_loader", "test.test_importlib.extension.test_path_hook", "test.test_importlib.fixtures", "test.test_importlib.frozen", "test.test_importlib.frozen.__main__", "test.test_importlib.frozen.test_finder", "test.test_importlib.frozen.test_loader", "test.test_importlib.import_", "test.test_importlib.import_.__main__", "test.test_importlib.import_.test___loader__", "test.test_importlib.import_.test___package__", "test.test_importlib.import_.test_api", "test.test_importlib.import_.test_caching", "test.test_importlib.import_.test_fromlist", "test.test_importlib.import_.test_meta_path", "test.test_importlib.import_.test_packages", "test.test_importlib.import_.test_path", "test.test_importlib.import_.test_relative_imports", "test.test_importlib.source", "test.test_importlib.source.__main__", "test.test_importlib.source.test_case_sensitivity", "test.test_importlib.source.test_file_loader", "test.test_importlib.source.test_finder", "test.test_importlib.source.test_path_hook", "test.test_importlib.source.test_source_encoding", "test.test_importlib.stubs", "test.test_importlib.test_abc", "test.test_importlib.test_api", "test.test_importlib.test_files", "test.test_importlib.test_lazy", "test.test_importlib.test_locks", "test.test_importlib.test_main", "test.test_importlib.test_metadata_api", "test.test_importlib.test_namespace_pkgs", "test.test_importlib.test_open", "test.test_importlib.test_path", "test.test_importlib.test_pkg_import", "test.test_importlib.test_read", "test.test_importlib.test_resource", "test.test_importlib.test_spec", "test.test_importlib.test_threaded_import", "test.test_importlib.test_util", "test.test_importlib.test_windows", "test.test_importlib.test_zip", "test.test_importlib.threaded_import_hangers", "test.test_importlib.util", "test.test_importlib.zipdata01", "test.test_importlib.zipdata02", "test.test_index", "test.test_inspect", "test.test_int", "test.test_int_literal", "test.test_io", "test.test_ioctl", "test.test_ipaddress", "test.test_isinstance", "test.test_iter", "test.test_iterlen", "test.test_itertools", "test.test_json", "test.test_json.__main__", "test.test_json.test_decode", "test.test_json.test_default", "test.test_json.test_dump", "test.test_json.test_encode_basestring_ascii", "test.test_json.test_enum", "test.test_json.test_fail", "test.test_json.test_float", "test.test_json.test_indent", "test.test_json.test_pass1", "test.test_json.test_pass2", "test.test_json.test_pass3", "test.test_json.test_recursion", "test.test_json.test_scanstring", "test.test_json.test_separators", "test.test_json.test_speedups", "test.test_json.test_tool", "test.test_json.test_unicode", "test.test_keyword", "test.test_keywordonlyarg", "test.test_kqueue", "test.test_largefile", "test.test_lib2to3", "test.test_linecache", "test.test_list", "test.test_listcomps", "test.test_lltrace", "test.test_locale", "test.test_logging", "test.test_long", "test.test_longexp", "test.test_lzma", "test.test_mailbox", "test.test_mailcap", "test.test_marshal", "test.test_math", "test.test_memoryio", "test.test_memoryview", "test.test_metaclass", "test.test_mimetypes", "test.test_minidom", "test.test_mmap", "test.test_module", "test.test_modulefinder", "test.test_msilib", "test.test_multibytecodec", "test.test_multiprocessing_fork", "test.test_multiprocessing_forkserver", "test.test_multiprocessing_main_handling", "test.test_multiprocessing_spawn", "test.test_named_expressions", "test.test_netrc", "test.test_nis", "test.test_nntplib", "test.test_ntpath", "test.test_numeric_tower", "test.test_opcodes", "test.test_openpty", "test.test_operator", "test.test_optparse", "test.test_ordered_dict", "test.test_os", "test.test_ossaudiodev", "test.test_osx_env", "test.test_parser", "test.test_pathlib", "test.test_pdb", "test.test_peepholer", "test.test_peg_generator", "test.test_peg_generator.__main__", "test.test_peg_generator.test_c_parser", "test.test_peg_generator.test_first_sets", "test.test_peg_generator.test_pegen", "test.test_peg_parser", "test.test_pickle", "test.test_picklebuffer", "test.test_pickletools", "test.test_pipes", "test.test_pkg", "test.test_pkgutil", "test.test_platform", "test.test_plistlib", "test.test_poll", "test.test_popen", "test.test_poplib", "test.test_positional_only_arg", "test.test_posix", "test.test_posixpath", "test.test_pow", "test.test_pprint", "test.test_print", "test.test_profile", "test.test_property", "test.test_pstats", "test.test_pty", "test.test_pulldom", "test.test_pwd", "test.test_py_compile", "test.test_pyclbr", "test.test_pydoc", "test.test_pyexpat", "test.test_queue", "test.test_quopri", "test.test_raise", "test.test_random", "test.test_range", "test.test_re", "test.test_readline", "test.test_regrtest", "test.test_repl", "test.test_reprlib", "test.test_resource", "test.test_richcmp", "test.test_rlcompleter", "test.test_robotparser", "test.test_runpy", "test.test_sax", "test.test_sched", "test.test_scope", "test.test_script_helper", "test.test_secrets", "test.test_select", "test.test_selectors", "test.test_set", "test.test_setcomps", "test.test_shelve", "test.test_shlex", "test.test_shutil", "test.test_signal", "test.test_site", "test.test_slice", "test.test_smtpd", "test.test_smtplib", "test.test_smtpnet", "test.test_sndhdr", "test.test_socket", "test.test_socketserver", "test.test_sort", "test.test_source_encoding", "test.test_spwd", "test.test_sqlite", "test.test_ssl", "test.test_startfile", "test.test_stat", "test.test_statistics", "test.test_strftime", "test.test_string", "test.test_string_literals", "test.test_stringprep", "test.test_strptime", "test.test_strtod", "test.test_struct", "test.test_structmembers", "test.test_structseq", "test.test_subclassinit", "test.test_subprocess", "test.test_sunau", "test.test_sundry", "test.test_super", "test.test_support", "test.test_symbol", "test.test_symtable", "test.test_syntax", "test.test_sys", "test.test_sys_setprofile", "test.test_sys_settrace", "test.test_sysconfig", "test.test_syslog", "test.test_tabnanny", "test.test_tarfile", "test.test_tcl", "test.test_telnetlib", "test.test_tempfile", "test.test_textwrap", "test.test_thread", "test.test_threadedtempfile", "test.test_threading", "test.test_threading_local", "test.test_threadsignals", "test.test_time", "test.test_timeit", "test.test_timeout", "test.test_tix", "test.test_tk", "test.test_tokenize", "test.test_tools", "test.test_tools.__main__", "test.test_tools.test_fixcid", "test.test_tools.test_gprof2html", "test.test_tools.test_i18n", "test.test_tools.test_lll", "test.test_tools.test_md5sum", "test.test_tools.test_pathfix", "test.test_tools.test_pdeps", "test.test_tools.test_pindent", "test.test_tools.test_reindent", "test.test_tools.test_sundry", "test.test_trace", "test.test_traceback", "test.test_tracemalloc", "test.test_ttk_guionly", "test.test_ttk_textonly", "test.test_tuple", "test.test_turtle", "test.test_type_comments", "test.test_typechecks", "test.test_types", "test.test_typing", "test.test_ucn", "test.test_unary", "test.test_unicode", "test.test_unicode_file", "test.test_unicode_file_functions", "test.test_unicode_identifiers", "test.test_unicodedata", "test.test_unittest", "test.test_univnewlines", "test.test_unpack", "test.test_unpack_ex", "test.test_unparse", "test.test_urllib", "test.test_urllib2", "test.test_urllib2_localnet", "test.test_urllib2net", "test.test_urllib_response", "test.test_urllibnet", "test.test_urlparse", "test.test_userdict", "test.test_userlist", "test.test_userstring", "test.test_utf8_mode", "test.test_utf8source", "test.test_uu", "test.test_uuid", "test.test_venv", "test.test_wait3", "test.test_wait4", "test.test_warnings", "test.test_warnings.__main__", "test.test_wave", "test.test_weakref", "test.test_weakset", "test.test_webbrowser", "test.test_winconsoleio", "test.test_winreg", "test.test_winsound", "test.test_with", "test.test_wsgiref", "test.test_xdrlib", "test.test_xml_dom_minicompat", "test.test_xml_etree", "test.test_xml_etree_c", "test.test_xmlrpc", "test.test_xmlrpc_net", "test.test_xxtestfuzz", "test.test_yield_from", "test.test_zipapp", "test.test_zipfile", "test.test_zipfile64", "test.test_zipimport", "test.test_zipimport_support", "test.test_zlib", "test.test_zoneinfo", "test.test_zoneinfo.__main__", "test.test_zoneinfo._support", "test.test_zoneinfo.test_zoneinfo", "test.testcodec", "test.tf_inherit_check", "test.time_hashlib", "test.tracedmodules", "test.tracedmodules.testmod", "test.win_console_handler", "test.xmltests", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_colorchooser", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_simpledialog", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest._log", "unittest.async_case", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_async_case", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testasync", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsealable", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.parsers.expat.errors", "xml.parsers.expat.errors.dom", "xml.parsers.expat.errors.etree", "xml.parsers.expat.errors.parsers", "xml.parsers.expat.errors.sax", "xml.parsers.expat.model", "xml.parsers.expat.model.dom", "xml.parsers.expat.model.etree", "xml.parsers.expat.model.parsers", "xml.parsers.expat.model.sax", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib", "zoneinfo", "zoneinfo._common", "zoneinfo._tzpath", "zoneinfo._zoneinfo" ], "3.10": [ "__future__", "__main__", "_abc", "_aix_support", "_ast", "_asyncio", "_bisect", "_blake2", "_bootsubprocess", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_contextvars", "_crypt", "_csv", "_ctypes", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_msi", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_overlapped", "_pickle", "_posixshmem", "_posixsubprocess", "_py_abc", "_pydecimal", "_pyio", "_queue", "_random", "_scproxy", "_sha1", "_sha256", "_sha3", "_sha512", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_statistics", "_string", "_strptime", "_struct", "_symtable", "_thread", "_threading_local", "_tkinter", "_tracemalloc", "_uuid", "_warnings", "_weakref", "_weakrefset", "_winapi", "_zoneinfo", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.__main__", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.exceptions", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.mixins", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.staggered", "asyncio.streams", "asyncio.subprocess", "asyncio.tasks", "asyncio.threads", "asyncio.transports", "asyncio.trsock", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "binhex", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._collections", "distutils._functools", "distutils._macos_compat", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command._framework_compat", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_rpm", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.py37compat", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.py38compat", "distutils.py39compat", "distutils.spawn", "distutils.sysconfig", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._bundled", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.format", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_format", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_sidebar", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_util", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.idle_test.test_zzdummy", "idlelib.idle_test.tkinter_testing_utils", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.sidebar", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.util", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "imp", "importlib", "importlib._abc", "importlib._adapters", "importlib._bootstrap", "importlib._bootstrap_external", "importlib._common", "importlib.abc", "importlib.machinery", "importlib.metadata", "importlib.metadata._adapters", "importlib.metadata._collections", "importlib.metadata._functools", "importlib.metadata._itertools", "importlib.metadata._meta", "importlib.metadata._text", "importlib.readers", "importlib.resources", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.resource_tracker", "multiprocessing.shared_memory", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "nt", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "pyexpat.errors", "pyexpat.model", "queue", "quopri", "random", "re", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sqlite3.test", "sqlite3.test.backup", "sqlite3.test.dbapi", "sqlite3.test.dump", "sqlite3.test.factory", "sqlite3.test.hooks", "sqlite3.test.regression", "sqlite3.test.transactions", "sqlite3.test.types", "sqlite3.test.userfunctions", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_colorchooser", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_messagebox", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_simpledialog", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest._log", "unittest.async_case", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_async_case", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testasync", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsealable", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib", "zoneinfo", "zoneinfo._common", "zoneinfo._tzpath", "zoneinfo._zoneinfo" ], "3.11": [ "__future__", "__main__", "_abc", "_aix_support", "_ast", "_asyncio", "_bisect", "_blake2", "_bootsubprocess", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_contextvars", "_crypt", "_csv", "_ctypes", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_msi", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_overlapped", "_pickle", "_posixshmem", "_posixsubprocess", "_py_abc", "_pydecimal", "_pyio", "_queue", "_random", "_scproxy", "_sha1", "_sha256", "_sha3", "_sha512", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_statistics", "_string", "_strptime", "_struct", "_symtable", "_thread", "_threading_local", "_tkinter", "_tokenize", "_tracemalloc", "_typing", "_uuid", "_warnings", "_weakref", "_weakrefset", "_winapi", "_zoneinfo", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asynchat", "asyncio", "asyncio.__main__", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.exceptions", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.mixins", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.staggered", "asyncio.streams", "asyncio.subprocess", "asyncio.taskgroups", "asyncio.tasks", "asyncio.threads", "asyncio.timeouts", "asyncio.transports", "asyncio.trsock", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "asyncore", "atexit", "audioop", "base64", "bdb", "binascii", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.test", "ctypes.test.__main__", "ctypes.test.test_anon", "ctypes.test.test_array_in_pointer", "ctypes.test.test_arrays", "ctypes.test.test_as_parameter", "ctypes.test.test_bitfields", "ctypes.test.test_buffers", "ctypes.test.test_bytes", "ctypes.test.test_byteswap", "ctypes.test.test_callbacks", "ctypes.test.test_cast", "ctypes.test.test_cfuncs", "ctypes.test.test_checkretval", "ctypes.test.test_delattr", "ctypes.test.test_errno", "ctypes.test.test_find", "ctypes.test.test_frombuffer", "ctypes.test.test_funcptr", "ctypes.test.test_functions", "ctypes.test.test_incomplete", "ctypes.test.test_init", "ctypes.test.test_internals", "ctypes.test.test_keeprefs", "ctypes.test.test_libc", "ctypes.test.test_loading", "ctypes.test.test_macholib", "ctypes.test.test_memfunctions", "ctypes.test.test_numbers", "ctypes.test.test_objects", "ctypes.test.test_parameters", "ctypes.test.test_pep3118", "ctypes.test.test_pickling", "ctypes.test.test_pointers", "ctypes.test.test_prototypes", "ctypes.test.test_python_api", "ctypes.test.test_random_things", "ctypes.test.test_refcounts", "ctypes.test.test_repr", "ctypes.test.test_returnfuncptrs", "ctypes.test.test_simplesubclasses", "ctypes.test.test_sizes", "ctypes.test.test_slicing", "ctypes.test.test_stringptr", "ctypes.test.test_strings", "ctypes.test.test_struct_fields", "ctypes.test.test_structures", "ctypes.test.test_unaligned_structures", "ctypes.test.test_unicode", "ctypes.test.test_values", "ctypes.test.test_varsize_struct", "ctypes.test.test_win32", "ctypes.test.test_wintypes", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "distutils", "distutils._collections", "distutils._functools", "distutils._macos_compat", "distutils._msvccompiler", "distutils.archive_util", "distutils.bcppcompiler", "distutils.ccompiler", "distutils.cmd", "distutils.command", "distutils.command._framework_compat", "distutils.command.bdist", "distutils.command.bdist_dumb", "distutils.command.bdist_rpm", "distutils.command.build", "distutils.command.build_clib", "distutils.command.build_ext", "distutils.command.build_py", "distutils.command.build_scripts", "distutils.command.check", "distutils.command.clean", "distutils.command.config", "distutils.command.install", "distutils.command.install_data", "distutils.command.install_egg_info", "distutils.command.install_headers", "distutils.command.install_lib", "distutils.command.install_scripts", "distutils.command.py37compat", "distutils.command.register", "distutils.command.sdist", "distutils.command.upload", "distutils.config", "distutils.core", "distutils.cygwinccompiler", "distutils.debug", "distutils.dep_util", "distutils.dir_util", "distutils.dist", "distutils.errors", "distutils.extension", "distutils.fancy_getopt", "distutils.file_util", "distutils.filelist", "distutils.log", "distutils.msvc9compiler", "distutils.msvccompiler", "distutils.py38compat", "distutils.py39compat", "distutils.spawn", "distutils.sysconfig", "distutils.text_file", "distutils.unixccompiler", "distutils.util", "distutils.version", "distutils.versionpredicate", "doctest", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.format", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_format", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_sidebar", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_util", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.idle_test.test_zzdummy", "idlelib.idle_test.tkinter_testing_utils", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.sidebar", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.util", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "imp", "importlib", "importlib._abc", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.metadata", "importlib.metadata._adapters", "importlib.metadata._collections", "importlib.metadata._functools", "importlib.metadata._itertools", "importlib.metadata._meta", "importlib.metadata._text", "importlib.readers", "importlib.resources", "importlib.resources._adapters", "importlib.resources._common", "importlib.resources._itertools", "importlib.resources._legacy", "importlib.resources.abc", "importlib.resources.readers", "importlib.resources.simple", "importlib.simple", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "lib2to3.tests", "lib2to3.tests.__main__", "lib2to3.tests.pytree_idempotency", "lib2to3.tests.support", "lib2to3.tests.test_all_fixers", "lib2to3.tests.test_fixers", "lib2to3.tests.test_main", "lib2to3.tests.test_parser", "lib2to3.tests.test_pytree", "lib2to3.tests.test_refactor", "lib2to3.tests.test_util", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.resource_tracker", "multiprocessing.shared_memory", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "nt", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "pyexpat.errors", "pyexpat.model", "queue", "quopri", "random", "re", "re._casefix", "re._compiler", "re._constants", "re._parser", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtpd", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.dbapi2", "sqlite3.dump", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.test", "tkinter.test.support", "tkinter.test.test_tkinter", "tkinter.test.test_tkinter.test_colorchooser", "tkinter.test.test_tkinter.test_font", "tkinter.test.test_tkinter.test_geometry_managers", "tkinter.test.test_tkinter.test_images", "tkinter.test.test_tkinter.test_loadtk", "tkinter.test.test_tkinter.test_messagebox", "tkinter.test.test_tkinter.test_misc", "tkinter.test.test_tkinter.test_simpledialog", "tkinter.test.test_tkinter.test_text", "tkinter.test.test_tkinter.test_variables", "tkinter.test.test_tkinter.test_widgets", "tkinter.test.test_ttk", "tkinter.test.test_ttk.test_extensions", "tkinter.test.test_ttk.test_style", "tkinter.test.test_ttk.test_widgets", "tkinter.test.widget_tests", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "tomllib", "tomllib._parser", "tomllib._re", "tomllib._types", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest._log", "unittest.async_case", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.test", "unittest.test.__main__", "unittest.test._test_warnings", "unittest.test.dummy", "unittest.test.support", "unittest.test.test_assertions", "unittest.test.test_async_case", "unittest.test.test_break", "unittest.test.test_case", "unittest.test.test_discovery", "unittest.test.test_functiontestcase", "unittest.test.test_loader", "unittest.test.test_program", "unittest.test.test_result", "unittest.test.test_runner", "unittest.test.test_setups", "unittest.test.test_skipping", "unittest.test.test_suite", "unittest.test.testmock", "unittest.test.testmock.__main__", "unittest.test.testmock.support", "unittest.test.testmock.testasync", "unittest.test.testmock.testcallable", "unittest.test.testmock.testhelpers", "unittest.test.testmock.testmagicmethods", "unittest.test.testmock.testmock", "unittest.test.testmock.testpatch", "unittest.test.testmock.testsealable", "unittest.test.testmock.testsentinel", "unittest.test.testmock.testwith", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.types", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "xxsubtype", "zipapp", "zipfile", "zipimport", "zlib", "zoneinfo", "zoneinfo._common", "zoneinfo._tzpath", "zoneinfo._zoneinfo" ], "3.12": [ "__future__", "__main__", "_abc", "_aix_support", "_ast", "_asyncio", "_bisect", "_blake2", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_compat_pickle", "_compression", "_contextvars", "_crypt", "_csv", "_ctypes", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_io", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_msi", "_multibytecodec", "_multiprocessing", "_opcode", "_operator", "_osx_support", "_overlapped", "_pickle", "_posixshmem", "_posixsubprocess", "_py_abc", "_pydatetime", "_pydecimal", "_pyio", "_pylong", "_queue", "_random", "_scproxy", "_sha1", "_sha2", "_sha3", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_statistics", "_string", "_strptime", "_struct", "_symtable", "_thread", "_threading_local", "_tkinter", "_tokenize", "_tracemalloc", "_typing", "_uuid", "_warnings", "_weakref", "_weakrefset", "_winapi", "_wmi", "_zoneinfo", "abc", "aifc", "antigravity", "argparse", "array", "ast", "asyncio", "asyncio.__main__", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.exceptions", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.mixins", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.staggered", "asyncio.streams", "asyncio.subprocess", "asyncio.taskgroups", "asyncio.tasks", "asyncio.threads", "asyncio.timeouts", "asyncio.transports", "asyncio.trsock", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "atexit", "audioop", "base64", "bdb", "binascii", "bisect", "builtins", "bz2", "cProfile", "calendar", "cgi", "cgitb", "chunk", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "crypt", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "decimal", "difflib", "dis", "doctest", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.format", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_format", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_sidebar", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_util", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.idle_test.test_zzdummy", "idlelib.idle_test.tkinter_testing_utils", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.sidebar", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.util", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "imghdr", "importlib", "importlib._abc", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.metadata", "importlib.metadata._adapters", "importlib.metadata._collections", "importlib.metadata._functools", "importlib.metadata._itertools", "importlib.metadata._meta", "importlib.metadata._text", "importlib.readers", "importlib.resources", "importlib.resources._adapters", "importlib.resources._common", "importlib.resources._itertools", "importlib.resources._legacy", "importlib.resources.abc", "importlib.resources.readers", "importlib.resources.simple", "importlib.simple", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "lib2to3", "lib2to3.__main__", "lib2to3.btm_matcher", "lib2to3.btm_utils", "lib2to3.fixer_base", "lib2to3.fixer_util", "lib2to3.fixes", "lib2to3.fixes.fix_apply", "lib2to3.fixes.fix_asserts", "lib2to3.fixes.fix_basestring", "lib2to3.fixes.fix_buffer", "lib2to3.fixes.fix_dict", "lib2to3.fixes.fix_except", "lib2to3.fixes.fix_exec", "lib2to3.fixes.fix_execfile", "lib2to3.fixes.fix_exitfunc", "lib2to3.fixes.fix_filter", "lib2to3.fixes.fix_funcattrs", "lib2to3.fixes.fix_future", "lib2to3.fixes.fix_getcwdu", "lib2to3.fixes.fix_has_key", "lib2to3.fixes.fix_idioms", "lib2to3.fixes.fix_import", "lib2to3.fixes.fix_imports", "lib2to3.fixes.fix_imports2", "lib2to3.fixes.fix_input", "lib2to3.fixes.fix_intern", "lib2to3.fixes.fix_isinstance", "lib2to3.fixes.fix_itertools", "lib2to3.fixes.fix_itertools_imports", "lib2to3.fixes.fix_long", "lib2to3.fixes.fix_map", "lib2to3.fixes.fix_metaclass", "lib2to3.fixes.fix_methodattrs", "lib2to3.fixes.fix_ne", "lib2to3.fixes.fix_next", "lib2to3.fixes.fix_nonzero", "lib2to3.fixes.fix_numliterals", "lib2to3.fixes.fix_operator", "lib2to3.fixes.fix_paren", "lib2to3.fixes.fix_print", "lib2to3.fixes.fix_raise", "lib2to3.fixes.fix_raw_input", "lib2to3.fixes.fix_reduce", "lib2to3.fixes.fix_reload", "lib2to3.fixes.fix_renames", "lib2to3.fixes.fix_repr", "lib2to3.fixes.fix_set_literal", "lib2to3.fixes.fix_standarderror", "lib2to3.fixes.fix_sys_exc", "lib2to3.fixes.fix_throw", "lib2to3.fixes.fix_tuple_params", "lib2to3.fixes.fix_types", "lib2to3.fixes.fix_unicode", "lib2to3.fixes.fix_urllib", "lib2to3.fixes.fix_ws_comma", "lib2to3.fixes.fix_xrange", "lib2to3.fixes.fix_xreadlines", "lib2to3.fixes.fix_zip", "lib2to3.main", "lib2to3.patcomp", "lib2to3.pgen2", "lib2to3.pgen2.conv", "lib2to3.pgen2.driver", "lib2to3.pgen2.grammar", "lib2to3.pgen2.literals", "lib2to3.pgen2.parse", "lib2to3.pgen2.pgen", "lib2to3.pgen2.token", "lib2to3.pgen2.tokenize", "lib2to3.pygram", "lib2to3.pytree", "lib2to3.refactor", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "mailbox", "mailcap", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msilib", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.resource_tracker", "multiprocessing.shared_memory", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nis", "nntplib", "nt", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "ossaudiodev", "pathlib", "pdb", "pickle", "pickletools", "pipes", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "pyexpat.errors", "pyexpat.model", "queue", "quopri", "random", "re", "re._casefix", "re._compiler", "re._constants", "re._parser", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtplib", "sndhdr", "socket", "socketserver", "spwd", "sqlite3", "sqlite3.__main__", "sqlite3.dbapi2", "sqlite3.dump", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "sunau", "symtable", "sys", "sysconfig", "syslog", "tabnanny", "tarfile", "telnetlib", "tempfile", "termios", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.tix", "tkinter.ttk", "token", "tokenize", "tomllib", "tomllib._parser", "tomllib._re", "tomllib._types", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest._log", "unittest.async_case", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uu", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.types", "wsgiref.util", "wsgiref.validate", "xdrlib", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "zipapp", "zipfile", "zipfile.__main__", "zipfile._path", "zipfile._path.glob", "zipimport", "zlib", "zoneinfo", "zoneinfo._common", "zoneinfo._tzpath", "zoneinfo._zoneinfo" ], "3.13": [ "__future__", "__main__", "_abc", "_aix_support", "_android_support", "_apple_support", "_ast", "_asyncio", "_bisect", "_blake2", "_bz2", "_codecs", "_codecs_cn", "_codecs_hk", "_codecs_iso2022", "_codecs_jp", "_codecs_kr", "_codecs_tw", "_collections", "_collections_abc", "_colorize", "_compat_pickle", "_compression", "_contextvars", "_csv", "_ctypes", "_curses", "_curses_panel", "_datetime", "_dbm", "_decimal", "_elementtree", "_frozen_importlib", "_frozen_importlib_external", "_functools", "_gdbm", "_hashlib", "_heapq", "_imp", "_interpchannels", "_interpqueues", "_interpreters", "_io", "_ios_support", "_json", "_locale", "_lsprof", "_lzma", "_markupbase", "_md5", "_multibytecodec", "_multiprocessing", "_opcode", "_opcode_metadata", "_operator", "_osx_support", "_overlapped", "_pickle", "_posixshmem", "_posixsubprocess", "_py_abc", "_pydatetime", "_pydecimal", "_pyio", "_pylong", "_pyrepl", "_pyrepl.__main__", "_pyrepl._minimal_curses", "_pyrepl._threading_handler", "_pyrepl.commands", "_pyrepl.completing_reader", "_pyrepl.console", "_pyrepl.curses", "_pyrepl.fancy_termios", "_pyrepl.historical_reader", "_pyrepl.input", "_pyrepl.keymap", "_pyrepl.main", "_pyrepl.pager", "_pyrepl.reader", "_pyrepl.readline", "_pyrepl.simple_interact", "_pyrepl.trace", "_pyrepl.types", "_pyrepl.unix_console", "_pyrepl.unix_eventqueue", "_pyrepl.utils", "_pyrepl.windows_console", "_queue", "_random", "_scproxy", "_sha1", "_sha2", "_sha3", "_signal", "_sitebuiltins", "_socket", "_sqlite3", "_sre", "_ssl", "_stat", "_statistics", "_string", "_strptime", "_struct", "_suggestions", "_symtable", "_sysconfig", "_thread", "_threading_local", "_tkinter", "_tokenize", "_tracemalloc", "_typing", "_uuid", "_warnings", "_weakref", "_weakrefset", "_winapi", "_wmi", "_zoneinfo", "abc", "antigravity", "argparse", "array", "ast", "asyncio", "asyncio.__main__", "asyncio.base_events", "asyncio.base_futures", "asyncio.base_subprocess", "asyncio.base_tasks", "asyncio.constants", "asyncio.coroutines", "asyncio.events", "asyncio.exceptions", "asyncio.format_helpers", "asyncio.futures", "asyncio.locks", "asyncio.log", "asyncio.mixins", "asyncio.proactor_events", "asyncio.protocols", "asyncio.queues", "asyncio.runners", "asyncio.selector_events", "asyncio.sslproto", "asyncio.staggered", "asyncio.streams", "asyncio.subprocess", "asyncio.taskgroups", "asyncio.tasks", "asyncio.threads", "asyncio.timeouts", "asyncio.transports", "asyncio.trsock", "asyncio.unix_events", "asyncio.windows_events", "asyncio.windows_utils", "atexit", "base64", "bdb", "binascii", "bisect", "builtins", "bz2", "cProfile", "calendar", "cmath", "cmd", "code", "codecs", "codeop", "collections", "collections.abc", "colorsys", "compileall", "concurrent", "concurrent.futures", "concurrent.futures._base", "concurrent.futures.process", "concurrent.futures.thread", "configparser", "contextlib", "contextvars", "copy", "copyreg", "csv", "ctypes", "ctypes._aix", "ctypes._endian", "ctypes.macholib", "ctypes.macholib.dyld", "ctypes.macholib.dylib", "ctypes.macholib.framework", "ctypes.util", "ctypes.wintypes", "curses", "curses.ascii", "curses.has_key", "curses.panel", "curses.textpad", "dataclasses", "datetime", "dbm", "dbm.dumb", "dbm.gnu", "dbm.ndbm", "dbm.sqlite3", "decimal", "difflib", "dis", "doctest", "email", "email._encoded_words", "email._header_value_parser", "email._parseaddr", "email._policybase", "email.base64mime", "email.charset", "email.contentmanager", "email.encoders", "email.errors", "email.feedparser", "email.generator", "email.header", "email.headerregistry", "email.iterators", "email.message", "email.mime", "email.mime.application", "email.mime.audio", "email.mime.base", "email.mime.image", "email.mime.message", "email.mime.multipart", "email.mime.nonmultipart", "email.mime.text", "email.parser", "email.policy", "email.quoprimime", "email.utils", "encodings", "encodings.aliases", "encodings.ascii", "encodings.base64_codec", "encodings.big5", "encodings.big5hkscs", "encodings.bz2_codec", "encodings.charmap", "encodings.cp037", "encodings.cp1006", "encodings.cp1026", "encodings.cp1125", "encodings.cp1140", "encodings.cp1250", "encodings.cp1251", "encodings.cp1252", "encodings.cp1253", "encodings.cp1254", "encodings.cp1255", "encodings.cp1256", "encodings.cp1257", "encodings.cp1258", "encodings.cp273", "encodings.cp424", "encodings.cp437", "encodings.cp500", "encodings.cp720", "encodings.cp737", "encodings.cp775", "encodings.cp850", "encodings.cp852", "encodings.cp855", "encodings.cp856", "encodings.cp857", "encodings.cp858", "encodings.cp860", "encodings.cp861", "encodings.cp862", "encodings.cp863", "encodings.cp864", "encodings.cp865", "encodings.cp866", "encodings.cp869", "encodings.cp874", "encodings.cp875", "encodings.cp932", "encodings.cp949", "encodings.cp950", "encodings.euc_jis_2004", "encodings.euc_jisx0213", "encodings.euc_jp", "encodings.euc_kr", "encodings.gb18030", "encodings.gb2312", "encodings.gbk", "encodings.hex_codec", "encodings.hp_roman8", "encodings.hz", "encodings.idna", "encodings.iso2022_jp", "encodings.iso2022_jp_1", "encodings.iso2022_jp_2", "encodings.iso2022_jp_2004", "encodings.iso2022_jp_3", "encodings.iso2022_jp_ext", "encodings.iso2022_kr", "encodings.iso8859_1", "encodings.iso8859_10", "encodings.iso8859_11", "encodings.iso8859_13", "encodings.iso8859_14", "encodings.iso8859_15", "encodings.iso8859_16", "encodings.iso8859_2", "encodings.iso8859_3", "encodings.iso8859_4", "encodings.iso8859_5", "encodings.iso8859_6", "encodings.iso8859_7", "encodings.iso8859_8", "encodings.iso8859_9", "encodings.johab", "encodings.koi8_r", "encodings.koi8_t", "encodings.koi8_u", "encodings.kz1048", "encodings.latin_1", "encodings.mac_arabic", "encodings.mac_croatian", "encodings.mac_cyrillic", "encodings.mac_farsi", "encodings.mac_greek", "encodings.mac_iceland", "encodings.mac_latin2", "encodings.mac_roman", "encodings.mac_romanian", "encodings.mac_turkish", "encodings.mbcs", "encodings.oem", "encodings.palmos", "encodings.ptcp154", "encodings.punycode", "encodings.quopri_codec", "encodings.raw_unicode_escape", "encodings.rot_13", "encodings.shift_jis", "encodings.shift_jis_2004", "encodings.shift_jisx0213", "encodings.tis_620", "encodings.undefined", "encodings.unicode_escape", "encodings.utf_16", "encodings.utf_16_be", "encodings.utf_16_le", "encodings.utf_32", "encodings.utf_32_be", "encodings.utf_32_le", "encodings.utf_7", "encodings.utf_8", "encodings.utf_8_sig", "encodings.uu_codec", "encodings.zlib_codec", "ensurepip", "ensurepip.__main__", "ensurepip._uninstall", "enum", "errno", "faulthandler", "fcntl", "filecmp", "fileinput", "fnmatch", "fractions", "ftplib", "functools", "gc", "genericpath", "getopt", "getpass", "gettext", "glob", "graphlib", "grp", "gzip", "hashlib", "heapq", "hmac", "html", "html.entities", "html.parser", "http", "http.client", "http.cookiejar", "http.cookies", "http.server", "idlelib", "idlelib.__main__", "idlelib.autocomplete", "idlelib.autocomplete_w", "idlelib.autoexpand", "idlelib.browser", "idlelib.calltip", "idlelib.calltip_w", "idlelib.codecontext", "idlelib.colorizer", "idlelib.config", "idlelib.config_key", "idlelib.configdialog", "idlelib.debugger", "idlelib.debugger_r", "idlelib.debugobj", "idlelib.debugobj_r", "idlelib.delegator", "idlelib.dynoption", "idlelib.editor", "idlelib.filelist", "idlelib.format", "idlelib.grep", "idlelib.help", "idlelib.help_about", "idlelib.history", "idlelib.hyperparser", "idlelib.idle", "idlelib.idle_test", "idlelib.idle_test.htest", "idlelib.idle_test.mock_idle", "idlelib.idle_test.mock_tk", "idlelib.idle_test.template", "idlelib.idle_test.test_autocomplete", "idlelib.idle_test.test_autocomplete_w", "idlelib.idle_test.test_autoexpand", "idlelib.idle_test.test_browser", "idlelib.idle_test.test_calltip", "idlelib.idle_test.test_calltip_w", "idlelib.idle_test.test_codecontext", "idlelib.idle_test.test_colorizer", "idlelib.idle_test.test_config", "idlelib.idle_test.test_config_key", "idlelib.idle_test.test_configdialog", "idlelib.idle_test.test_debugger", "idlelib.idle_test.test_debugger_r", "idlelib.idle_test.test_debugobj", "idlelib.idle_test.test_debugobj_r", "idlelib.idle_test.test_delegator", "idlelib.idle_test.test_editmenu", "idlelib.idle_test.test_editor", "idlelib.idle_test.test_filelist", "idlelib.idle_test.test_format", "idlelib.idle_test.test_grep", "idlelib.idle_test.test_help", "idlelib.idle_test.test_help_about", "idlelib.idle_test.test_history", "idlelib.idle_test.test_hyperparser", "idlelib.idle_test.test_iomenu", "idlelib.idle_test.test_macosx", "idlelib.idle_test.test_mainmenu", "idlelib.idle_test.test_multicall", "idlelib.idle_test.test_outwin", "idlelib.idle_test.test_parenmatch", "idlelib.idle_test.test_pathbrowser", "idlelib.idle_test.test_percolator", "idlelib.idle_test.test_pyparse", "idlelib.idle_test.test_pyshell", "idlelib.idle_test.test_query", "idlelib.idle_test.test_redirector", "idlelib.idle_test.test_replace", "idlelib.idle_test.test_rpc", "idlelib.idle_test.test_run", "idlelib.idle_test.test_runscript", "idlelib.idle_test.test_scrolledlist", "idlelib.idle_test.test_search", "idlelib.idle_test.test_searchbase", "idlelib.idle_test.test_searchengine", "idlelib.idle_test.test_sidebar", "idlelib.idle_test.test_squeezer", "idlelib.idle_test.test_stackviewer", "idlelib.idle_test.test_statusbar", "idlelib.idle_test.test_text", "idlelib.idle_test.test_textview", "idlelib.idle_test.test_tooltip", "idlelib.idle_test.test_tree", "idlelib.idle_test.test_undo", "idlelib.idle_test.test_util", "idlelib.idle_test.test_warning", "idlelib.idle_test.test_window", "idlelib.idle_test.test_zoomheight", "idlelib.idle_test.test_zzdummy", "idlelib.idle_test.tkinter_testing_utils", "idlelib.iomenu", "idlelib.macosx", "idlelib.mainmenu", "idlelib.multicall", "idlelib.outwin", "idlelib.parenmatch", "idlelib.pathbrowser", "idlelib.percolator", "idlelib.pyparse", "idlelib.pyshell", "idlelib.query", "idlelib.redirector", "idlelib.replace", "idlelib.rpc", "idlelib.run", "idlelib.runscript", "idlelib.scrolledlist", "idlelib.search", "idlelib.searchbase", "idlelib.searchengine", "idlelib.sidebar", "idlelib.squeezer", "idlelib.stackviewer", "idlelib.statusbar", "idlelib.textview", "idlelib.tooltip", "idlelib.tree", "idlelib.undo", "idlelib.util", "idlelib.window", "idlelib.zoomheight", "idlelib.zzdummy", "imaplib", "importlib", "importlib._abc", "importlib._bootstrap", "importlib._bootstrap_external", "importlib.abc", "importlib.machinery", "importlib.metadata", "importlib.metadata._adapters", "importlib.metadata._collections", "importlib.metadata._functools", "importlib.metadata._itertools", "importlib.metadata._meta", "importlib.metadata._text", "importlib.metadata.diagnose", "importlib.readers", "importlib.resources", "importlib.resources._adapters", "importlib.resources._common", "importlib.resources._functional", "importlib.resources._itertools", "importlib.resources.abc", "importlib.resources.readers", "importlib.resources.simple", "importlib.simple", "importlib.util", "inspect", "io", "ipaddress", "itertools", "json", "json.decoder", "json.encoder", "json.scanner", "json.tool", "keyword", "linecache", "locale", "logging", "logging.config", "logging.handlers", "lzma", "mailbox", "marshal", "math", "mimetypes", "mmap", "modulefinder", "msvcrt", "multiprocessing", "multiprocessing.connection", "multiprocessing.context", "multiprocessing.dummy", "multiprocessing.dummy.connection", "multiprocessing.forkserver", "multiprocessing.heap", "multiprocessing.managers", "multiprocessing.pool", "multiprocessing.popen_fork", "multiprocessing.popen_forkserver", "multiprocessing.popen_spawn_posix", "multiprocessing.popen_spawn_win32", "multiprocessing.process", "multiprocessing.queues", "multiprocessing.reduction", "multiprocessing.resource_sharer", "multiprocessing.resource_tracker", "multiprocessing.shared_memory", "multiprocessing.sharedctypes", "multiprocessing.spawn", "multiprocessing.synchronize", "multiprocessing.util", "netrc", "nt", "ntpath", "nturl2path", "numbers", "opcode", "operator", "optparse", "os", "os.path", "pathlib", "pathlib._abc", "pathlib._local", "pdb", "pickle", "pickletools", "pkgutil", "platform", "plistlib", "poplib", "posix", "posixpath", "pprint", "profile", "pstats", "pty", "pwd", "py_compile", "pyclbr", "pydoc", "pydoc_data", "pydoc_data.topics", "pyexpat", "pyexpat.errors", "pyexpat.model", "queue", "quopri", "random", "re", "re._casefix", "re._compiler", "re._constants", "re._parser", "readline", "reprlib", "resource", "rlcompleter", "runpy", "sched", "secrets", "select", "selectors", "shelve", "shlex", "shutil", "signal", "site", "smtplib", "socket", "socketserver", "sqlite3", "sqlite3.__main__", "sqlite3.dbapi2", "sqlite3.dump", "sre_compile", "sre_constants", "sre_parse", "ssl", "stat", "statistics", "string", "stringprep", "struct", "subprocess", "symtable", "sys", "sysconfig", "sysconfig.__main__", "syslog", "tabnanny", "tarfile", "tempfile", "termios", "textwrap", "this", "threading", "time", "timeit", "tkinter", "tkinter.__main__", "tkinter.colorchooser", "tkinter.commondialog", "tkinter.constants", "tkinter.dialog", "tkinter.dnd", "tkinter.filedialog", "tkinter.font", "tkinter.messagebox", "tkinter.scrolledtext", "tkinter.simpledialog", "tkinter.ttk", "token", "tokenize", "tomllib", "tomllib._parser", "tomllib._re", "tomllib._types", "trace", "traceback", "tracemalloc", "tty", "turtle", "turtledemo", "turtledemo.__main__", "turtledemo.bytedesign", "turtledemo.chaos", "turtledemo.clock", "turtledemo.colormixer", "turtledemo.forest", "turtledemo.fractalcurves", "turtledemo.lindenmayer", "turtledemo.minimal_hanoi", "turtledemo.nim", "turtledemo.paint", "turtledemo.peace", "turtledemo.penrose", "turtledemo.planet_and_moon", "turtledemo.rosette", "turtledemo.round_dance", "turtledemo.sorting_animate", "turtledemo.tree", "turtledemo.two_canvases", "turtledemo.yinyang", "types", "typing", "unicodedata", "unittest", "unittest.__main__", "unittest._log", "unittest.async_case", "unittest.case", "unittest.loader", "unittest.main", "unittest.mock", "unittest.result", "unittest.runner", "unittest.signals", "unittest.suite", "unittest.util", "urllib", "urllib.error", "urllib.parse", "urllib.request", "urllib.response", "urllib.robotparser", "uuid", "venv", "venv.__main__", "warnings", "wave", "weakref", "webbrowser", "winreg", "winsound", "wsgiref", "wsgiref.handlers", "wsgiref.headers", "wsgiref.simple_server", "wsgiref.types", "wsgiref.util", "wsgiref.validate", "xml", "xml.dom", "xml.dom.NodeFilter", "xml.dom.domreg", "xml.dom.expatbuilder", "xml.dom.minicompat", "xml.dom.minidom", "xml.dom.pulldom", "xml.dom.xmlbuilder", "xml.etree", "xml.etree.ElementInclude", "xml.etree.ElementPath", "xml.etree.ElementTree", "xml.etree.cElementTree", "xml.parsers", "xml.parsers.expat", "xml.sax", "xml.sax._exceptions", "xml.sax.expatreader", "xml.sax.handler", "xml.sax.saxutils", "xml.sax.xmlreader", "xmlrpc", "xmlrpc.client", "xmlrpc.server", "zipapp", "zipfile", "zipfile.__main__", "zipfile._path", "zipfile._path.glob", "zipimport", "zlib", "zoneinfo", "zoneinfo._common", "zoneinfo._tzpath", "zoneinfo._zoneinfo" ] } ================================================ FILE: src/scripts/generate_python_stdlib_list/script.sh ================================================ #!/bin/bash # script.sh # This script dynamically retrieves the list of major Python versions from Docker Hub, # handling pagination, and uses a single Docker container (python:latest) to generate the # standard library module lists for each version using the stdlib-list package. # The output is written to a single JSON file where each key is a Python version. # Ensure required tools are available command -v curl >/dev/null 2>&1 || { echo >&2 "curl is required but it's not installed. Aborting."; exit 1; } command -v jq >/dev/null 2>&1 || { echo >&2 "jq is required but it's not installed. Aborting."; exit 1; } # Initial API URL with max page_size API_URL="https://registry.hub.docker.com/v2/repositories/library/python/tags/?page_size=100" echo "Querying Docker Hub for Python image tags..." all_tags="" # Loop over pages until there is no next URL. while [ -n "$API_URL" ]; do echo "Fetching tags from: $API_URL" response=$(curl -s "$API_URL") # Append tags from this page. page_tags=$(echo "$response" | jq -r '.results[].name') all_tags+="$page_tags"$'\n' # Get next URL. API_URL=$(echo "$response" | jq -r '.next') if [ "$API_URL" == "null" ]; then API_URL="" fi done # Filter out tags that match the major version pattern (e.g., 3.8, 3.9) versions=$(echo "$all_tags" | grep -E '^[0-9]+\.[0-9]+$' | sort -V | uniq) if [ -z "$versions" ]; then echo "No major Python versions found. Exiting." exit 1 fi echo "Found Python versions:" echo "$versions" # Convert the list to a space-separated string for environment passing. versions_list=$(echo "$versions" | tr '\n' ' ') echo "Using versions: $versions_list" # Use a single Docker container (python:latest) to generate the stdlib lists. # Pass the versions list to the container via the PYTHON_VERSIONS environment variable. docker run --rm -e PYTHON_VERSIONS="$versions_list" -v "$(dirname "$0")/output.json":/output.json python:latest bash -c ' pip install stdlib-list > /dev/null 2>&1 && python - << "EOF" import json import os from stdlib_list import stdlib_list # Retrieve the space-separated versions from the environment variable. versions = os.environ.get("PYTHON_VERSIONS", "").split() if not versions: print("No Python versions provided.") exit(1) result = {} for v in versions: try: modules = stdlib_list(v) except Exception as e: modules = {"error": str(e)} result[v] = modules # Write the result to a JSON file. with open("output.json", "w") as f: json.dump(result, f, indent=2) EOF ' echo "Standard library list generated in the 'output.json' file." ================================================ FILE: src/symbolExtractor/c/index.ts ================================================ import type { ExtractedFilesMap } from "../types.ts"; import { CExtractor } from "../../languagePlugins/c/extractor/index.ts"; import type { DependencyManifest } from "../../manifest/dependencyManifest/types.ts"; import type { z } from "zod"; import type { localConfigSchema } from "../../cli/middlewares/napiConfig.ts"; export function extractCSymbols( files: Map, dependencyManifest: DependencyManifest, symbolsToExtract: Map }>, _napiConfig: z.infer, ): ExtractedFilesMap { console.time(`Extracted ${symbolsToExtract.size} symbol(s)`); const extractor = new CExtractor(files, dependencyManifest, _napiConfig); const extractedFiles = extractor.extractSymbols(symbolsToExtract); console.timeEnd(`Extracted ${symbolsToExtract.size} symbol(s)`); return extractedFiles; } ================================================ FILE: src/symbolExtractor/csharp/index.ts ================================================ import type { z } from "zod"; import { join, SEPARATOR } from "@std/path"; import type { localConfigSchema } from "../../cli/middlewares/napiConfig.ts"; import type { ExtractedFilesMap } from "../types.ts"; import { CSharpExtractor, type ExtractedFile, } from "../../languagePlugins/csharp/extractor/index.ts"; import type { DependencyManifest } from "../../manifest/dependencyManifest/types.ts"; import type { DotNetProject } from "../../languagePlugins/csharp/projectMapper/index.ts"; import type Parser from "tree-sitter"; import { csharpParser } from "../../helpers/treeSitter/parsers.ts"; import { CSharpNamespaceMapper } from "../../languagePlugins/csharp/namespaceMapper/index.ts"; import { CSharpProjectMapper } from "../../languagePlugins/csharp/projectMapper/index.ts"; import { CSharpUsingResolver, ExternalSymbol, InternalSymbol, } from "../../languagePlugins/csharp/usingResolver/index.ts"; import { CSharpInvocationResolver } from "../../languagePlugins/csharp/invocationResolver/index.ts"; /** * Extracts C# symbols from the given files. * @param files - A map of file paths to their content. * @param dependencyManifest - The dependency manifest. * @param symbolsToExtract - A map of symbols to extract, where the key is the symbol name and the value is an object containing the file path and a set of symbols. * @param _napiConfig - The NAPI configuration. * @returns - A map of extracted files, where the key is the file path and the value is an object containing the file path and content. */ export function extractCSharpSymbols( files: Map, dependencyManifest: DependencyManifest, symbolsToExtract: Map }>, _napiConfig: z.infer, ): ExtractedFilesMap { console.time(`Extracted ${symbolsToExtract.size} symbol(s)`); const extractor = new CSharpExtractor(files, dependencyManifest); const extractedFiles: ExtractedFile[] = []; const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); const csprojFiles = new Map(); // Extract symbols from the files for (const symbolSet of symbolsToExtract.values()) { for (const symbol of symbolSet.symbols) { const extractedFile = extractor.extractSymbolFromFile( symbolSet.filePath, symbol, ); if (extractedFile) { // Add the extracted file to the list of extracted files extractedFiles.push(...extractedFile); // Add the extracted file to the parsed files map // This is used to create a representation of the exported project // It will help us to find which namespaces cannot be used in using directives anymore for (const file of extractedFile) { const filePath = file.name; if (!parsedFiles.has(filePath)) { parsedFiles.set(filePath, { path: filePath, rootNode: csharpParser.parse(extractor.getContent(file)).rootNode, }); } // Add the csproj file to the csproj files map const subproject = file.subproject; if (!csprojFiles.has(subproject.csprojPath)) { csprojFiles.set(subproject.csprojPath, { path: subproject.csprojPath, content: subproject.csprojContent, }); const globalUsingsPath = join( subproject.rootFolder, "GlobalUsings.cs", ); const globalUsingsContent = extractor.generateGlobalUsings( subproject, ); if (!parsedFiles.has(globalUsingsPath)) { parsedFiles.set(globalUsingsPath, { path: globalUsingsPath, rootNode: csharpParser.parse(globalUsingsContent).rootNode, }); } } } } } } // For each extracted file, check if the using directives are still valid and useful const nsMapper = new CSharpNamespaceMapper(parsedFiles); const pjMapper = new CSharpProjectMapper(csprojFiles); const usingResolver = new CSharpUsingResolver(nsMapper, pjMapper); const invocationResolver = new CSharpInvocationResolver(nsMapper, pjMapper); for (const extractedFile of extractedFiles) { const imports = extractedFile.imports; const invocations = invocationResolver.getInvocationsFromFile( extractedFile.name, ); for (const importDirective of imports) { const resolvedInNewFile = usingResolver.resolveUsingDirective( importDirective, ); const resolvedInOldFile = extractor.usingResolver.resolveUsingDirective( importDirective, ); if ( (resolvedInNewFile instanceof ExternalSymbol && resolvedInOldFile instanceof InternalSymbol) || !invocationResolver.isUsingUseful(invocations, importDirective) ) { extractedFile.imports = extractedFile.imports.filter( (imp) => imp !== importDirective, ); } } } // Actually extract the files const subprojects: DotNetProject[] = []; const extractedFilesMap: ExtractedFilesMap = new Map(); for (const extractedFile of extractedFiles) { const { subproject, namespace, name } = extractedFile; if (!subprojects.includes(subproject)) { subprojects.push(subproject); } // File path for the extracted file const fakeprojectpath = subproject.name.split(".").join(SEPARATOR); const spindex = namespace.split(".").indexOf(subproject.name); const nspath = namespace .split(".") .slice(spindex !== -1 ? spindex : 0) .join(SEPARATOR) .replace(fakeprojectpath, subproject.name); const key = join( spindex !== -1 || nspath.startsWith(subproject.name) ? "" : subproject.name, nspath, `${name}.cs`, ); if (!extractedFilesMap.has(key)) { const filecontent = extractor.getContent(extractedFile); // Add the extracted file to the output map extractedFilesMap.set(key, { path: key, content: filecontent, }); } } // Add the .csproj and GlobalUsings.cs files for each subproject for (const subproject of subprojects) { const projectPath = join(subproject.name, `${subproject.name}.csproj`); const globalUsingPath = join(subproject.name, "GlobalUsings.cs"); if (!extractedFilesMap.has(projectPath)) { // Add the project files to the output map extractedFilesMap.set(projectPath, { path: projectPath, content: subproject.csprojContent, }); extractedFilesMap.set(globalUsingPath, { path: globalUsingPath, content: extractor.generateGlobalUsings(subproject), }); } } console.timeEnd(`Extracted ${symbolsToExtract.size} symbol(s)`); return extractedFilesMap; } ================================================ FILE: src/symbolExtractor/index.ts ================================================ import type { ExtractedFilesMap } from "./types.ts"; import { extractPythonSymbols } from "./python/index.ts"; import { extractCSharpSymbols } from "./csharp/index.ts"; import { extractCSymbols } from "./c/index.ts"; import { extractJavaSymbols } from "./java/index.ts"; import type { localConfigSchema } from "../cli/middlewares/napiConfig.ts"; import type z from "zod"; import type { DependencyManifest } from "../manifest/dependencyManifest/types.ts"; const handlerMap: Record< string, ( files: Map, dependencyManifest: DependencyManifest, symbolsToExtract: Map }>, napiConfig: z.infer, ) => ExtractedFilesMap > = { python: extractPythonSymbols, "c-sharp": extractCSharpSymbols, c: extractCSymbols, java: extractJavaSymbols, }; export class UnsupportedLanguageError extends Error { constructor(language: string) { const supportedLanguages = Object.keys(handlerMap).join(", "); super( `Unsupported language: ${language}. Supported languages: ${supportedLanguages}`, ); } } export function extractSymbols( files: Map, dependencyManifest: DependencyManifest, symbolsToExtract: Map }>, napiConfig: z.infer, ): ExtractedFilesMap { const languageName = napiConfig.language; const handler = handlerMap[languageName]; if (!handler) { throw new UnsupportedLanguageError(languageName); } const extractedFiles = handler( files, dependencyManifest, symbolsToExtract, napiConfig, ); return extractedFiles; } ================================================ FILE: src/symbolExtractor/java/index.ts ================================================ import type { ExtractedFilesMap } from "../types.ts"; import { JavaExtractor } from "../../languagePlugins/java/extractor/index.ts"; import type { DependencyManifest } from "../../manifest/dependencyManifest/types.ts"; import type { z } from "zod"; import type { localConfigSchema } from "../../cli/middlewares/napiConfig.ts"; export function extractJavaSymbols( files: Map, dependencyManifest: DependencyManifest, symbolsToExtract: Map }>, _napiConfig: z.infer, ): ExtractedFilesMap { console.time(`Extracted ${symbolsToExtract.size} symbol(s)`); const extractor = new JavaExtractor(files, dependencyManifest); const extractedFiles = extractor.extractSymbols(symbolsToExtract); console.timeEnd(`Extracted ${symbolsToExtract.size} symbol(s)`); return extractedFiles; } ================================================ FILE: src/symbolExtractor/python/index.ts ================================================ import type z from "zod"; import type { localConfigSchema } from "../../cli/middlewares/napiConfig.ts"; import type { ExtractedFilesMap } from "../types.ts"; import type Parser from "tree-sitter"; import { pythonParser } from "../../helpers/treeSitter/parsers.ts"; import type { DependencyManifest } from "../../manifest/dependencyManifest/types.ts"; import { PythonExportExtractor } from "../../languagePlugins/python/exportExtractor/index.ts"; import { PythonImportExtractor } from "../../languagePlugins/python/importExtractor/index.ts"; import { PythonModuleResolver } from "../../languagePlugins/python/moduleResolver/index.ts"; import { PythonItemResolver } from "../../languagePlugins/python/itemResolver/index.ts"; import { PythonUsageResolver } from "../../languagePlugins/python/usageResolver/index.ts"; import { PythonSymbolExtractor } from "../../languagePlugins/python/symbolExtractor/index.ts"; export function extractPythonSymbols( files: Map, dependencyManifest: DependencyManifest, symbolsToExtract: Map }>, napiConfig: z.infer, ): ExtractedFilesMap { const pythonVersion = napiConfig.python?.version; if (!pythonVersion) { throw new Error("Python version is required"); } const parsedFiles = new Map< string, { path: string; rootNode: Parser.SyntaxNode } >(); for (const { path, content } of files.values()) { const rootNode = pythonParser.parse(content, undefined, { bufferSize: content.length + 10, }).rootNode; parsedFiles.set(path, { path, rootNode }); } const exportExtractor = new PythonExportExtractor(pythonParser, parsedFiles); const importExtractor = new PythonImportExtractor(pythonParser, parsedFiles); const moduleResolver = new PythonModuleResolver( new Set(parsedFiles.keys()), pythonVersion, ); const itemResolver = new PythonItemResolver( exportExtractor, importExtractor, moduleResolver, ); const usageResolver = new PythonUsageResolver(pythonParser, exportExtractor); const symbolExtractor = new PythonSymbolExtractor( pythonParser, parsedFiles, exportExtractor, importExtractor, moduleResolver, itemResolver, usageResolver, dependencyManifest, ); const extractedFiles = symbolExtractor.extractSymbol(symbolsToExtract); return extractedFiles; } ================================================ FILE: src/symbolExtractor/types.ts ================================================ export type ExtractedFilesMap = Map< string, { path: string; content: string; } >; ================================================ FILE: viewer/.gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* node_modules dist dist-ssr *.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea .DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? ================================================ FILE: viewer/README.md ================================================ # React + TypeScript + Vite This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. Currently, two official plugins are available: - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) ## React Compiler The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). ## Expanding the ESLint configuration If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: ```js export default defineConfig([ globalIgnores(['dist']), { files: ['**/*.{ts,tsx}'], extends: [ // Other configs... // Remove tseslint.configs.recommended and replace with this tseslint.configs.recommendedTypeChecked, // Alternatively, use this for stricter rules tseslint.configs.strictTypeChecked, // Optionally, add this for stylistic rules tseslint.configs.stylisticTypeChecked, // Other configs... ], languageOptions: { parserOptions: { project: ['./tsconfig.node.json', './tsconfig.app.json'], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, ]) ``` You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: ```js // eslint.config.js import reactX from 'eslint-plugin-react-x' import reactDom from 'eslint-plugin-react-dom' export default defineConfig([ globalIgnores(['dist']), { files: ['**/*.{ts,tsx}'], extends: [ // Other configs... // Enable lint rules for React reactX.configs['recommended-typescript'], // Enable lint rules for React DOM reactDom.configs.recommended, ], languageOptions: { parserOptions: { project: ['./tsconfig.node.json', './tsconfig.app.json'], tsconfigRootDir: import.meta.dirname, }, // other options... }, }, ]) ``` ================================================ FILE: viewer/index.html ================================================ NanoAPI Viewer
================================================ FILE: viewer/src/App.tsx ================================================ import { BrowserRouter, Routes, Route } from "react-router-dom"; import { ThemeProvider } from "./contexts/ThemeProvider"; import { TooltipProvider } from "./components/shadcn/Tooltip"; import ManifestList from "./pages/ManifestList"; import ManifestView from "./pages/ManifestView"; export default function App() { return ( } /> } /> ); } ================================================ FILE: viewer/src/api.ts ================================================ import type { DependencyManifest } from "./types/dependencyManifest"; import type { AuditManifest } from "./types/auditManifest"; export interface ManifestListItem { id: string; branch: string; commitSha: string; commitShaDate: string; createdAt: string; fileCount: number; } export interface ManifestEnvelope { id: string; branch: string; commitSha: string; commitShaDate: string; createdAt: string; manifest: DependencyManifest; } const BASE = "/api"; export async function fetchManifests(): Promise { const res = await fetch(`${BASE}/manifests`); return res.json(); } export async function fetchManifest(id: string): Promise { const res = await fetch(`${BASE}/manifests/${id}`); if (!res.ok) throw new Error("Manifest not found"); return res.json(); } export async function fetchAudit(id: string): Promise { const res = await fetch(`${BASE}/manifests/${id}/audit`); if (!res.ok) throw new Error("Audit not found"); return res.json(); } ================================================ FILE: viewer/src/components/DependencyVisualizer/DependencyVisualizer.tsx ================================================ import { useState } from "react"; import { useSearchParams } from "react-router-dom"; import type { DependencyManifest } from "../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../types/auditManifest.ts"; import { SidebarProvider, SidebarTrigger } from "../shadcn/Sidebar.tsx"; import { FileExplorerSidebar, type ExplorerNodeData, } from "./components/FileExplorerSidebar.tsx"; import BreadcrumbNav from "./components/BreadcrumbNav.tsx"; import ProjectVisualizer from "./visualizers/ProjectVisualizer.tsx"; import FileVisualizer from "./visualizers/FileVisualizer.tsx"; import SymbolVisualizer from "./visualizers/SymbolVisualizer.tsx"; export default function DependencyVisualizer(props: { manifestId: string; dependencyManifest: DependencyManifest; auditManifest: AuditManifest; }) { const [searchParams] = useSearchParams(); const [highlightedCytoscapeRef, setHighlightedCytoscapeRef] = useState< { filePath: string; symbolId: string | undefined } | undefined >(undefined); function handleHighlight(node: ExplorerNodeData) { if (!node.fileId) return; const newRef = { filePath: node.fileId, symbolId: node.symbolId, }; if ( highlightedCytoscapeRef?.filePath === newRef.filePath && highlightedCytoscapeRef?.symbolId === newRef.symbolId ) { setHighlightedCytoscapeRef(undefined); } else { setHighlightedCytoscapeRef(newRef); } } function toDetails(node: ExplorerNodeData) { if (node.symbolId && node.fileId) { const p = new URLSearchParams(searchParams); p.set("fileId", node.fileId); p.set("instanceId", node.symbolId); return `?${p.toString()}`; } else if (node.fileId) { const p = new URLSearchParams(searchParams); p.set("fileId", node.fileId); p.delete("instanceId"); return `?${p.toString()}`; } else { const p = new URLSearchParams(searchParams); p.delete("fileId"); p.delete("instanceId"); return `?${p.toString()}`; } } const fileId = searchParams.get("fileId"); const instanceId = searchParams.get("instanceId"); return (
{ const p = new URLSearchParams(searchParams); p.delete("fileId"); p.delete("instanceId"); return `?${p.toString()}`; }} fileId={fileId} toFileIdLink={(fId) => { const p = new URLSearchParams(searchParams); p.set("fileId", fId); p.delete("instanceId"); return `?${p.toString()}`; }} instanceId={instanceId} toInstanceIdLink={(fId, iId) => { const p = new URLSearchParams(searchParams); p.set("fileId", fId); p.set("instanceId", iId); return `?${p.toString()}`; }} />
{fileId && instanceId ? ( ) : fileId ? ( ) : ( )}
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/BreadcrumbNav.tsx ================================================ import { Link } from "react-router-dom"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbSeparator, } from "../../shadcn/Breadcrumb.tsx"; export default function BreadcrumbNav(props: { toProjectLink: () => string; fileId: string | null; toFileIdLink: (fileId: string) => string; instanceId: string | null; toInstanceIdLink: (fileId: string, instanceId: string) => string; }) { return ( Project {props.fileId && ( <> {props.fileId} {props.instanceId && ( <> {props.instanceId} )} )} ); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/DisplayNameWithTooltip.tsx ================================================ import { Tooltip, TooltipContent, TooltipTrigger, } from "../../shadcn/Tooltip.tsx"; export default function DisplayNameWithTooltip(props: { name: string; maxChar?: number; truncateBefore?: boolean; }) { const maxChar = props.maxChar || 30; if (props.name.length > maxChar) { const displayedName = props.truncateBefore ? "..." + props.name.slice(0, maxChar) : props.name.slice(0, maxChar) + "..."; return ( {displayedName}
{props.name}
); } return {props.name}; } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/FileExplorerSidebar.tsx ================================================ import { useEffect, useMemo, useState } from "react"; import { Link } from "react-router-dom"; import type { DependencyManifest } from "../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../types/auditManifest.ts"; import { Sidebar, SidebarContent, SidebarGroup, SidebarHeader, SidebarRail, } from "../../shadcn/Sidebar.tsx"; import { Button } from "../../shadcn/Button.tsx"; import { Input } from "../../shadcn/Input.tsx"; import { Tooltip, TooltipContent, TooltipTrigger, } from "../../shadcn/Tooltip.tsx"; import { ChevronDown, ChevronRight, Code, File, FolderClosed, FolderOpen, ScanEye, Search, SearchCode, X, } from "lucide-react"; import { ScrollArea, ScrollBar } from "../../shadcn/Scrollarea.tsx"; import DisplayNameWithTooltip from "./DisplayNameWithTooltip.tsx"; export interface ExplorerNodeData { id: string; displayName: string; fileId?: string; symbolId?: string; children: Map; } function buildExplorerTree( dependencyManifest: DependencyManifest, filteredSymbols: { fileId: string; symbolId: string }[], ): ExplorerNodeData | undefined { const getExplorerNodeId = (filePath: string, instanceId?: string) => { if (instanceId) { return `${filePath}#${instanceId}`; } return filePath; }; const root: ExplorerNodeData = { id: "root", displayName: "Project", children: new Map(), }; const filteredSymbolsSet = new Set( filteredSymbols.map((s) => `${s.fileId}#${s.symbolId}`), ); const filesWithFilteredSymbols = new Set( filteredSymbols.map((s) => s.fileId), ); const shouldShowAll = filteredSymbols.length === 0; let hasMatchingNodes = false; for (const fileDependencyManifest of Object.values(dependencyManifest)) { const filePath = fileDependencyManifest.filePath; const fileShouldBeIncluded = shouldShowAll || filesWithFilteredSymbols.has(filePath); if (!fileShouldBeIncluded) { continue; } hasMatchingNodes = true; const parts = filePath.split("/"); let currentNode: ExplorerNodeData = root; for (const part of parts) { const id = getExplorerNodeId(part); if (!currentNode.children.has(id)) { currentNode.children.set(id, { id: id, displayName: part, children: new Map(), }); } currentNode = currentNode.children.get(id)!; } currentNode.fileId = getExplorerNodeId(filePath); for (const instanceId of Object.keys(fileDependencyManifest.symbols)) { const symbolKey = `${filePath}#${instanceId}`; if (shouldShowAll || filteredSymbolsSet.has(symbolKey)) { const id = getExplorerNodeId(filePath, instanceId); currentNode.children.set(id, { id: id, displayName: instanceId, fileId: filePath, symbolId: instanceId, children: new Map(), }); } } } if (!shouldShowAll && !hasMatchingNodes) { return undefined; } const flattenTree = (node: ExplorerNodeData): ExplorerNodeData => { if (node.children.size > 0) { const flattenedChildren = new Map(); const folders: Array<[string, ExplorerNodeData]> = []; const files: Array<[string, ExplorerNodeData]> = []; for (const [id, child] of node.children) { const flattenedChild = flattenTree(child); if (flattenedChild.fileId) { files.push([id, flattenedChild]); } else { folders.push([id, flattenedChild]); } } for (const [id, folder] of folders) { flattenedChildren.set(id, folder); } for (const [id, file] of files) { flattenedChildren.set(id, file); } node.children = flattenedChildren; } while (node.children.size === 1) { const childEntry = Array.from(node.children.entries())[0]; const child = childEntry[1]; if (child.fileId) { break; } node.displayName = `${node.displayName}/${child.displayName}`; node.id = child.id; node.children = child.children; } return node; }; return flattenTree(root); } function computeFilteredSymbols( dependencyManifest: DependencyManifest, searchTerm: string, ): { fileId: string; symbolId: string }[] { const term = searchTerm.toLowerCase(); const results: { fileId: string; symbolId: string }[] = []; for (const file of Object.values(dependencyManifest)) { const filePathMatch = file.filePath.toLowerCase().includes(term); for (const symbolId of Object.keys(file.symbols)) { if (filePathMatch || symbolId.toLowerCase().includes(term)) { results.push({ fileId: file.filePath, symbolId }); } } } return results; } export function FileExplorerSidebar(props: { dependencyManifest: DependencyManifest; auditManifest: AuditManifest; onHighlightInCytoscape: (node: ExplorerNodeData) => void; toDetails: (node: ExplorerNodeData) => string; }) { const [searchTerm, setSearchTerm] = useState(""); const [activeSearch, setActiveSearch] = useState(""); const filteredSymbols = useMemo(() => { if (!activeSearch) return []; return computeFilteredSymbols(props.dependencyManifest, activeSearch); }, [props.dependencyManifest, activeSearch]); const [explorerTree, setExplorerTree] = useState(); useEffect(() => { const tree = buildExplorerTree(props.dependencyManifest, filteredSymbols); setExplorerTree(tree); }, [props.dependencyManifest, filteredSymbols]); function onSubmitSearch(e: React.FormEvent) { e.preventDefault(); if (searchTerm.trim()) { setActiveSearch(searchTerm.trim()); } } function onClearSearch() { setSearchTerm(""); setActiveSearch(""); } return ( logo
NanoAPI
setSearchTerm(e.target.value)} placeholder="Search files & symbols" /> {activeSearch && ( )}
{!explorerTree ? (
No matching files found
) : ( )}
); } function ExplorerNode(props: { node: ExplorerNodeData; level: number; onHighlightInCytoscape: (node: ExplorerNodeData) => void; toDetails: (node: ExplorerNodeData) => string; }) { const [showChildren, setShowChildren] = useState(false); const type: "folder" | "file" | "symbol" = props.node.symbolId ? "symbol" : props.node.fileId ? "file" : "folder"; return (
{(() => { switch (type) { case "folder": return ( ); case "file": return (
Highlight in graph View graph for this file
); case "symbol": return (
Highlight in graph View graph for this symbol
); } })()} {showChildren && Array.from(props.node.children.values()).map((child) => ( ))}
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/SymbolExtractionDialog.tsx ================================================ import { useState } from "react"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "../../shadcn/Dialog.tsx"; import { Button } from "../../shadcn/Button.tsx"; import { Card, CardContent, CardHeader, CardTitle, } from "../../shadcn/Card.tsx"; import { Alert, AlertDescription, AlertTitle } from "../../shadcn/Alert.tsx"; import { Separator } from "../../shadcn/Separator.tsx"; import { Code, Copy, Info, Terminal } from "lucide-react"; import { ScrollArea } from "../../shadcn/Scrollarea.tsx"; export default function SymbolExtractionDialog(props: { manifestId: string; children: React.ReactNode; filePath: string; symbolIds: string[]; }) { const [open, setOpen] = useState(false); const [copied, setCopied] = useState(false); const generateCommand = () => { const symbolOptions = props.symbolIds.map((symbolId) => { return `--symbol="${props.filePath}|${symbolId}"`; }); return `napi extract --manifestId=${props.manifestId} ${symbolOptions.join(" ")}`; }; const copyToClipboard = async () => { try { await navigator.clipboard.writeText(generateCommand()); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error("Failed to copy to clipboard:", err); } }; return ( {props.children} Extract Symbol(s) via CLI Use the napi CLI to extract symbols from your program.
Prerequisites
Ensure you have a napi manifest generated locally:
napi generate
Command
{generateCommand()}
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/contextMenu/FileContextMenu.tsx ================================================ import { Link, useSearchParams } from "react-router-dom"; import type { DependencyManifest } from "../../../../types/dependencyManifest.ts"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "../../../shadcn/Dropdownmenu.tsx"; import { PanelRight, SearchCode } from "lucide-react"; import DisplayNameWithTooltip from "../DisplayNameWithTooltip.tsx"; export default function FileContextMenu(props: { context: | { position: { x: number; y: number }; fileDependencyManifest: DependencyManifest[string]; } | undefined; onClose: () => void; onOpenDetails: (filePath: string) => void; }) { const [searchParams] = useSearchParams(); function getToFileLink(filePath: string) { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.delete("instanceId"); return `?${newSearchParams.toString()}`; } return (
props.onClose()} > { props.context?.fileDependencyManifest && props.onOpenDetails( props.context.fileDependencyManifest.filePath, ); props.onClose(); }} >
Show details
Inspect symbols
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/contextMenu/SymbolContextMenu.tsx ================================================ import { Link, useSearchParams } from "react-router-dom"; import type { DependencyManifest } from "../../../../types/dependencyManifest.ts"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "../../../shadcn/Dropdownmenu.tsx"; import { PanelRight, SearchCode } from "lucide-react"; import DisplayNameWithTooltip from "../DisplayNameWithTooltip.tsx"; export default function SymbolContextMenu(props: { context: | { position: { x: number; y: number }; fileDependencyManifest: DependencyManifest[string]; symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; } | undefined; onClose: () => void; onOpenDetails: (filePath: string, symbolId: string) => void; }) { const [searchParams] = useSearchParams(); function getToSymbolLink(filePath: string, symbolId: string) { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.set("instanceId", symbolId); return `?${newSearchParams.toString()}`; } return (
props.onClose()} > { props.context?.fileDependencyManifest && props.onOpenDetails( props.context.fileDependencyManifest.filePath, props.context.symbolDependencyManifest.id, ); props.onClose(); }} >
Show details
Inspect symbol
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/controls/ControlExtensions/FiltersExtension.tsx ================================================ import { type MouseEvent, useEffect, useState } from "react"; import { Tooltip, TooltipContent, TooltipTrigger, } from "../../../../shadcn/Tooltip.tsx"; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "../../../../shadcn/Dropdownmenu.tsx"; import { Button } from "../../../../shadcn/Button.tsx"; import { Funnel } from "lucide-react"; export default function FiltersExtension(props: { busy: boolean; currentFileName?: string; onFilterChange: ( showExternal: boolean, showVariables: boolean, showFunctions: boolean, showClasses: boolean, showStructs: boolean, showEnums: boolean, showInterfaces: boolean, showRecords: boolean, showDelegates: boolean, ) => void; }) { const [showExternal, setShowExternal] = useState(true); const [showVariables, setShowVariables] = useState(true); const [showFunctions, setShowFunctions] = useState(true); const [showClasses, setShowClasses] = useState(true); const [showStructs, setShowStructs] = useState(true); const [showEnums, setShowEnums] = useState(true); const [showInterfaces, setShowInterfaces] = useState(true); const [showRecords, setShowRecords] = useState(true); const [showDelegates, setShowDelegates] = useState(true); useEffect(() => { props.onFilterChange( showExternal, showVariables, showFunctions, showClasses, showStructs, showEnums, showInterfaces, showRecords, showDelegates, ); }, [showExternal, showVariables, showFunctions, showClasses, showStructs, showEnums, showInterfaces, showRecords, showDelegates]); function handleFilterClick( e: MouseEvent, set: React.Dispatch>, ) { e.preventDefault(); set((prev) => !prev); } return ( Filters {[ { label: "Show external", checked: showExternal, set: setShowExternal }, { label: "Show variables", checked: showVariables, set: setShowVariables }, { label: "Show functions", checked: showFunctions, set: setShowFunctions }, { label: "Show classes", checked: showClasses, set: setShowClasses }, { label: "Show structs", checked: showStructs, set: setShowStructs }, { label: "Show enums", checked: showEnums, set: setShowEnums }, { label: "Show interfaces", checked: showInterfaces, set: setShowInterfaces }, { label: "Show records", checked: showRecords, set: setShowRecords }, { label: "Show delegates", checked: showDelegates, set: setShowDelegates }, ].map(({ label, checked, set }) => ( handleFilterClick(e, set)} > {label} ))} Hide or show specific elements in the graph. ); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/controls/ControlExtensions/GraphDepthExtension.tsx ================================================ import { useState } from "react"; import { Tooltip, TooltipContent, TooltipTrigger, } from "../../../../shadcn/Tooltip.tsx"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "../../../../shadcn/Dropdownmenu.tsx"; import { Button } from "../../../../shadcn/Button.tsx"; import { Settings2 } from "lucide-react"; import { Slider } from "../../../../shadcn/Slider.tsx"; import { Input } from "../../../../shadcn/Input.tsx"; import { Label } from "../../../../shadcn/Label.tsx"; export default function GraphDepthExtension(props: { busy: boolean; dependencyState: { depth: number; setDepth: (depth: number) => void; }; dependentState: { depth: number; setDepth: (depth: number) => void; }; }) { const { dependencyState, dependentState } = props; const [tempDependencyDepth, setTempDependencyDepth] = useState(dependencyState.depth); const [tempDependentDepth, setTempDependentDepth] = useState(dependentState.depth); function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (tempDependencyDepth !== dependencyState.depth) { dependencyState.setDepth(tempDependencyDepth); } if (tempDependentDepth !== dependentState.depth) { dependentState.setDepth(tempDependentDepth); } } return (
setTempDependencyDepth(value[0])} /> setTempDependencyDepth(parseInt(e.target.value, 10))} className="w-20" />
setTempDependentDepth(value[0])} /> setTempDependentDepth(parseInt(e.target.value, 10))} disabled={props.busy} className="w-20" />
Set the depth of the dependencies shown on the graph.
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/controls/ControlExtensions/MetricsExtension.tsx ================================================ import { Tooltip, TooltipContent, TooltipTrigger, } from "../../../../shadcn/Tooltip.tsx"; import { Button } from "../../../../shadcn/Button.tsx"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "../../../../shadcn/Dropdownmenu.tsx"; import type { Metric } from "../../../../../types/dependencyManifest.ts"; import { metricLinesCount, metricCodeLineCount, metricCharacterCount, metricCodeCharacterCount, metricDependencyCount, metricDependentCount, metricCyclomaticComplexity, } from "../../../../../types/dependencyManifest.ts"; export default function MetricsExtension(props: { busy: boolean; metricState: { metric: Metric | undefined; setMetric: (metric: Metric | undefined) => void; }; }) { const metric = props.metricState.metric; function getMetricLabel(metric: Metric | undefined) { switch (metric) { case metricLinesCount: return "Lines"; case metricCodeLineCount: return "Code Lines"; case metricCharacterCount: return "Chars"; case metricCodeCharacterCount: return "Code Chars"; case metricDependencyCount: return "Dependencies"; case metricDependentCount: return "Dependents"; case metricCyclomaticComplexity: return "Complexity"; default: return "None"; } } return ( {([ { metric: undefined, label: "No Metric" }, { metric: metricLinesCount, label: "Lines" }, { metric: metricCodeLineCount, label: "Code Lines" }, { metric: metricCharacterCount, label: "Total Characters" }, { metric: metricCodeCharacterCount, label: "Code Characters" }, { metric: metricDependencyCount, label: "Dependencies" }, { metric: metricDependentCount, label: "Dependents" }, { metric: metricCyclomaticComplexity, label: "Complexity" }, ] as { metric: Metric | undefined; label: string }[]).map((val) => ( props.metricState?.setMetric?.(val.metric)} > {val.label} ))} Select a metric to display on the graph ); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/controls/Controls.tsx ================================================ import type { ReactNode } from "react"; import { Button } from "../../../shadcn/Button.tsx"; import { Tooltip, TooltipContent, TooltipTrigger, } from "../../../shadcn/Tooltip.tsx"; import type { Core } from "cytoscape"; import { Focus, Network, ZoomIn, ZoomOut } from "lucide-react"; export default function Controls(props: { busy: boolean; cy: Core | undefined; onLayout: () => void; children?: ReactNode; }) { function handleFit() { if (!props.cy) return; const elements = props.cy.elements(); const padding = 10; props.cy.center(elements).fit(elements, padding); } function handleZoom(zoom: number) { if (!props.cy) return; const level = props.cy.zoom() * zoom; const x = props.cy.width() / 2; const y = props.cy.height() / 2; props.cy.zoom({ level, renderedPosition: { x, y } }); } return (
Fit to screen Reset layout Zoom out Zoom in {props.children}
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/detailsPanes/AlertBadge.tsx ================================================ import { Check, TriangleAlert } from "lucide-react"; export default function AlertBadge(props: { count: number }) { return (
{props.count > 0 ? ( <> {props.count} ) : }
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/detailsPanes/FileDetailsPane.tsx ================================================ import type { DependencyManifest } from "../../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../../types/auditManifest.ts"; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "../../../shadcn/Sheet.tsx"; import { Card, CardContent, CardHeader, CardTitle, } from "../../../shadcn/Card.tsx"; import { Code, File, SearchCode } from "lucide-react"; import { ScrollArea } from "../../../shadcn/Scrollarea.tsx"; import { Button } from "../../../shadcn/Button.tsx"; import { Link, useSearchParams } from "react-router-dom"; import DisplayNameWithTooltip from "../DisplayNameWithTooltip.tsx"; import Metrics from "./Metrics.tsx"; import AlertBadge from "./AlertBadge.tsx"; export default function FileDetailsPane(props: { context: | { manifestId: string; fileDependencyManifest: DependencyManifest[string]; fileAuditManifest: AuditManifest[string]; } | undefined; onClose: () => void; }) { const [searchParams] = useSearchParams(); function getToFileLink(filePath: string) { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.delete("instanceId"); return `?${newSearchParams.toString()}`; } return (
props.onClose()} >
File Metrics
Symbols ( {Object.keys( props.context?.fileDependencyManifest?.symbols || {}, ).length || 0} )
{Object.entries( Object.values( props.context?.fileDependencyManifest?.symbols || {}, ).reduce( (acc, symbol) => { acc[symbol.type] = (acc[symbol.type] || 0) + 1; return acc; }, {} as Record, ), ).map(([type, count]) => (
{type}
{count}
))}
{Object.values( props.context?.fileDependencyManifest?.symbols || {}, ).map((symbol) => (
))}
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/detailsPanes/Metrics.tsx ================================================ import type { DependencyManifest } from "../../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../../types/auditManifest.ts"; import { Alert, AlertDescription } from "../../../shadcn/Alert.tsx"; export default function Metrics(props: { dependencyManifest: | DependencyManifest[string] | DependencyManifest[string]["symbols"][string] | undefined; auditManifest: | AuditManifest[string] | AuditManifest[string]["symbols"][string] | undefined; }) { function metricToHumanString(metric: string) { switch (metric) { case "linesCount": return "Lines"; case "codeLineCount": return "Code Lines"; case "characterCount": return "Characters"; case "codeCharacterCount": return "Code Characters"; case "dependencyCount": return "Dependencies"; case "dependentCount": return "Dependents"; case "cyclomaticComplexity": return "Cyclomatic Complexity"; default: return metric; } } return (
{Object.entries(props.dependencyManifest?.metrics || {}).map(( [key, value], ) => (
{metricToHumanString(key)}
{value}
{(props.auditManifest?.alerts || {})?.[key] && ( {props.auditManifest?.alerts?.[key]?.message?.long} )}
))}
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/components/detailsPanes/SymbolDetailsPane.tsx ================================================ import type { DependencyManifest } from "../../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../../types/auditManifest.ts"; import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger, } from "../../../shadcn/Sheet.tsx"; import { Card, CardContent, CardHeader, CardTitle, } from "../../../shadcn/Card.tsx"; import { Code, File, SearchCode } from "lucide-react"; import { ScrollArea } from "../../../shadcn/Scrollarea.tsx"; import { Button } from "../../../shadcn/Button.tsx"; import { Link, useSearchParams } from "react-router-dom"; import DisplayNameWithTooltip from "../DisplayNameWithTooltip.tsx"; import Metrics from "./Metrics.tsx"; import AlertBadge from "./AlertBadge.tsx"; export default function SymbolDetailsPane(props: { context: | { manifestId: string; fileDependencyManifest: DependencyManifest[string]; symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; fileAuditManifest: AuditManifest[string]; symbolAuditManifest: AuditManifest[string]["symbols"][string]; } | undefined; onClose: () => void; }) { const [searchParams] = useSearchParams(); function getToSymbolLink(filePath: string, symbolId: string) { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.set("instanceId", symbolId); return `?${newSearchParams.toString()}`; } function getToFileLink(filePath: string) { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.delete("instanceId"); return `?${newSearchParams.toString()}`; } return (
props.onClose()} >
Symbol Metrics
File Metrics
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/visualizers/FileVisualizer.tsx ================================================ import { useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import Controls from "../components/controls/Controls.tsx"; import MetricsExtension from "../components/controls/ControlExtensions/MetricsExtension.tsx"; import FiltersExtension from "../components/controls/ControlExtensions/FiltersExtension.tsx"; import SymbolContextMenu from "../components/contextMenu/SymbolContextMenu.tsx"; import SymbolDetailsPane from "../components/detailsPanes/SymbolDetailsPane.tsx"; import { FileDependencyVisualizer } from "../../../cytoscape/fileDependencyVisualizer/index.ts"; import type { DependencyManifest, Metric } from "../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../types/auditManifest.ts"; import { useTheme } from "../../../contexts/ThemeProvider.tsx"; interface FileVisualizerProps { manifestId: string; dependencyManifest: DependencyManifest; auditManifest: AuditManifest; highlightedCytoscapeRef: | { filePath: string; symbolId: string | undefined } | undefined; fileId: string; } export default function FileVisualizer(props: FileVisualizerProps) { const navigate = useNavigate(); const { theme } = useTheme(); const [searchParams, setSearchParams] = useSearchParams(); const containerRef = useRef(null); const [busy, setBusy] = useState(true); const [fileVisualizer, setFileVisualizer] = useState< FileDependencyVisualizer | undefined >(undefined); const metricFromUrl = (searchParams.get("metric") || undefined) as | Metric | undefined; const [metric, setMetric] = useState(metricFromUrl); function handleMetricChange(newMetric: Metric | undefined) { if (newMetric) { searchParams.set("metric", newMetric); setSearchParams(searchParams); } else { searchParams.delete("metric"); setSearchParams(searchParams); } setMetric(newMetric); } const [contextMenu, setContextMenu] = useState< | { position: { x: number; y: number }; fileDependencyManifest: DependencyManifest[string]; symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; } | undefined >(undefined); const [detailsPane, setDetailsPane] = useState< | { manifestId: string; fileDependencyManifest: DependencyManifest[string]; symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; fileAuditManifest: AuditManifest[string]; symbolAuditManifest: AuditManifest[string]["symbols"][string]; } | undefined >(undefined); useEffect(() => { setBusy(true); const visualizer = new FileDependencyVisualizer( containerRef.current as HTMLElement, props.fileId, props.dependencyManifest, props.auditManifest, { theme, defaultMetric: metric, onAfterNodeRightClick: (value: { position: { x: number; y: number }; filePath: string; symbolId: string; }) => { const fileDependencyManifest = props.dependencyManifest[value.filePath]; const symbolDependencyManifest = fileDependencyManifest.symbols[value.symbolId]; setContextMenu({ position: value.position, fileDependencyManifest, symbolDependencyManifest, }); }, onAfterNodeDblClick: (filePath: string, symbolId: string) => { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.set("instanceId", symbolId); navigate(`?${newSearchParams.toString()}`); }, }, ); setFileVisualizer(visualizer); setBusy(false); return () => { visualizer?.cy.destroy(); setFileVisualizer(undefined); }; }, [props.dependencyManifest, props.auditManifest, props.fileId]); useEffect(() => { if (fileVisualizer) { fileVisualizer.setTargetMetric(metric); } }, [metric]); useEffect(() => { if (fileVisualizer) { if (props.highlightedCytoscapeRef) { fileVisualizer.highlightNode(props.highlightedCytoscapeRef); } else { fileVisualizer.unhighlightNodes(); } } }, [props.highlightedCytoscapeRef]); useEffect(() => { if (fileVisualizer) { fileVisualizer.updateTheme(theme); } }, [theme]); function handleFilterChange( showExternal: boolean, showVariables: boolean, showFunctions: boolean, showClasses: boolean, showStructs: boolean, showEnums: boolean, showInterfaces: boolean, showRecords: boolean, showDelegates: boolean, ) { if (fileVisualizer) { fileVisualizer.filterNodes( showExternal, showVariables, showFunctions, showClasses, showStructs, showEnums, showInterfaces, showRecords, showDelegates, ); } } return (
fileVisualizer?.layoutGraph(fileVisualizer.cy)} >
setContextMenu(undefined)} onOpenDetails={(filePath, symbolId) => { const fileDependencyManifest = props.dependencyManifest[filePath]; const symbolDependencyManifest = fileDependencyManifest.symbols[symbolId]; const fileAuditManifest = props.auditManifest[filePath]; const symbolAuditManifest = fileAuditManifest.symbols[symbolId]; setDetailsPane({ manifestId: props.manifestId, fileDependencyManifest, symbolDependencyManifest, fileAuditManifest, symbolAuditManifest, }); }} /> setDetailsPane(undefined)} />
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/visualizers/ProjectVisualizer.tsx ================================================ import { useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import Controls from "../components/controls/Controls.tsx"; import MetricsExtension from "../components/controls/ControlExtensions/MetricsExtension.tsx"; import FileContextMenu from "../components/contextMenu/FileContextMenu.tsx"; import FileDetailsPane from "../components/detailsPanes/FileDetailsPane.tsx"; import { ProjectDependencyVisualizer } from "../../../cytoscape/projectDependencyVisualizer/index.ts"; import type { DependencyManifest, Metric } from "../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../types/auditManifest.ts"; import { useTheme } from "../../../contexts/ThemeProvider.tsx"; interface ProjectVisualizerProps { manifestId: string; dependencyManifest: DependencyManifest; auditManifest: AuditManifest; highlightedCytoscapeRef: | { filePath: string; symbolId: string | undefined } | undefined; } export default function ProjectVisualizer(props: ProjectVisualizerProps) { const navigate = useNavigate(); const { theme } = useTheme(); const [searchParams, setSearchParams] = useSearchParams(); const containerRef = useRef(null); const [busy, setBusy] = useState(true); const [projectVisualizer, setProjectVisualizer] = useState< ProjectDependencyVisualizer | undefined >(undefined); const metricFromUrl = (searchParams.get("metric") || undefined) as | Metric | undefined; const [metric, setMetric] = useState(metricFromUrl); function handleMetricChange(newMetric: Metric | undefined) { if (newMetric) { searchParams.set("metric", newMetric); setSearchParams(searchParams); } else { searchParams.delete("metric"); setSearchParams(searchParams); } setMetric(newMetric); } const [contextMenu, setContextMenu] = useState< | { position: { x: number; y: number }; fileDependencyManifest: DependencyManifest[string]; } | undefined >(undefined); const [detailsPane, setDetailsPane] = useState< | { manifestId: string; fileDependencyManifest: DependencyManifest[string]; fileAuditManifest: AuditManifest[string]; } | undefined >(undefined); useEffect(() => { setBusy(true); const visualizer = new ProjectDependencyVisualizer( containerRef.current as HTMLElement, props.dependencyManifest, props.auditManifest, { theme, defaultMetric: metric, onAfterNodeRightClick: (value: { position: { x: number; y: number }; filePath: string; }) => { setContextMenu({ position: value.position, fileDependencyManifest: props.dependencyManifest[value.filePath], }); }, onAfterNodeDblClick: (filePath: string) => { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.delete("instanceId"); navigate(`?${newSearchParams.toString()}`); }, }, ); setProjectVisualizer(visualizer); setBusy(false); return () => { visualizer?.cy.destroy(); setProjectVisualizer(undefined); }; }, [props.dependencyManifest, props.auditManifest]); useEffect(() => { if (projectVisualizer) { projectVisualizer.setTargetMetric(metric); } }, [metric]); useEffect(() => { if (projectVisualizer) { if (props.highlightedCytoscapeRef) { projectVisualizer.highlightNode(props.highlightedCytoscapeRef); } else { projectVisualizer.unhighlightNodes(); } } }, [props.highlightedCytoscapeRef]); useEffect(() => { if (projectVisualizer) { projectVisualizer.updateTheme(theme); } }, [theme]); return (
projectVisualizer?.layoutGraph(projectVisualizer.cy)} >
setDetailsPane(undefined)} /> setContextMenu(undefined)} onOpenDetails={(filePath) => { setDetailsPane({ manifestId: props.manifestId, fileDependencyManifest: props.dependencyManifest[filePath], fileAuditManifest: props.auditManifest[filePath], }); }} />
); } ================================================ FILE: viewer/src/components/DependencyVisualizer/visualizers/SymbolVisualizer.tsx ================================================ import { useEffect, useRef, useState } from "react"; import { useNavigate, useSearchParams } from "react-router-dom"; import Controls from "../components/controls/Controls.tsx"; import GraphDepthExtension from "../components/controls/ControlExtensions/GraphDepthExtension.tsx"; import SymbolContextMenu from "../components/contextMenu/SymbolContextMenu.tsx"; import SymbolDetailsPane from "../components/detailsPanes/SymbolDetailsPane.tsx"; import { SymbolDependencyVisualizer } from "../../../cytoscape/symbolDependencyVisualizer/index.ts"; import type { DependencyManifest } from "../../../types/dependencyManifest.ts"; import type { AuditManifest } from "../../../types/auditManifest.ts"; import { useTheme } from "../../../contexts/ThemeProvider.tsx"; interface SymbolVisualizerProps { manifestId: string; dependencyManifest: DependencyManifest; auditManifest: AuditManifest; highlightedCytoscapeRef: | { filePath: string; symbolId: string | undefined } | undefined; fileId: string; instanceId: string; } export default function SymbolVisualizer(props: SymbolVisualizerProps) { const navigate = useNavigate(); const { theme } = useTheme(); const [searchParams, setSearchParams] = useSearchParams(); const containerRef = useRef(null); const [busy, setBusy] = useState(true); const [symbolVisualizer, setSymbolVisualizer] = useState< SymbolDependencyVisualizer | undefined >(undefined); const dependencyDepthFromUrl = (searchParams.get("dependencyDepth") || undefined) as number | undefined; const dependentDepthFromUrl = (searchParams.get("dependentDepth") || undefined) as number | undefined; const [dependencyDepth, setDependencyDepth] = useState( dependencyDepthFromUrl || 3, ); const [dependentDepth, setDependentDepth] = useState( dependentDepthFromUrl || 0, ); function handleDependencyDepthChange(depth: number) { searchParams.set("dependencyDepth", depth.toString()); setSearchParams(searchParams); setDependencyDepth(depth); } function handleDependentDepthChange(depth: number) { searchParams.set("dependentDepth", depth.toString()); setSearchParams(searchParams); setDependentDepth(depth); } const [contextMenu, setContextMenu] = useState< | { position: { x: number; y: number }; fileDependencyManifest: DependencyManifest[string]; symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; } | undefined >(undefined); const [detailsPane, setDetailsPane] = useState< | { manifestId: string; fileDependencyManifest: DependencyManifest[string]; symbolDependencyManifest: DependencyManifest[string]["symbols"][string]; fileAuditManifest: AuditManifest[string]; symbolAuditManifest: AuditManifest[string]["symbols"][string]; } | undefined >(undefined); useEffect(() => { setBusy(true); if (!props.fileId || !props.instanceId) { return; } const visualizer = new SymbolDependencyVisualizer( containerRef.current as HTMLElement, props.fileId, props.instanceId, dependencyDepth, dependentDepth, props.dependencyManifest, props.auditManifest, { theme, onAfterNodeRightClick: (value: { position: { x: number; y: number }; filePath: string; symbolId: string; }) => { const fileDependencyManifest = props.dependencyManifest[value.filePath]; const symbolDependencyManifest = fileDependencyManifest.symbols[value.symbolId]; setContextMenu({ position: value.position, fileDependencyManifest, symbolDependencyManifest, }); }, onAfterNodeDblClick: (filePath: string, symbolId: string) => { const newSearchParams = new URLSearchParams(searchParams); newSearchParams.set("fileId", filePath); newSearchParams.set("instanceId", symbolId); navigate(`?${newSearchParams.toString()}`); }, }, ); setSymbolVisualizer(visualizer); setBusy(false); return () => { visualizer?.cy.destroy(); setSymbolVisualizer(undefined); }; }, [ props.dependencyManifest, props.auditManifest, props.fileId, props.instanceId, dependencyDepth, dependentDepth, ]); useEffect(() => { if (symbolVisualizer) { if (props.highlightedCytoscapeRef) { symbolVisualizer.highlightNode(props.highlightedCytoscapeRef); } else { symbolVisualizer.unhighlightNodes(); } } }, [props.highlightedCytoscapeRef]); useEffect(() => { if (symbolVisualizer) { symbolVisualizer.updateTheme(theme); } }, [theme]); return (
symbolVisualizer?.layoutGraph(symbolVisualizer.cy)} >
setContextMenu(undefined)} onOpenDetails={(filePath, symbolId) => { const fileDependencyManifest = props.dependencyManifest[filePath]; const symbolDependencyManifest = fileDependencyManifest.symbols[symbolId]; const fileAuditManifest = props.auditManifest[filePath]; const symbolAuditManifest = fileAuditManifest.symbols[symbolId]; setDetailsPane({ manifestId: props.manifestId, fileDependencyManifest, symbolDependencyManifest, fileAuditManifest, symbolAuditManifest, }); }} /> setDetailsPane(undefined)} />
); } ================================================ FILE: viewer/src/components/shadcn/Alert.tsx ================================================ import type * as React from "react"; import { cva, type VariantProps } from "class-variance-authority"; import { cn } from "../../lib/utils.ts"; const alertVariants = cva( "relative w-full rounded-lg border px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current", { variants: { variant: { default: "bg-card text-card-foreground", destructive: "text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive/90", }, }, defaultVariants: { variant: "default", }, }, ); function Alert({ className, variant, ...props }: React.ComponentProps<"div"> & VariantProps) { return (
); } function AlertTitle({ className, ...props }: React.ComponentProps<"div">) { return (
); } function AlertDescription({ className, ...props }: React.ComponentProps<"div">) { return (
); } export { Alert, AlertDescription, AlertTitle }; ================================================ FILE: viewer/src/components/shadcn/Breadcrumb.tsx ================================================ import type * as React from "react"; import { Slot } from "@radix-ui/react-slot"; import { ChevronRight, MoreHorizontal } from "lucide-react"; import { cn } from "../../lib/utils.ts"; function Breadcrumb({ ...props }: React.ComponentProps<"nav">) { return