├── .github ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── issue-automation.yml │ ├── prerelease.yml │ └── publish.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .prettierignore ├── .vscodeignore ├── LICENSE ├── README.md ├── assets ├── Default_Sourcery_Config.png ├── Sourcery_Code_Review.gif ├── Sourcery_Example_Duplicates_VS_Code.gif ├── Sourcery_Example_Full_Project_Scan_VS_Code.gif ├── Sourcery_GitHub-Refactor-Branch.gif ├── Sourcery_Ignore_Config_Example.png ├── Sourcery_Inline_Skip_Example.png ├── Sourcery_Inline_Skip_Specific_Example.png ├── Sourcery_Logo_VS_Code_Description.png ├── Sourcery_Metrics_VS_Code.gif ├── Sourcery_Skip_Config.png ├── Sourcery_VS_Code_Example.gif ├── Sourcery_VS_Code_Refactoring.gif ├── code_review.png ├── custom-rules.gif ├── docstring_generation.png ├── favicon.png ├── hubContainer.html └── sourcery-cli.gif ├── download-binaries.sh ├── media ├── animations.css ├── chat.css ├── github-dark.min.css ├── github.min.css ├── ide-styles.css ├── reset.css ├── search.css └── vscode.css ├── package-lock.json ├── package.json ├── renovate.json ├── sourcery-icon.png ├── src ├── ask-sourcery.ts ├── chat.ts ├── executable.ts ├── extension.ts ├── renderMarkdownMessage.ts ├── rule-search-results.ts ├── rule-search.ts ├── uninstall.ts └── webview │ └── search.js ├── tsconfig.json ├── walkthrough ├── Ask_Sourcery.gif ├── Sourcery_3_Ways_to_Refactor_VS_Code.gif ├── Sourcery_Code_Review.gif ├── Sourcery_Login.gif ├── Sourcery_Metrics_VS_Code.gif ├── accept_recommendation.md ├── code_review.png ├── code_reviews.md ├── configuration.md ├── github_code_reviews.md ├── login.md └── view_welcome_file.md ├── welcome-to-sourcery.py └── yarn.lock /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | ## Checklist 3 | 4 | - [ ] If `package.json` or `yarn.lock` have changed, then test the VSIX built by `yarn run vsce package` works from a direct install 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Check conformance with Prettier 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | prettier: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Check out repo 11 | uses: actions/checkout@v2 12 | 13 | - name: Cache dependencies 14 | uses: actions/cache@v4 15 | with: 16 | path: ~/.npm 17 | key: npm-${{ hashFiles('package-lock.json') }} 18 | restore-keys: npm- 19 | 20 | - name: Install dependencies 21 | run: npm ci --ignore-scripts 22 | 23 | - name: Run prettier 24 | run: |- 25 | npm run lint:format 26 | -------------------------------------------------------------------------------- /.github/workflows/issue-automation.yml: -------------------------------------------------------------------------------- 1 | name: Add issues to On Call project 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - labeled 8 | 9 | jobs: 10 | add-to-project: 11 | name: Add issue to "On Call" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/add-to-project@v0.4.1 15 | with: 16 | project-url: https://github.com/orgs/sourcery-ai/projects/3 17 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 18 | -------------------------------------------------------------------------------- /.github/workflows/prerelease.yml: -------------------------------------------------------------------------------- 1 | name: Pre-release VSIX 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to release' 8 | required: true 9 | 10 | jobs: 11 | package-vsix: 12 | strategy: 13 | matrix: 14 | include: 15 | - os: windows-latest 16 | platform: win 17 | arch: x64 18 | target: win32-x64 19 | - os: ubuntu-latest 20 | platform: linux 21 | arch: x64 22 | target: linux-x64 23 | - os: macos-latest 24 | platform: mac 25 | arch: x64 26 | target: darwin-x64 27 | - os: macos-latest 28 | platform: mac 29 | arch: arm64 30 | target: darwin-arm64 31 | runs-on: ubuntu-latest 32 | 33 | steps: 34 | - uses: actions/checkout@v3 35 | with: 36 | token: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 37 | 38 | - uses: actions/setup-node@v3 39 | with: 40 | node-version: latest 41 | 42 | - run: yarn install --frozen-lockfile 43 | 44 | - name: Update binaries 45 | run: | 46 | ./download-binaries.sh ${{ github.event.inputs.version }} 47 | mkdir -p sourcery_binaries/install 48 | rm -rf sourcery_binaries/install/* 49 | mv downloaded_binaries/${{matrix.platform}}-${{matrix.arch}} sourcery_binaries/install/${{matrix.platform}} 50 | 51 | - name: Package VSCode extension 52 | run: yarn run vsce package --target ${{matrix.target}} 53 | 54 | - name: Rename packaged VSIX file 55 | run: mv sourcery-*.vsix sourcery-${{ github.event.inputs.version }}-${{ matrix.target }}.vsix 56 | 57 | - name: Upload archive 58 | uses: actions/upload-artifact@v4 59 | with: 60 | path: '*.vsix' 61 | name: sourcery-${{ github.event.inputs.version }}-${{ matrix.target }} 62 | 63 | release: 64 | needs: package-vsix 65 | runs-on: ubuntu-latest 66 | 67 | steps: 68 | - name: Download artifacts 69 | uses: actions/download-artifact@v4 70 | with: 71 | merge-multiple: true 72 | path: artifacts 73 | 74 | - name: Create release 75 | uses: ncipollo/release-action@v1 76 | with: 77 | tag: v${{ github.event.inputs.version }} 78 | name: Sourcery ${{ github.event.inputs.version }} 79 | body: v${{ github.event.inputs.version }} 80 | prerelease: true 81 | artifacts: 'artifacts/*.vsix' 82 | artifactContentType: raw 83 | artifactErrorsFailBuild: true 84 | token: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 85 | 86 | - name: Notify Slack 87 | uses: 8398a7/action-slack@v3 88 | with: 89 | status: ${{ job.status }} 90 | text: Publish VS Code extension pre-release v${{ github.event.inputs.version }} - ${{ job.status }} 91 | fields: repo,commit,workflow,job 92 | env: 93 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_RELEASES_WEBHOOK_URL }} 94 | if: always() 95 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish VSIX 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | version: 7 | description: 'Version to release' 8 | required: true 9 | 10 | permissions: 11 | contents: read 12 | actions: read 13 | 14 | jobs: 15 | package-vsix: 16 | strategy: 17 | matrix: 18 | include: 19 | - os: windows-latest 20 | platform: win 21 | arch: x64 22 | target: win32-x64 23 | - os: ubuntu-latest 24 | platform: linux 25 | arch: x64 26 | target: linux-x64 27 | - os: macos-latest 28 | platform: mac 29 | arch: x64 30 | target: darwin-x64 31 | - os: macos-latest 32 | platform: mac 33 | arch: arm64 34 | target: darwin-arm64 35 | runs-on: ubuntu-latest 36 | 37 | steps: 38 | - uses: actions/checkout@v3 39 | with: 40 | token: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 41 | 42 | - uses: actions/setup-node@v3 43 | with: 44 | node-version: latest 45 | 46 | - run: yarn install --frozen-lockfile 47 | 48 | - name: Update binaries 49 | run: | 50 | ./download-binaries.sh ${{ github.event.inputs.version }} 51 | mkdir -p sourcery_binaries/install 52 | rm -rf sourcery_binaries/install/* 53 | mv downloaded_binaries/${{matrix.platform}}-${{matrix.arch}} sourcery_binaries/install/${{matrix.platform}} 54 | 55 | - name: Update version number 56 | run: yarn run bump ${{ github.event.inputs.version }} 57 | 58 | - name: Package VSCode extension 59 | run: yarn run vsce package --target ${{matrix.target}} 60 | 61 | - name: Rename packaged VSIX file 62 | run: mv sourcery-*.vsix sourcery-${{ github.event.inputs.version }}-${{ matrix.target }}.vsix 63 | 64 | - name: Upload archive 65 | uses: actions/upload-artifact@v4 66 | with: 67 | path: '*.vsix' 68 | name: sourcery-${{ github.event.inputs.version }}-${{ matrix.target }} 69 | 70 | 71 | publish-vsix: 72 | runs-on: ubuntu-latest 73 | needs: package-vsix 74 | 75 | steps: 76 | - uses: actions/checkout@v3 77 | with: 78 | token: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 79 | 80 | - uses: actions/setup-node@v3 81 | with: 82 | node-version: latest 83 | 84 | - run: yarn install --frozen-lockfile 85 | 86 | - name: Update version number 87 | run: yarn run bump ${{ github.event.inputs.version }} 88 | 89 | - name: Commit to main, create tag, and push 90 | run: | 91 | git add . 92 | git config --global user.name ${{ github.actor }} 93 | git config --global user.email ${{ github.actor }}@users.noreply.github.com 94 | git commit -m 'Update version number to v${{ github.event.inputs.version }}' 95 | git tag v${{ github.event.inputs.version }} 96 | git push origin main v${{ github.event.inputs.version }} 97 | env: 98 | GITHUB_TOKEN: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 99 | 100 | - name: Download artifacts 101 | uses: actions/download-artifact@v4 102 | with: 103 | merge-multiple: true 104 | path: artifacts 105 | 106 | 107 | - name: Package and publish VSCode extension 108 | run: | 109 | yarn run vsce publish --packagePath $(find . -iname *.vsix) -p ${{ secrets.VSCE_TOKEN }} 110 | 111 | - name: Create release 112 | uses: ncipollo/release-action@v1 113 | with: 114 | tag: v${{ github.event.inputs.version }} 115 | name: Sourcery ${{ github.event.inputs.version }} 116 | body: v${{ github.event.inputs.version }} 117 | prerelease: false 118 | artifacts: 'artifacts/*.vsix' 119 | artifactContentType: raw 120 | artifactErrorsFailBuild: true 121 | token: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 122 | 123 | - name: Update version number to dev 124 | run: | 125 | yarn run bump prepatch --preid dev 126 | git commit -am 'Bump version to dev' 127 | git push origin main 128 | env: 129 | GITHUB_TOKEN: ${{ secrets.SOURCERY_RELEASE_TOKEN }} 130 | 131 | - name: Notify Slack 132 | uses: 8398a7/action-slack@v3 133 | with: 134 | status: ${{ job.status }} 135 | text: Publish VS Code extension v${{ github.event.inputs.version }} - ${{ job.status }} 136 | env: 137 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_RELEASES_WEBHOOK_URL }} 138 | if: always() 139 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Created by https://www.gitignore.io/api/node,visualstudiocode 4 | # Edit at https://www.gitignore.io/?templates=node,visualstudiocode 5 | 6 | ### Node ### 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | node_modules/ 48 | jspm_packages/ 49 | 50 | # TypeScript v1 declaration files 51 | typings/ 52 | 53 | # TypeScript cache 54 | *.tsbuildinfo 55 | 56 | # Optional npm cache directory 57 | .npm 58 | 59 | # Optional eslint cache 60 | .eslintcache 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # next.js build output 79 | .next 80 | 81 | # nuxt.js build output 82 | .nuxt 83 | 84 | # rollup.js default build output 85 | dist/ 86 | 87 | # Uncomment the public line if your project uses Gatsby 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 90 | # public 91 | 92 | # Storybook build outputs 93 | .out 94 | .storybook-out 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # Temporary folders 109 | tmp/ 110 | temp/ 111 | 112 | ### VisualStudioCode ### 113 | .vscode/* 114 | !.vscode/settings.json 115 | !.vscode/tasks.json 116 | !.vscode/launch.json 117 | !.vscode/extensions.json 118 | 119 | ### VisualStudioCode Patch ### 120 | # Ignore all local history of files 121 | .history 122 | 123 | # End of https://www.gitignore.io/api/node,visualstudiocode 124 | 125 | 126 | .vscode-dev/ 127 | out/ 128 | *.vsix 129 | sourcery_binaries/ 130 | .idea/ 131 | .DS_Store 132 | downloaded_binaries/ -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/mirrors-prettier 3 | rev: v2.7.1 4 | hooks: 5 | - id: prettier 6 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Ignore everything: 2 | /* 3 | 4 | # Except src and media: 5 | !/src 6 | !/media 7 | 8 | # But do ignore resource webview assets 9 | /src/resources/webview/assets/* 10 | -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | 2 | * 3 | */** 4 | **/.DS_Store 5 | !sourcery_binaries/** 6 | !out/** 7 | !package.json 8 | !sourcery-icon.png 9 | !welcome-to-sourcery.py 10 | !README.md 11 | !LICENSE 12 | !media/** 13 | !src/webview/** 14 | !walkthrough/** 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sourcery.AI LIMITED 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [twitter-shield]: https://img.shields.io/twitter/follow/SourceryAI?style=social 2 | [twitter-url]: https://bit.ly/sourceryai-twitter 3 | [github-shield]: https://img.shields.io/github/stars/sourcery-ai/sourcery?style=social 4 | [github-url]: https://bit.ly/sourceryai-github 5 | [vscode-shield]: https://img.shields.io/visual-studio-marketplace/r/sourcery.sourcery?logo=visual-studio-code&style=social 6 | [vscode-url]: https://bit.ly/sourceryai-vscode 7 | 8 | [![Github Repo][github-shield]][github-url] 9 | [![VSCode Plugin][vscode-shield]][vscode-url] 10 | [![Twitter Follow][twitter-shield]][twitter-url] 11 | 12 | ![Sourcery](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/Sourcery_Logo_VS_Code_Description.png) 13 | 14 | **[WEBSITE](https://sourcery.ai/) | [DOCS](https://docs.sourcery.ai/Welcome/) | [BUGS](https://github.com/sourcery-ai/sourcery/issues)** 15 | 16 | Sourcery is your pair programmer that reviews and enhances your code in real-time. With intelligent code reviews and an AI chat assistant that understands your code, you can ship better code faster. 17 | 18 | ![Sourcery in VS Code](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/Sourcery_VS_Code_Example.gif) 19 | 20 | --- 21 | 22 | ## Sourcery 23 | 24 | Sourcery is a VS Code extension to help you move faster and ship code with confidence. Sourcery gives you code reviews anytime you want, so you can get feedback before you need to open a PR. Plus Sourcery's coding assistant helps you speed up repetitive tasks while you work. 25 | 26 | Here are some of the features Sourcery offers to help improve your code: 27 | 28 | - [Chat with an AI that knows about your code](#chat) 29 | - [Code reviews anytime, in IDE](#code-reviews-on-demand) 30 | - [GitHub & GitLab Pull Request reviews](#github-pull-request-review) 31 | - [Real-time refactoring suggestions](#real-time-refactoring-suggestions) 32 | - [Continuous code quality feedback](#continuous-code-quality-feedback) 33 | 34 | 35 | To start using Sourcery on your code, check out our [Getting Started guide](https://docs.sourcery.ai/getting-started/). 36 | 37 | Check out our [documentation](https://docs.sourcery.ai/Welcome/) for more information on how to use Sourcery. 38 | 39 | Sourcery is free to use for open source projects. 40 | 41 | To use Sourcery on non open-sourced projects you'll need a Sourcery Pro subscription. To get a 14 day free trial to Sourcery Pro, **[sign up for an account on the Sourcery site](https://app.sourcery.ai/dashboard/subscription?utm_source=vscode_marketplace&utm_medium=web&utm_campaign=vscode_marketplace)**. 42 | 43 | --- 44 | 45 | ## Features 46 | 47 | ### Chat 48 | 49 | ![Generating documentation](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/docstring_generation.png) 50 | 51 | Talk with an AI that knows about your code. Select code to add it to context and ask questions or ask for improvements. Apply code changes with a single click. 52 | 53 | Sourcery Chat comes with in-built recipes to: 54 | - Generate diagrams: No need to spend hours manually creating diagrams. Sourcery can generate them for you in Mermaid format - grab the code or view it as an image. 55 | - Generate Tests: Generate comprehensive unit tests for your code. 56 | - Generate docstrings: Sourcery can generate docstrings for your code, copying the style from your existing docstrings. Add them to your code with a single click. 57 | - Explain code: Sourcery can explain your code, point out how it works and anything unusual or not idiomatic. 58 | 59 | ### Code reviews on demand 60 | 61 | ![Sourcery Code Reviews](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/code_review.png) 62 | 63 | Get feedback on your code anytime you want. Sourcery can review your code directly in your IDE and give you instant actionable feedback. 64 | Review your current file, your uncommitted changes, or compare any two Git branches. Individual review comments are shown inline in your code, along with suggested fixes if applicable. 65 | 66 | 67 | ### GitHub & GitLab Pull Request Review 68 | 69 | ![Sourcery Reviewing GitHub PRs](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/Sourcery_Code_Review.gif) 70 | 71 | Sourcery can help you speed up code reviews and clean up every new commit by automatically reviewing each of your GitHub pull requests. This also gives you a PR summary, can generate PR titles, and posts a reviewers guide to the PR, including diagrams and a breakdown of the changes. 72 | 73 | To get started, add [Sourcery to your GitHub repo](https://github.com/apps/sourcery-ai/installations/new) or [GitLab project](https://app.sourcery.ai/login?connection=gitlab). Sourcery will then start reviewing every new pull request automatically! 74 | 75 | ### Real-time refactoring suggestions 76 | 77 | ![Refactoring Code with Sourcery](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/Sourcery_VS_Code_Refactoring.gif) 78 | 79 | While you work, Sourcery will review all of the Python, JavaScript, and TypeScript files you have open and look for opportunities to clean up and improve your code. Once Sourcery finds a potential improvement it will underline that section of your code. 80 | 81 | Hover your mouse over the underlined section of code to see the changes Sourcery suggests and to see a diff of the proposed change. 82 | 83 | To make the change, just bring up the quick fix menu and choose to accept the change. Sourcery will then apply the change to your code. We're constantly adding new improvements and rules that Sourcery can make. The current list of rules Sourcery checks for is available **[here](https://docs.sourcery.ai/refactorings/)**. 84 | 85 | The commands to bring up the quick fix menu depend on your OS & region, but generally they are: 86 | 87 | 88 | | OS | Keyboard Shortcut | 89 | | --- | --- | 90 | | Mac | Command . | 91 | | Windows | Ctrl . | 92 | | Linux | Ctrl . | 93 | 94 | Sourcery reviews all of the files you have open. You can get an overview of all the suggestions Sourcery has in the Problem window. 95 | 96 | ### Set up your own rules - or use public rulesets 97 | 98 | Sourcery is widely extendable so that you can have it check for whatever types of best practices you care about within your code. 99 | 100 | Sourcery reads rules from a .sourcery.yaml configuration file that you can set up in your project directory - then it will look to apply those rules across the full project. To get started we’ve [set up a quick tutorial](https://docs.sourcery.ai/Tutorials/Custom-Rules/) and you can [dive deeper in our full documentation](https://docs.sourcery.ai/Reference/Custom-Rules/pattern-syntax/) 101 | 102 | ![Sourcery Custom Rules](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/custom-rules.gif) 103 | 104 | You can also opt into publicly available sets of rules to help improve your code. [Check out the Google Python Style Guide](https://docs.sourcery.ai/Reference/Custom-Rules/gpsg/) and see how you can add it to Sourcery. 105 | 106 | ### Continuous code quality feedback 107 | 108 | ![Code Quality Metrics in Sourcery](https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/main/assets/Sourcery_Metrics_VS_Code.gif) 109 | 110 | Sourcery gives each of your functions a quality score ranging from 0% (bad) - 100% (good) and also gives you sub-scores on Method Length, Complexity, and Working Memory so that you can figure out how to structure your code as cleanly as possible. 111 | 112 | **Method Length** is a metric is a measure of how long each method is on average. It is based on the number of nodes in the method's Abstract Syntax Tree. 113 | 114 | **Complexity** is a measure of how difficult your code is to read and understand. It is based on these principles: 115 | 116 | - Each break in the linear flow of the code makes it harder to understand 117 | - When these flow-breaking structures are nested they are even harder to understand 118 | 119 | **Working Memory** is a measure of the number of variables that need to be kept in your working memory as you read through the code. 120 | 121 | Sourcery will warn you if your overall quality score for a function falls below 25%. 122 | 123 | 124 | --- 125 | 126 | ## About us 127 | 128 | We're a small team out of London trying to make it easier for everyone to write brilliant code. Follow us on [Twitter](https://twitter.com/sourceryai) or visit our [blog](https://sourcery.ai/blog) to keep up with updates. 129 | 130 | --- 131 | 132 | ## Licensing 133 | 134 | This repository includes source code as well as packaged binaries. The MIT license only applies to the source code, not the binaries. 135 | -------------------------------------------------------------------------------- /assets/Default_Sourcery_Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Default_Sourcery_Config.png -------------------------------------------------------------------------------- /assets/Sourcery_Code_Review.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Code_Review.gif -------------------------------------------------------------------------------- /assets/Sourcery_Example_Duplicates_VS_Code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Example_Duplicates_VS_Code.gif -------------------------------------------------------------------------------- /assets/Sourcery_Example_Full_Project_Scan_VS_Code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Example_Full_Project_Scan_VS_Code.gif -------------------------------------------------------------------------------- /assets/Sourcery_GitHub-Refactor-Branch.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_GitHub-Refactor-Branch.gif -------------------------------------------------------------------------------- /assets/Sourcery_Ignore_Config_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Ignore_Config_Example.png -------------------------------------------------------------------------------- /assets/Sourcery_Inline_Skip_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Inline_Skip_Example.png -------------------------------------------------------------------------------- /assets/Sourcery_Inline_Skip_Specific_Example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Inline_Skip_Specific_Example.png -------------------------------------------------------------------------------- /assets/Sourcery_Logo_VS_Code_Description.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Logo_VS_Code_Description.png -------------------------------------------------------------------------------- /assets/Sourcery_Metrics_VS_Code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Metrics_VS_Code.gif -------------------------------------------------------------------------------- /assets/Sourcery_Skip_Config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_Skip_Config.png -------------------------------------------------------------------------------- /assets/Sourcery_VS_Code_Example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_VS_Code_Example.gif -------------------------------------------------------------------------------- /assets/Sourcery_VS_Code_Refactoring.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/Sourcery_VS_Code_Refactoring.gif -------------------------------------------------------------------------------- /assets/code_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/code_review.png -------------------------------------------------------------------------------- /assets/custom-rules.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/custom-rules.gif -------------------------------------------------------------------------------- /assets/docstring_generation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/docstring_generation.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/favicon.png -------------------------------------------------------------------------------- /assets/hubContainer.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/hubContainer.html -------------------------------------------------------------------------------- /assets/sourcery-cli.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/assets/sourcery-cli.gif -------------------------------------------------------------------------------- /download-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | if [[ -z "$VERSION" ]]; then 5 | echo Error: please pass version 6 | echo 7 | echo Usage: $0 VERSION 8 | exit 1 9 | fi 10 | 11 | # Get release asset json 12 | RELEASES_URL=https://api.github.com/repos/sourcery-ai/sourcery/releases 13 | ASSETS=$( curl -s $RELEASES_URL | jq ".[] | select(.tag_name == \"v$VERSION\") | .assets" ) 14 | if [[ -z $ASSETS ]]; then 15 | echo Could not find version $VERSION in $RELEASES_URL 16 | exit 1 17 | fi 18 | 19 | # This is where we will build the package from 20 | mkdir -p sourcery_binaries/install/{linux,mac,win} 21 | rm -rf sourcery_binaries/install/{linux,mac,win}/* 22 | 23 | # Download the binaries into here 24 | mkdir -p downloaded_binaries/{linux-x64,mac-arm64,mac-x64,win-x64} 25 | rm -rf downloaded_binaries/{linux-x64,mac-arm64,mac-x64,win-x64}/* 26 | 27 | 28 | echo Downloading linux binary 29 | curl -s -L $( echo $ASSETS | jq -r ".[] | select(.name == \"sourcery-$VERSION-linux.tar.gz\") | .browser_download_url" ) \ 30 | | tar -xz -C downloaded_binaries/linux-x64 31 | 32 | echo Downloading mac intel binary 33 | curl -s -L $( echo $ASSETS | jq -r ".[] | select(.name == \"sourcery-$VERSION-mac.tar.gz\") | .browser_download_url" ) \ 34 | | tar -xz -C downloaded_binaries/mac-x64 35 | 36 | echo Downloading mac arm64 binary 37 | curl -s -L $( echo $ASSETS | jq -r ".[] | select(.name == \"sourcery-$VERSION-mac-arm64.tar.gz\") | .browser_download_url" ) \ 38 | | tar -xz -C downloaded_binaries/mac-arm64 39 | 40 | echo Downloading windows binary 41 | curl -s -L $( echo $ASSETS | jq -r ".[] | select(.name == \"sourcery-$VERSION-win.zip\") | .browser_download_url" ) -o temp.zip 42 | unzip temp.zip -d downloaded_binaries/win-x64/ 43 | rm temp.zip 44 | -------------------------------------------------------------------------------- /media/animations.css: -------------------------------------------------------------------------------- 1 | /* Simple CSS animation to animate the agent cursor */ 2 | @keyframes cursor-blink { 3 | 0% { 4 | opacity: 0; 5 | } 6 | } 7 | 8 | /* Animation for the typing dots */ 9 | @keyframes typing-dots { 10 | 0% { 11 | transform: translateY(0px); 12 | } 13 | 14 | 40% { 15 | transform: translateY(-0.25rem); 16 | opacity: 0.4; 17 | } 18 | 19 | 60% { 20 | transform: translateY(0px); 21 | } 22 | 23 | 100% { 24 | transform: translateY(0px); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /media/chat.css: -------------------------------------------------------------------------------- 1 | /* Sidebar sections */ 2 | .sidebar__section-container { 3 | transition: all 0.3s ease; 4 | overflow: auto; 5 | flex-grow: 1; 6 | background-color: var(--vscode-sideBar-background); 7 | margin-block-start: 0; 8 | margin-block-end: 0; 9 | } 10 | 11 | .sidebar__section-container:not(.active) { 12 | max-height: 0; 13 | overflow: hidden; 14 | } 15 | 16 | /* ------------- Chat UI ------------- */ 17 | .sidebar__chat-assistant-body { 18 | display: flex; 19 | flex-direction: column; 20 | height: 100%; 21 | } 22 | 23 | .sidebar__chat-assistant--dialogue-container { 24 | padding: 0 var(--spacing-md); 25 | margin: 0; 26 | display: flex; 27 | flex-direction: column; 28 | overflow-y: scroll; 29 | } 30 | 31 | .sidebar__chat-assistant--chat-bubble { 32 | display: flex; 33 | overflow: hidden; 34 | width: 100%; 35 | gap: var(--spacing-md); 36 | align-items: flex-end; 37 | grid-gap: var(--spacing-md) var(--spacing-xs); 38 | margin-bottom: var(--spacing-md); 39 | word-break: break-word; 40 | } 41 | 42 | /* Change width of columns based on agent/user */ 43 | .sidebar__chat-assistant--chat-bubble-agent { 44 | grid-template-columns: 32px 1fr; 45 | flex-direction: row; 46 | } 47 | 48 | .sidebar__chat-assistant--chat-bubble-user { 49 | grid-template-columns: 1fr 32px; 50 | flex-direction: row-reverse; 51 | } 52 | 53 | /* Styles for avatars */ 54 | .sidebar__chat-assistant--chat-avatar-container { 55 | flex-shrink: 0; 56 | flex-grow: 0; 57 | background: var(--vscode-foreground); 58 | padding: var(--spacing-xxs); 59 | border-radius: 100%; 60 | width: 2rem; 61 | height: 2rem; 62 | display: flex; 63 | align-items: center; 64 | justify-content: center; 65 | } 66 | 67 | .sidebar__chat-assistant--agent-avatar-image { 68 | width: 100%; 69 | display: block; 70 | } 71 | 72 | /* Styles for content inside chat bubbles */ 73 | .sidebar__chat-assistant--chat-bubble-content-assistant, 74 | .sidebar__chat-assistant--code-block-container { 75 | background-color: var(--vscode-settings-textInputBackground); 76 | padding: var(--spacing-xs); 77 | flex-grow: 1; 78 | flex-shrink: 1; 79 | overflow: hidden; 80 | border-radius: var(--spacing-sm) var(--spacing-sm) var(--spacing-sm) 0; 81 | } 82 | 83 | .sidebar__chat-assistant--chat-bubble-content-assistant a[href="VALID_FILE"], 84 | .sidebar__chat-assistant--chat-bubble-content-assistant 85 | a[href="VALID_DIRECTORY"], 86 | .sidebar__chat-assistant--chat-bubble-content-assistant a[href="INVALID_PATH"] { 87 | font-family: monospace; 88 | } 89 | 90 | .sidebar__chat-assistant--chat-bubble-content-assistant a[href="INVALID_PATH"] { 91 | color: var(--vscode-textPreformat-foreground); 92 | cursor: text; 93 | text-decoration: none; 94 | } 95 | 96 | .sidebar__chat-assistant--chat-bubble-content-user { 97 | background-color: var(--vscode-settings-textInputBackground); 98 | padding: var(--spacing-xs); 99 | border-radius: var(--spacing-sm) var(--spacing-sm) 0 var(--spacing-sm); 100 | } 101 | 102 | .sidebar__chat-assistant--chat-bubble-content-user 103 | .sidebar__chat-assistant--chat-bubble-text { 104 | white-space: pre-wrap; 105 | } 106 | 107 | .sidebar__chat-assistant--chat-bubble-text { 108 | margin: var(--spacing-sm); 109 | color: var(--vscode-settings-textInputForeground); 110 | } 111 | 112 | .sidebar__chat-assistant--chat-bubble-inline-button { 113 | /* Override a few things that are coming from the main CSS file */ 114 | display: inline-block; 115 | width: auto; 116 | padding: 0; 117 | margin: 0; 118 | background: transparent; 119 | } 120 | 121 | .sidebar__chat-assistant--chat-bubble-text > *:not(:last-child) { 122 | margin-bottom: var(--spacing-sm); 123 | } 124 | 125 | .sidebar__chat-assistant--chat-bubble-text :is(h1, h2, h3, h4, h5, h6) { 126 | font-size: 1em; 127 | font-weight: bold; 128 | } 129 | 130 | .sidebar__chat-assistant--chat-bubble-text 131 | :is(h1, h2, h3, h4, h5, h6):not(:first-child) { 132 | margin-top: var(--spacing-md); 133 | } 134 | 135 | .sidebar__chat-assistant--chat-bubble-text :is(ol, ul) { 136 | margin-left: 1.5em; 137 | } 138 | 139 | .sidebar__chat-assistant--chat-bubble-text li { 140 | /*padding-left: 1.5em;*/ 141 | } 142 | 143 | .sidebar__chat-assistant--chat-bubble-text code { 144 | font-family: var(--vscode-editor-font-family); 145 | font-size: var(--vscode-sidebar-font-size); 146 | } 147 | 148 | .sidebar__chat-assistant--chat-bubble-text pre { 149 | margin: 0; 150 | width: 100%; 151 | background-color: var(--vscode-editor-background); 152 | outline: 1px solid var(--vscode-panel-border); 153 | border-radius: var(--spacing-xs); 154 | position: relative; 155 | overflow: hidden; 156 | } 157 | 158 | .sidebar__chat-assistant--chat-bubble-text--code-action-buttons { 159 | visibility: hidden; 160 | display: flex; 161 | flex-direction: row; 162 | position: absolute; 163 | top: var(--spacing-sm); 164 | right: var(--spacing-sm); 165 | } 166 | 167 | .sidebar__chat-assistant--chat-bubble-text--code-action-buttons 168 | .sidebar__chat-assistant--chat-bubble-text--code-action-button:first-of-type { 169 | border-top-left-radius: var(--spacing-xxs); 170 | border-bottom-left-radius: var(--spacing-xxs); 171 | } 172 | 173 | .sidebar__chat-assistant--chat-bubble-text--code-action-buttons 174 | .sidebar__chat-assistant--chat-bubble-text--code-action-button:last-of-type { 175 | border-top-right-radius: var(--spacing-xxs); 176 | border-bottom-right-radius: var(--spacing-xxs); 177 | border-left: none; 178 | } 179 | 180 | button.sidebar__chat-assistant--chat-bubble-text--code-action-button { 181 | position: relative; 182 | background: var(--vscode-button-secondaryBackground); 183 | border: 1px solid var(--vscode-panel-border); 184 | color: var(--vscode-settings-textInputForeground); 185 | padding: var(--spacing-md); 186 | fill: var(--vscode-button-secondaryForeground); 187 | margin: 0; 188 | width: var(--spacing-lg); 189 | height: var(--spacing-lg); 190 | outline: none; 191 | } 192 | 193 | .sidebar__chat-assistant--code-block-action-button-icon { 194 | height: var(--spacing-lg); 195 | width: var(--spacing-lg); 196 | position: absolute; 197 | left: var(--spacing-xxs); 198 | top: var(--spacing-xxs); 199 | } 200 | 201 | button.sidebar__chat-assistant--chat-bubble-text--code-action-button:hover { 202 | background-color: var(--vscode-button-secondaryHoverBackground); 203 | } 204 | 205 | .sidebar__chat-assistant--chat-bubble-text 206 | pre:hover 207 | .sidebar__chat-assistant--chat-bubble-text--code-action-buttons { 208 | visibility: visible; 209 | } 210 | 211 | .sidebar__chat-assistant--code-block { 212 | margin: 0; 213 | font-family: var(--vscode-editor-font-family); 214 | white-space: pre-line; 215 | } 216 | 217 | .sidebar__chat-assistant--code-block code { 218 | font-size: var(--vscode-sidebar-font-size); 219 | } 220 | 221 | .sidebar__chat-assistant--chat-text-inline-code { 222 | display: inline-block; 223 | font-family: var(--vscode-editor-font-family); 224 | font-size: var(--vscode-sidebar-font-size); 225 | } 226 | 227 | .sidebar__chat-assistant--chat-text-bold { 228 | font-weight: 600; 229 | } 230 | 231 | .sidebar__chat-assistant--chat-text-separator { 232 | display: block; 233 | margin: var(--spacing-xxs); 234 | } 235 | 236 | .sidebar__chat-assistant--chat-numbered-list { 237 | color: var(--vscode-editor-background); 238 | } 239 | 240 | /* Hover functionality for inline code references in sidebar */ 241 | .sidebar__chat-assistant--chat-bubble-inline-button 242 | .sidebar__chat-assistant--chat-text-inline-code { 243 | text-decoration: underline; 244 | text-underline-offset: var(--spacing-xxs); 245 | } 246 | 247 | .sidebar__chat-assistant--chat-bubble-inline-button:hover { 248 | background: transparent; 249 | } 250 | 251 | .sidebar__chat-assistant--chat-bubble-inline-button:hover 252 | .sidebar__chat-assistant--chat-text-inline-code { 253 | text-decoration: none; 254 | } 255 | 256 | /* Make avatar bigger for user, since we're using an emoji for now */ 257 | .sidebar__chat-assistant--chat-bubble-user 258 | .sidebar__chat-assistant--agent-avatar-image { 259 | font-size: 1.25rem; 260 | text-align: center; 261 | } 262 | 263 | /* Error states inside chat bubbles */ 264 | .sidebar__chat-assistant--chat-bubble-error 265 | .sidebar__chat-assistant--chat-bubble-content-assistant { 266 | background: var(--vscode-inputValidation-errorBackground); 267 | } 268 | 269 | .sidebar__chat-assistant--chat-bubble-error 270 | .sidebar__chat-assistant--chat-bubble-text { 271 | color: var(--vscode-foreground); 272 | } 273 | 274 | .sidebar__chat-assistant--chat-bubble-error 275 | .sidebar__chat-assistant--chat-avatar-container { 276 | background: var(--vscode-inputValidation-errorBackground); 277 | } 278 | 279 | .sidebar__chat-assistant--chat-bubble-error { 280 | fill: var(--vscode-foreground); 281 | } 282 | 283 | .sidebar__chat-assistant--cursor { 284 | display: inline-block; 285 | animation: cursor-blink 1.5s steps(2) infinite; 286 | width: 2px; 287 | } 288 | 289 | /* Style and animate typing dots */ 290 | .sidebar__chat-assistant--agent-typing-dot { 291 | display: inline-block; 292 | height: var(--spacing-xxs); 293 | width: var(--spacing-xxs); 294 | background-color: var(--vscode-settings-textInputForeground); 295 | border-radius: 50%; 296 | animation-name: typing-dots; 297 | animation-duration: 0.8s; 298 | animation-timing-function: ease-in-out; 299 | animation-iteration-count: infinite; 300 | } 301 | 302 | .sidebar__chat-assistant--agent-typing-dot:nth-of-type(1) { 303 | animation-delay: 0s; 304 | } 305 | 306 | .sidebar__chat-assistant--agent-typing-dot:nth-of-type(2) { 307 | animation-delay: 0.2s; 308 | } 309 | 310 | .sidebar__chat-assistant--agent-typing-dot:nth-of-type(3) { 311 | animation-delay: 0.4s; 312 | } 313 | 314 | /* ------------- Text box container ------------- */ 315 | .sidebar__chat-assistant--footer { 316 | border-bottom: 1px solid var(--vscode-sideBar-border); 317 | padding: var(--spacing-md); 318 | background-color: var(--vscode-sideBar-background); 319 | flex-shrink: 0; 320 | flex-grow: 0; 321 | margin-block-start: 0; 322 | margin-block-end: 0; 323 | } 324 | 325 | .sidebar__chat-assistant--cancel-button { 326 | display: flex; 327 | align-items: center; 328 | justify-content: center; 329 | border-radius: var(--spacing-xxs); 330 | margin-bottom: var(--spacing-xs); 331 | outline: none; 332 | background-color: transparent; 333 | color: var(--vscode-foreground); 334 | } 335 | 336 | .sidebar__chat-assistant--cancel-button:hover { 337 | background-color: transparent; 338 | opacity: 0.8; 339 | } 340 | 341 | .sidebar__chat-assistant--cancel-button:disabled { 342 | color: var(--vscode-disabledForeground); 343 | cursor: initial; 344 | } 345 | 346 | .sidebar__chat-assistant--cancel-button:hover:disabled { 347 | background-color: transparent; 348 | opacity: 1; 349 | } 350 | 351 | .sidebar__chat-assistant--regenerate-button-icon { 352 | fill: var(--vscode-foreground); 353 | width: var(--spacing-lg); 354 | margin-right: var(--spacing-xs); 355 | } 356 | 357 | .sidebar__chat-assistant--cancel-button:disabled 358 | .sidebar__chat-assistant--regenerate-button-icon { 359 | fill: var(--vscode-disabledForeground); 360 | } 361 | 362 | .sidebar__chat-assistant--textarea-container { 363 | position: relative; 364 | } 365 | 366 | .sidebar__chat-assistant--textarea { 367 | font-weight: var(--vscode-font-weight); 368 | font-size: var(--vscode-font-size); 369 | border-radius: var(--spacing-xxs); 370 | background-color: var(--vscode-editor-background); 371 | padding-right: 3rem; 372 | outline: 1px solid transparent; 373 | resize: none; 374 | } 375 | 376 | .sidebar__chat-assistant--textarea-send-button { 377 | position: absolute; 378 | right: 5px; 379 | bottom: 5px; 380 | outline: none; 381 | border-radius: var(--spacing-xxs); 382 | font-size: var(--vscode-sidebar-font-size); 383 | background-color: transparent; 384 | width: 2rem; 385 | height: 2rem; 386 | display: flex; 387 | align-items: center; 388 | justify-content: center; 389 | transition: background-color 0.3s ease-in-out; 390 | } 391 | 392 | .sidebar__chat-assistant--textarea-send-icon { 393 | fill: orange; 394 | width: var(--spacing-lg); 395 | } 396 | 397 | /* Disabled state for send button */ 398 | .sidebar__textarea-send-button--disabled { 399 | pointer-events: none; 400 | background-color: transparent; 401 | } 402 | 403 | .sidebar__textarea-send-button--disabled svg { 404 | opacity: 0.2; 405 | } 406 | 407 | .sidebar__branch-form { 408 | display: grid; 409 | grid-template-columns: min-content auto; 410 | align-items: center; 411 | } 412 | 413 | .sidebar__branch-form label { 414 | margin-right: 10px; 415 | font-weight: bold; 416 | } 417 | 418 | .columnOne { 419 | grid-column: 1/2; 420 | grid-row: auto; 421 | } 422 | 423 | .columnTwo { 424 | grid-column: 2/2; 425 | grid-row: auto; 426 | } 427 | -------------------------------------------------------------------------------- /media/github-dark.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs { 2 | display: block; 3 | overflow-x: auto; 4 | padding: 1em; 5 | } 6 | code.hljs { 7 | padding: 3px 5px; 8 | } /*! 9 | Theme: GitHub Dark 10 | Description: Dark theme as seen on github.com 11 | Author: github.com 12 | Maintainer: @Hirse 13 | Updated: 2021-05-15 14 | 15 | Outdated base version: https://github.com/primer/github-syntax-dark 16 | Current colors taken from GitHub's CSS 17 | */ 18 | .hljs { 19 | color: #c9d1d9; 20 | background: #0d1117; 21 | } 22 | .hljs-doctag, 23 | .hljs-keyword, 24 | .hljs-meta .hljs-keyword, 25 | .hljs-template-tag, 26 | .hljs-template-variable, 27 | .hljs-type, 28 | .hljs-variable.language_ { 29 | color: #ff7b72; 30 | } 31 | .hljs-title, 32 | .hljs-title.class_, 33 | .hljs-title.class_.inherited__, 34 | .hljs-title.function_ { 35 | color: #d2a8ff; 36 | } 37 | .hljs-attr, 38 | .hljs-attribute, 39 | .hljs-literal, 40 | .hljs-meta, 41 | .hljs-number, 42 | .hljs-operator, 43 | .hljs-selector-attr, 44 | .hljs-selector-class, 45 | .hljs-selector-id, 46 | .hljs-variable { 47 | color: #79c0ff; 48 | } 49 | .hljs-meta .hljs-string, 50 | .hljs-regexp, 51 | .hljs-string { 52 | color: #a5d6ff; 53 | } 54 | .hljs-built_in, 55 | .hljs-symbol { 56 | color: #ffa657; 57 | } 58 | .hljs-code, 59 | .hljs-comment, 60 | .hljs-formula { 61 | color: #8b949e; 62 | } 63 | .hljs-name, 64 | .hljs-quote, 65 | .hljs-selector-pseudo, 66 | .hljs-selector-tag { 67 | color: #7ee787; 68 | } 69 | .hljs-subst { 70 | color: #c9d1d9; 71 | } 72 | .hljs-section { 73 | color: #1f6feb; 74 | font-weight: 700; 75 | } 76 | .hljs-bullet { 77 | color: #f2cc60; 78 | } 79 | .hljs-emphasis { 80 | color: #c9d1d9; 81 | font-style: italic; 82 | } 83 | .hljs-strong { 84 | color: #c9d1d9; 85 | font-weight: 700; 86 | } 87 | .hljs-addition { 88 | color: #aff5b4; 89 | background-color: #033a16; 90 | } 91 | .hljs-deletion { 92 | color: #ffdcd7; 93 | background-color: #67060c; 94 | } 95 | -------------------------------------------------------------------------------- /media/github.min.css: -------------------------------------------------------------------------------- 1 | pre code.hljs { 2 | display: block; 3 | overflow-x: auto; 4 | padding: 1em; 5 | } 6 | code.hljs { 7 | padding: 3px 5px; 8 | } /*! 9 | Theme: GitHub 10 | Description: Light theme as seen on github.com 11 | Author: github.com 12 | Maintainer: @Hirse 13 | Updated: 2021-05-15 14 | 15 | Outdated base version: https://github.com/primer/github-syntax-light 16 | Current colors taken from GitHub's CSS 17 | */ 18 | .hljs { 19 | color: #24292e; 20 | background: #fff; 21 | } 22 | .hljs-doctag, 23 | .hljs-keyword, 24 | .hljs-meta .hljs-keyword, 25 | .hljs-template-tag, 26 | .hljs-template-variable, 27 | .hljs-type, 28 | .hljs-variable.language_ { 29 | color: #d73a49; 30 | } 31 | .hljs-title, 32 | .hljs-title.class_, 33 | .hljs-title.class_.inherited__, 34 | .hljs-title.function_ { 35 | color: #6f42c1; 36 | } 37 | .hljs-attr, 38 | .hljs-attribute, 39 | .hljs-literal, 40 | .hljs-meta, 41 | .hljs-number, 42 | .hljs-operator, 43 | .hljs-selector-attr, 44 | .hljs-selector-class, 45 | .hljs-selector-id, 46 | .hljs-variable { 47 | color: #005cc5; 48 | } 49 | .hljs-meta .hljs-string, 50 | .hljs-regexp, 51 | .hljs-string { 52 | color: #032f62; 53 | } 54 | .hljs-built_in, 55 | .hljs-symbol { 56 | color: #e36209; 57 | } 58 | .hljs-code, 59 | .hljs-comment, 60 | .hljs-formula { 61 | color: #6a737d; 62 | } 63 | .hljs-name, 64 | .hljs-quote, 65 | .hljs-selector-pseudo, 66 | .hljs-selector-tag { 67 | color: #22863a; 68 | } 69 | .hljs-subst { 70 | color: #24292e; 71 | } 72 | .hljs-section { 73 | color: #005cc5; 74 | font-weight: 700; 75 | } 76 | .hljs-bullet { 77 | color: #735c0f; 78 | } 79 | .hljs-emphasis { 80 | color: #24292e; 81 | font-style: italic; 82 | } 83 | .hljs-strong { 84 | color: #24292e; 85 | font-weight: 700; 86 | } 87 | .hljs-addition { 88 | color: #22863a; 89 | background-color: #f0fff4; 90 | } 91 | .hljs-deletion { 92 | color: #b31d28; 93 | background-color: #ffeef0; 94 | } 95 | -------------------------------------------------------------------------------- /media/ide-styles.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ide-text: var(--vscode-sideBar-foreground); 3 | --ide-disabled-text: var(--vscode-disabledForeground); 4 | --ide-window-bg: var(--vscode-sideBar-background); 5 | --ide-editor-bg: var(--vscode-editor-background); 6 | --ide-chat-input-text: var(--vscode-settings-textInputForeground); 7 | --ide-chat-input-bg: var(--vscode-settings-textInputBackground); 8 | --ide-warning-text: var(--vscode-statusBarItem-warningForeground); 9 | --ide-warning-bg: var(--vscode-statusBarItem-warningBackground); 10 | --ide-error-text: var(--vscode-inputValidation-errorForeground); 11 | --ide-error-bg: var(--vscode-inputValidation-errorBackground); 12 | --ide-border: var(--vscode-panel-border); 13 | --ide-primary-btn-text: var(--vscode-button-foreground); 14 | --ide-primary-btn-bg: var(--vscode-button-background); 15 | --ide-primary-btn-border: var(--vscode-button-border); 16 | 17 | --ide-highlight: orange; 18 | } 19 | 20 | html { 21 | font-size: var(--vscode-font-size); 22 | } 23 | -------------------------------------------------------------------------------- /media/reset.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 13px; 4 | } 5 | 6 | *, 7 | *:before, 8 | *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body, 13 | h1, 14 | h2, 15 | h3, 16 | h4, 17 | h5, 18 | h6, 19 | p, 20 | ol, 21 | ul { 22 | margin: 0; 23 | padding: 0; 24 | font-weight: normal; 25 | } 26 | 27 | img { 28 | max-width: 100%; 29 | height: auto; 30 | } 31 | -------------------------------------------------------------------------------- /media/search.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: transparent; 3 | } 4 | 5 | .hidden { 6 | display: none; 7 | } 8 | 9 | textarea { 10 | font-size: var(--vscode-editor-font-size); 11 | font-family: var(--vscode-editor-font-family); 12 | } 13 | -------------------------------------------------------------------------------- /media/vscode.css: -------------------------------------------------------------------------------- 1 | :root { 2 | /* Spacing. Converted them to rem and added a few more in-between values here */ 3 | --container-padding: 1.25rem; 4 | --input-padding-vertical: 0.375rem; 5 | --input-padding-horizontal: 0.25rem; 6 | --input-margin-vertical: 0.25rem; 7 | --input-margin-horizontal: 0; 8 | --spacing-xxs: 0.25rem; 9 | --spacing-xs: 0.375rem; 10 | --spacing-sm: 0.5rem; 11 | --spacing-md: 0.75rem; 12 | --spacing-lg: 1rem; 13 | --spacing-xl: 1.25rem; 14 | --spacing-xxl: 1.5rem; 15 | } 16 | 17 | body { 18 | padding: 0 var(--container-paddding); 19 | color: var(--vscode-foreground); 20 | font-size: var(--vscode-font-size); 21 | font-weight: var(--vscode-font-weight); 22 | font-family: var(--vscode-font-family); 23 | background-color: var(--vscode-editor-background); 24 | } 25 | 26 | ol, 27 | ul { 28 | padding-left: var(--container-paddding); 29 | } 30 | 31 | body > *, 32 | form > * { 33 | margin-block-start: var(--input-margin-vertical); 34 | margin-block-end: var(--input-margin-vertical); 35 | } 36 | 37 | *:focus { 38 | outline-color: var(--vscode-focusBorder) !important; 39 | } 40 | 41 | a { 42 | color: var(--vscode-textLink-foreground); 43 | } 44 | 45 | a:hover, 46 | a:active { 47 | color: var(--vscode-textLink-activeForeground); 48 | } 49 | 50 | code { 51 | font-size: var(--vscode-editor-font-size); 52 | font-family: var(--vscode-editor-font-family); 53 | } 54 | 55 | button { 56 | border: none; 57 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 58 | width: 100%; 59 | text-align: center; 60 | outline: 1px solid transparent; 61 | outline-offset: 2px !important; 62 | color: var(--vscode-button-foreground); 63 | background: var(--vscode-button-background); 64 | margin-top: 2.5px; 65 | margin-bottom: 2.5px; 66 | } 67 | 68 | button:hover { 69 | cursor: pointer; 70 | background: var(--vscode-button-hoverBackground); 71 | } 72 | 73 | button:focus { 74 | outline-color: var(--vscode-focusBorder); 75 | } 76 | 77 | button.secondary { 78 | color: var(--vscode-button-secondaryForeground); 79 | background: var(--vscode-button-secondaryBackground); 80 | } 81 | 82 | button.secondary:hover { 83 | background: var(--vscode-button-secondaryHoverBackground); 84 | } 85 | 86 | input:not([type="checkbox"]), 87 | textarea { 88 | display: block; 89 | width: 100%; 90 | border: none; 91 | font-family: var(--vscode-font-family); 92 | padding: var(--input-padding-vertical) var(--input-padding-horizontal); 93 | color: var(--vscode-input-foreground); 94 | outline-color: var(--vscode-input-border); 95 | background-color: var(--vscode-input-background); 96 | } 97 | 98 | input::placeholder, 99 | textarea::placeholder { 100 | color: var(--vscode-input-placeholderForeground); 101 | } 102 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sourcery", 3 | "displayName": "Sourcery", 4 | "description": "Instant Code Reviews in your IDE", 5 | "author": "Sourcery AI", 6 | "repository": "https://github.com/sourcery-ai/sourcery-vscode", 7 | "bugs": { 8 | "url": "https://github.com/sourcery-ai/public/issues" 9 | }, 10 | "license": "MIT", 11 | "version": "1.37.1-dev.1", 12 | "publisher": "sourcery", 13 | "icon": "sourcery-icon.png", 14 | "keywords": [ 15 | "ai", 16 | "copilot", 17 | "code review", 18 | "code quality", 19 | "tabnine", 20 | "python", 21 | "javascript", 22 | "jupyter", 23 | "node.js", 24 | "nodejs", 25 | "node", 26 | "refactor", 27 | "refactoring", 28 | "review", 29 | "typescript" 30 | ], 31 | "engines": { 32 | "vscode": "^1.76.0" 33 | }, 34 | "categories": [ 35 | "AI", 36 | "Linters", 37 | "Machine Learning", 38 | "Other", 39 | "Programming Languages", 40 | "Snippets" 41 | ], 42 | "activationEvents": [ 43 | "onLanguage:python", 44 | "onStartupFinished" 45 | ], 46 | "contributes": { 47 | "viewsContainers": { 48 | "activitybar": [ 49 | { 50 | "id": "sourcery-explorer", 51 | "title": "Sourcery", 52 | "icon": "sourcery-icon.png" 53 | } 54 | ] 55 | }, 56 | "views": { 57 | "sourcery-explorer": [ 58 | { 59 | "id": "sourcery.chat", 60 | "name": "Sourcery", 61 | "type": "webview", 62 | "icon": "sourcery-icon.png", 63 | "contextualTitle": "Sourcery" 64 | }, 65 | { 66 | "id": "sourcery.rules", 67 | "name": "Rules", 68 | "type": "webview", 69 | "icon": "sourcery-icon.png", 70 | "contextualTitle": "Rules", 71 | "when": "sourceryRulesActive" 72 | }, 73 | { 74 | "id": "sourcery.rules.treeview", 75 | "name": "Results", 76 | "contextualTitle": "Results", 77 | "when": "sourceryRulesActive" 78 | } 79 | ] 80 | }, 81 | "walkthroughs": [ 82 | { 83 | "id": "sourcery.walkthrough", 84 | "title": "Get started with Sourcery", 85 | "description": "Your first steps to try out the powerful tools and features that the Sourcery extension has to offer!", 86 | "when": "workspacePlatform != webworker", 87 | "steps": [ 88 | { 89 | "id": "sourcery.login", 90 | "title": "Login", 91 | "description": "Unlock all of Sourcery's features, including a 14 day trial of Sourcery Pro\n[Login](command:sourcery.login)", 92 | "media": { 93 | "markdown": "walkthrough/login.md" 94 | }, 95 | "completionEvents": [ 96 | "onContext:sourcery.is_authenticated" 97 | ] 98 | }, 99 | { 100 | "id": "sourcery.openWelcomeFile", 101 | "title": "Explore Sourcery", 102 | "description": "Get started by opening our tutorial \n[Open tutorial](command:toSide:sourcery.welcome.open)\n This will show you how to get code reviews straight in your IDE, as well as use the chat to ask questions \n You can enable or disable the Sourcery Code Lenses above code for easy access. \n[Toggle Code Lens](command:sourcery.chat.toggleCodeLens)\n", 103 | "media": { 104 | "markdown": "walkthrough/view_welcome_file.md" 105 | }, 106 | "completionEvents": [ 107 | "onCommand:sourcery.welcome.open" 108 | ] 109 | }, 110 | { 111 | "id": "sourcery.acceptRecommendation", 112 | "title": "Get Feedback on Your Current Changes", 113 | "description": "Get an instant code review of your current branch or your uncommitted changes to get the type of feedback you expect from a peer review in seconds.", 114 | "media": { 115 | "markdown": "walkthrough/code_reviews.md" 116 | } 117 | }, 118 | { 119 | "id": "sourcery.githubCodeReviews", 120 | "title": "Review Your PRs with Sourcery", 121 | "description": "Add Sourcery to your GitHub repos to get instant, in depth code reviews on every pull request.\n[Add to GitHub](https://github.com/apps/sourcery-ai/installations/new)\n", 122 | "media": { 123 | "markdown": "walkthrough/github_code_reviews.md" 124 | } 125 | }, 126 | { 127 | "id": "sourcery.configuration", 128 | "title": "Configure Sourcery to your needs.", 129 | "description": "Create a configuration file in your project to customise Sourcery.\n[Create config file](command:toSide:sourcery.config.create)\nA very useful setting to start with is the Python version, which lets you tell Sourcery which version you are using.", 130 | "media": { 131 | "markdown": "walkthrough/configuration.md" 132 | }, 133 | "completionEvents": [ 134 | "onCommand:sourcery.config.create" 135 | ] 136 | } 137 | ] 138 | } 139 | ], 140 | "viewsWelcome": [ 141 | { 142 | "view": "sourcery.rules.treeview", 143 | "contents": "Use this view to perform powerful pattern based search/replace of your codebase using Sourcery's rule syntax.\n[Documentation](https://docs.sourcery.ai/Reference/Custom-Rules/pattern-syntax/#custom-rule-pattern-syntax)\n" 144 | } 145 | ], 146 | "commands": [ 147 | { 148 | "command": "sourcery.login", 149 | "title": "Login via browser", 150 | "category": "Sourcery" 151 | }, 152 | { 153 | "command": "sourcery.login.choose", 154 | "title": "Login", 155 | "category": "Sourcery" 156 | }, 157 | { 158 | "command": "sourcery.hub.start", 159 | "title": "Open Sourcery Analytics", 160 | "category": "Sourcery" 161 | }, 162 | { 163 | "command": "sourcery.walkthrough.open", 164 | "title": "Open Walkthough", 165 | "category": "Sourcery" 166 | }, 167 | { 168 | "command": "sourcery.welcome.open", 169 | "title": "Open welcome file", 170 | "category": "Sourcery" 171 | }, 172 | { 173 | "command": "sourcery.refactor.workspace", 174 | "title": "Scan for refactorings", 175 | "category": "Sourcery" 176 | }, 177 | { 178 | "command": "sourcery.scan.rule", 179 | "title": "Scan with specific rule", 180 | "category": "Sourcery" 181 | }, 182 | { 183 | "command": "sourcery.scan.applyRule", 184 | "title": "Apply specific change from rule", 185 | "category": "Sourcery", 186 | "icon": "$(replace)" 187 | }, 188 | { 189 | "command": "sourcery.scan.open", 190 | "title": "Search and Replace across workspace", 191 | "category": "Sourcery" 192 | }, 193 | { 194 | "command": "sourcery.clones.workspace", 195 | "title": "Detect clones", 196 | "category": "Sourcery" 197 | }, 198 | { 199 | "command": "sourcery.config.create", 200 | "title": "Create a default Sourcery configuration file for your project", 201 | "category": "Sourcery" 202 | }, 203 | { 204 | "command": "sourcery.scan.toggleAdvanced", 205 | "title": "Toggle Advanced Mode", 206 | "category": "Sourcery" 207 | }, 208 | { 209 | "command": "sourcery.chat.ask", 210 | "title": "Ask Sourcery", 211 | "category": "Sourcery" 212 | }, 213 | { 214 | "command": "sourcery.chat.toggleCodeLens", 215 | "title": "Toggle Code Lens for Coding Assistant", 216 | "category": "Sourcery" 217 | }, 218 | { 219 | "command": "sourcery.scan.selectLanguage", 220 | "title": "Select Language", 221 | "category": "Sourcery" 222 | }, 223 | { 224 | "command": "sourcery.rule.create", 225 | "title": "Create custom rule", 226 | "category": "Sourcery" 227 | }, 228 | { 229 | "command": "sourcery.effects.enable", 230 | "title": "Show Effects", 231 | "category": "Sourcery" 232 | }, 233 | { 234 | "command": "sourcery.effects.disable", 235 | "title": "Hide Effects", 236 | "category": "Sourcery" 237 | }, 238 | { 239 | "command": "sourcery.review", 240 | "title": "Review", 241 | "category": "Sourcery" 242 | } 243 | ], 244 | "submenus": [ 245 | { 246 | "id": "sourcery.scans", 247 | "label": "Sourcery" 248 | }, 249 | { 250 | "id": "sourcery.editor", 251 | "label": "Sourcery" 252 | } 253 | ], 254 | "menus": { 255 | "view/title": [ 256 | { 257 | "command": "sourcery.scan.selectLanguage", 258 | "when": "view == sourcery.rules", 259 | "group": "navigation" 260 | }, 261 | { 262 | "command": "sourcery.scan.toggleAdvanced", 263 | "when": "view == sourcery.rules", 264 | "group": "navigation" 265 | } 266 | ], 267 | "view/item/context": [ 268 | { 269 | "command": "sourcery.scan.applyRule", 270 | "when": "view == sourcery.rules.treeview && viewItem == editable", 271 | "group": "inline" 272 | } 273 | ], 274 | "editor/context": [ 275 | { 276 | "submenu": "sourcery.editor", 277 | "group": "1_modification@2" 278 | } 279 | ], 280 | "explorer/context": [ 281 | { 282 | "submenu": "sourcery.scans", 283 | "group": "1_modification@1" 284 | } 285 | ], 286 | "sourcery.editor": [ 287 | { 288 | "command": "sourcery.scan.open", 289 | "title": "Create Sourcery rule with this pattern", 290 | "when": "editorLangId==python && editorHasSelection" 291 | }, 292 | { 293 | "command": "sourcery.chat.ask", 294 | "title": "Ask Sourcery", 295 | "when": "editorHasSelection && sourcery.features.coding_assistant" 296 | } 297 | ], 298 | "sourcery.scans": [ 299 | { 300 | "command": "sourcery.refactor.workspace", 301 | "group": "1_modification@1", 302 | "when": "resourceLangId == python || explorerResourceIsFolder" 303 | }, 304 | { 305 | "command": "sourcery.clones.workspace", 306 | "group": "1_modification@2", 307 | "when": "resourceLangId == python || explorerResourceIsFolder" 308 | } 309 | ], 310 | "commandPalette": [ 311 | { 312 | "command": "sourcery.login", 313 | "when": "false" 314 | }, 315 | { 316 | "command": "sourcery.refactor.workspace", 317 | "when": "false" 318 | }, 319 | { 320 | "command": "sourcery.scan.rule", 321 | "when": "false" 322 | }, 323 | { 324 | "command": "sourcery.clones.workspace", 325 | "when": "false" 326 | }, 327 | { 328 | "command": "sourcery.scan.applyRule", 329 | "when": "false" 330 | }, 331 | { 332 | "command": "sourcery.scan.toggleAdvanced", 333 | "when": "false" 334 | }, 335 | { 336 | "command": "sourcery.chat.ask", 337 | "when": "sourcery.features.coding_assistant" 338 | }, 339 | { 340 | "command": "sourcery.scan.selectLanguage", 341 | "when": "false" 342 | }, 343 | { 344 | "command": "sourcery.rule.create", 345 | "when": "false" 346 | }, 347 | { 348 | "command": "sourcery.effects.enable", 349 | "when": "sourcery.features.code_understanding && !sourcery.effects.enabled" 350 | }, 351 | { 352 | "command": "sourcery.effects.disable", 353 | "when": "sourcery.features.code_understanding && sourcery.effects.enabled" 354 | } 355 | ] 356 | }, 357 | "configuration": { 358 | "title": "Sourcery Configuration", 359 | "type": "object", 360 | "properties": { 361 | "sourcery.token": { 362 | "type": "string", 363 | "default": "", 364 | "description": "Sourcery token. You can find your token at https://sourcery.ai/dashboard" 365 | }, 366 | "sourcery.codeLens": { 367 | "type": "boolean", 368 | "default": true, 369 | "description": "Show code lens for Sourcery's coding assistant." 370 | }, 371 | "sourcery.suggestFixes": { 372 | "type": "boolean", 373 | "default": true, 374 | "description": "Suggest AI fixes for all problems." 375 | }, 376 | "sourcery.ruleType.refactorings": { 377 | "type": "boolean", 378 | "default": true, 379 | "description": "Show refactorings." 380 | }, 381 | "sourcery.ruleType.suggestions": { 382 | "type": "boolean", 383 | "default": true, 384 | "description": "Show suggestions." 385 | }, 386 | "sourcery.ruleType.comments": { 387 | "type": "boolean", 388 | "default": true, 389 | "description": "Show comments." 390 | } 391 | } 392 | }, 393 | "keybindings": [ 394 | { 395 | "command": "sourcery.chat.ask", 396 | "key": "ctrl+shift+y", 397 | "mac": "cmd+y", 398 | "when": "editorTextFocus" 399 | } 400 | ] 401 | }, 402 | "main": "./out/extension", 403 | "scripts": { 404 | "vscode:uninstall": "node ./out/uninstall", 405 | "compile": "tsc -watch -p ./", 406 | "vscode": "npm run vscode:prepublish && VSCODE=$(which code-insiders || which code || echo echo ERROR: neither the code nor code-insiders vscode executable is installed); USER=dummy-dont-share-vscode-instance \"$VSCODE\" --user-data-dir=$PWD/.vscode-dev/user-data --verbose --extensionHomePath=$PWD/.vscode-dev/extensions --extensionDevelopmentPath=$PWD $*", 407 | "vscode_local": "SOURCERY_EXECUTABLE=../core/run-sourcery.sh yarn run vscode", 408 | "format": "prettier --write .", 409 | "lint:format": "prettier --check .", 410 | "vscode:prepublish": "npm run esbuild-base -- --minify", 411 | "esbuild-base": "esbuild ./src/extension.ts ./src/uninstall.ts --bundle --outdir=out --external:vscode --format=cjs --platform=node", 412 | "esbuild": "npm run esbuild-base -- --sourcemap", 413 | "esbuild-watch": "npm run esbuild-base -- --sourcemap --watch", 414 | "test-compile": "tsc -p ./" 415 | }, 416 | "devDependencies": { 417 | "@types/marked": "^5.0.0", 418 | "@types/mocha": "^10.0.1", 419 | "@types/node": "^18.15.1", 420 | "@types/vscode": "^1.76.0", 421 | "@typescript-eslint/eslint-plugin": "^5.55.0", 422 | "@typescript-eslint/parser": "^5.55.0", 423 | "@vscode/vsce": "^2.18.0", 424 | "esbuild": "^0.18.10", 425 | "eslint": "^8.36.0", 426 | "mocha": "^10.2.0", 427 | "ovsx": "^0.8.0", 428 | "prettier": "^2.7.1", 429 | "typescript": "^4.9.5", 430 | "version-bump-prompt": "^6.1.0", 431 | "vscode-test": "^1.6.1" 432 | }, 433 | "dependencies": { 434 | "highlight.js": "^11.8.0", 435 | "marked": "^5.1.0", 436 | "marked-highlight": "^2.0.1", 437 | "sanitize-html": "^2.11.0", 438 | "vscode-languageclient": "^8.1.0" 439 | }, 440 | "__metadata": { 441 | "id": "0bb8a841-96c3-4e2d-ab43-cffee71480a4", 442 | "publisherDisplayName": "sourcery", 443 | "publisherId": "076dffab-0485-4bcd-bc6c-62c3a0c7502a", 444 | "isPreReleaseVersion": false 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /sourcery-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/sourcery-icon.png -------------------------------------------------------------------------------- /src/ask-sourcery.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Recipe } from "./chat"; 3 | 4 | export function askSourceryCommand(recipes: Recipe[], contextRange?) { 5 | showAskSourceryQuickPick(recipes).then((result: any) => { 6 | let message; 7 | if ("id" in result) { 8 | // the user selected a specific recipe 9 | message = { 10 | target: "languageServer", 11 | view: "chat", 12 | request: "executeRecipe", 13 | recipeId: result.id, 14 | name: result.label, 15 | trigger: "askSourcery", 16 | }; 17 | } else { 18 | // the user entered some custom text 19 | message = { 20 | target: "languageServer", 21 | view: "chat", 22 | request: "sendMessage", 23 | textContent: result.label, 24 | }; 25 | } 26 | 27 | // Open the chat window. 28 | // This command is automatically generated by the extension based on the 29 | // views in the package.json file. It's used here to make sure that the 30 | // chat window opens after we've "Ask[ed] Sourcery" for something. 31 | vscode.commands.executeCommand("sourcery.chat.focus"); 32 | 33 | vscode.commands.executeCommand("sourcery.coding_assistant", { 34 | message, 35 | ideState: { 36 | selectionLocation: { 37 | range: contextRange, 38 | }, 39 | }, 40 | }); 41 | }); 42 | } 43 | 44 | export function showAskSourceryQuickPick(recipes: Recipe[]) { 45 | return new Promise((resolve) => { 46 | const recipeNames = recipes.map((item) => item.name); 47 | const recipeItems = recipes.map((item) => ({ 48 | label: item.name, 49 | id: item.id, 50 | })); 51 | 52 | const quickPick = vscode.window.createQuickPick(); 53 | quickPick.placeholder = 54 | "Ask Sourcery a question or choose one of these recipes"; 55 | quickPick.items = recipeItems; 56 | 57 | quickPick.onDidAccept(() => { 58 | const selection = quickPick.activeItems[0]; 59 | resolve(selection); 60 | quickPick.hide(); 61 | }); 62 | 63 | quickPick.onDidChangeValue(() => { 64 | // add what the user has typed to the pick list as the first item 65 | if (!recipeNames.includes(quickPick.value)) { 66 | const newItems = [{ label: quickPick.value }, ...recipeItems]; 67 | quickPick.items = newItems; 68 | } 69 | }); 70 | quickPick.onDidHide(() => quickPick.dispose()); 71 | quickPick.show(); 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /src/chat.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { randomBytes } from "crypto"; 3 | import { getCodingAssistantAssetsPath } from "./executable"; 4 | import * as path from "path"; 5 | 6 | export type Recipe = { 7 | id: string; 8 | name: string; 9 | }; 10 | 11 | type DocumentPosition = { 12 | line: number; 13 | character: number; 14 | }; 15 | 16 | type DocumentRange = { 17 | start: DocumentPosition; 18 | end: DocumentPosition; 19 | }; 20 | 21 | // Requests handled by the extension 22 | export type ExtensionMessage = 23 | | { 24 | target: "extension"; 25 | request: "openLink"; 26 | linkType: "url" | "file" | "directory"; 27 | link: string; 28 | documentRange: DocumentRange | null; 29 | } 30 | | { 31 | target: "extension"; 32 | request: "copyToClipboard"; 33 | content: string; 34 | } 35 | | { 36 | target: "extension"; 37 | request: "insertAtCursor"; 38 | content: string; 39 | } 40 | | { 41 | target: "extension"; 42 | request: "updateConfiguration"; 43 | section: "sourcery.codeLens"; 44 | value: boolean; 45 | // https://code.visualstudio.com/api/references/vscode-api#ConfigurationTarget 46 | configurationTarget: vscode.ConfigurationTarget; 47 | }; 48 | 49 | type LanguageServerMessage = { 50 | target: "languageServer"; 51 | // don't care about additional fields 52 | }; 53 | 54 | type OutboundMessage = LanguageServerMessage | ExtensionMessage; 55 | 56 | const getNonce = () => randomBytes(16).toString("base64"); 57 | 58 | export class ChatProvider implements vscode.WebviewViewProvider { 59 | public static readonly viewType = "sourcery.chat"; 60 | 61 | private _view?: vscode.WebviewView; 62 | private _extensionUri: vscode.Uri; 63 | private _assetsUri: vscode.Uri; 64 | private _panel?: vscode.WebviewPanel; // Track the current panel 65 | 66 | public recipes: Recipe[] = []; // this data is used in the "Ask Sourcery" command prompt, so can't be removed 67 | 68 | constructor(private _context: vscode.ExtensionContext) { 69 | this._extensionUri = _context.extensionUri; 70 | const _assetsPath = getCodingAssistantAssetsPath(); 71 | // Parsing the path using `file` rather than `parse` allows it to work on Windows 72 | // See https://github.com/microsoft/vscode-uri/blob/5af89bac2109c0dc7f904b20cc88cac0568747b1/src/uri.ts#L309-L311 73 | this._assetsUri = vscode.Uri.file(_assetsPath); 74 | } 75 | 76 | public async resolveWebviewView( 77 | webviewView: vscode.WebviewView, 78 | context: vscode.WebviewViewResolveContext, 79 | _token: vscode.CancellationToken 80 | ) { 81 | this._view = webviewView; 82 | console.log( 83 | `Initialising webview. Assets URI: ${this._assetsUri}; Extension URI: ${this._extensionUri}` 84 | ); 85 | 86 | webviewView.webview.options = { 87 | // Allow scripts in the webview 88 | enableScripts: true, 89 | localResourceRoots: [this._extensionUri, this._assetsUri], 90 | }; 91 | 92 | webviewView.webview.html = await this._getHtmlForWebview( 93 | webviewView.webview 94 | ); 95 | 96 | webviewView.webview.onDidReceiveMessage( 97 | async ({ message, ...rest }: { message: OutboundMessage }) => { 98 | switch (message.target) { 99 | case "languageServer": 100 | // Language server requests are passed onwards without further changes. 101 | // Note: the command (registered in extension.ts) attaches additional workspace context. 102 | vscode.commands.executeCommand("sourcery.coding_assistant", { 103 | message, 104 | ...rest, 105 | }); 106 | break; 107 | case "extension": 108 | switch (message.request) { 109 | case "openLink": 110 | this.handleOpenLinkRequest(message); 111 | break; 112 | case "copyToClipboard": { 113 | this.handleCopyToClipboardRequest(message); 114 | break; 115 | } 116 | case "insertAtCursor": { 117 | this.handleInsertAtCursorRequest(message); 118 | break; 119 | } 120 | case "updateConfiguration": { 121 | await vscode.workspace 122 | .getConfiguration() 123 | .update( 124 | message.section, 125 | message.value, 126 | message.configurationTarget 127 | ); 128 | } 129 | } 130 | } 131 | } 132 | ); 133 | } 134 | 135 | /** 136 | * Creates a new webview panel with chat functionality or reveals an existing one 137 | */ 138 | public async createOrShowWebviewPanel(): Promise { 139 | if (this._panel) { 140 | // If panel already exists, reveal it 141 | this._panel.reveal(); 142 | return this._panel; 143 | } 144 | 145 | // Create a new panel 146 | const panel = vscode.window.createWebviewPanel( 147 | "sourceryChatPanel", // Unique identifier 148 | "Sourcery Analytics", // Title 149 | vscode.ViewColumn.Active, 150 | { 151 | enableScripts: true, 152 | retainContextWhenHidden: true, 153 | localResourceRoots: [this._extensionUri, this._assetsUri], 154 | } 155 | ); 156 | 157 | // Set the panel's HTML content 158 | panel.webview.html = await this._getHtmlForWebview(panel.webview); 159 | 160 | // Set up message handling 161 | panel.webview.onDidReceiveMessage( 162 | async ({ message, ...rest }: { message: OutboundMessage }) => { 163 | switch (message.target) { 164 | case "languageServer": 165 | vscode.commands.executeCommand("sourcery.coding_assistant", { 166 | message, 167 | ...rest, 168 | }); 169 | break; 170 | case "extension": 171 | switch (message.request) { 172 | case "openLink": 173 | this.handleOpenLinkRequest(message); 174 | break; 175 | case "copyToClipboard": { 176 | this.handleCopyToClipboardRequest(message); 177 | break; 178 | } 179 | case "insertAtCursor": { 180 | this.handleInsertAtCursorRequest(message); 181 | break; 182 | } 183 | case "updateConfiguration": { 184 | await vscode.workspace 185 | .getConfiguration() 186 | .update( 187 | message.section, 188 | message.value, 189 | message.configurationTarget 190 | ); 191 | } 192 | } 193 | } 194 | } 195 | ); 196 | 197 | // Track this panel and handle disposal 198 | this._panel = panel; 199 | panel.onDidDispose(() => { 200 | this._panel = undefined; 201 | }); 202 | 203 | return panel; 204 | } 205 | 206 | public postCommand(command: any) { 207 | // Intercept the addRecipes command 208 | // The "Ask Sourcery" user interface needs to have the recipes available 209 | // and gets them from this class. When the update comes in, we add them to 210 | // the class before forwarding into the web view. 211 | switch (command.command) { 212 | case "recipes/addRecipes": { 213 | this.recipes = command.recipes; 214 | break; 215 | } 216 | } 217 | 218 | // Send to the sidebar view if it exists 219 | if (this._view?.webview) { 220 | this._view.webview.postMessage(command); 221 | } 222 | 223 | // Also send to the panel if it exists 224 | if (this._panel?.webview) { 225 | switch (command.command) { 226 | case "context/update": { 227 | command["updates"]["isAnalyticsPanel"] = true; 228 | break; 229 | } 230 | } 231 | this._panel.webview.postMessage(command); 232 | } 233 | } 234 | 235 | private handleOpenLinkRequest({ 236 | link, 237 | linkType, 238 | documentRange, 239 | }: { 240 | target: "extension"; 241 | request: "openLink"; 242 | linkType: "url" | "file" | "directory"; 243 | link: string; 244 | documentRange: DocumentRange | null; 245 | }) { 246 | if (linkType === "url") { 247 | vscode.env.openExternal(vscode.Uri.parse(link)); 248 | } else { 249 | let filePath = vscode.Uri.file(link); 250 | // Make the path relative to the workspace root 251 | if (!path.isAbsolute(link)) { 252 | const workspaceRoot = vscode.workspace.workspaceFolders?.[0].uri; 253 | if (workspaceRoot) { 254 | filePath = vscode.Uri.joinPath(workspaceRoot, link); 255 | } 256 | } 257 | 258 | if (linkType === "file") { 259 | // Open the file in the editor 260 | vscode.workspace.openTextDocument(filePath).then((doc) => { 261 | vscode.window.showTextDocument(doc).then((editor) => { 262 | if (documentRange) { 263 | editor.selection = new vscode.Selection( 264 | documentRange.start.line, 265 | documentRange.start.character, 266 | documentRange.end.line, 267 | documentRange.end.character 268 | ); 269 | editor.revealRange( 270 | editor.selection, 271 | vscode.TextEditorRevealType.InCenter 272 | ); 273 | } 274 | }); 275 | }); 276 | } else { 277 | // Reveal the directory in the explorer 278 | vscode.commands.executeCommand("revealInExplorer", filePath).then(() => 279 | // This is a little hack. 280 | // 281 | // There's some issue with the revealInExplorer command which means when the panel changes 282 | // (e.g. from sourcery to the explorer) the instruction to actually focus the file path 283 | // seems to get dropped. By calling it again (after the first command succeeds) we can 284 | // make sure the file actually gets navigated to. 285 | vscode.commands.executeCommand("revealInExplorer", filePath) 286 | ); 287 | } 288 | } 289 | } 290 | 291 | private handleCopyToClipboardRequest({ 292 | content, 293 | }: { 294 | target: "extension"; 295 | request: "copyToClipboard"; 296 | content: string; 297 | }) { 298 | vscode.env.clipboard.writeText(content); 299 | } 300 | 301 | private handleInsertAtCursorRequest({ 302 | content, 303 | }: { 304 | target: "extension"; 305 | request: "insertAtCursor"; 306 | content: string; 307 | }) { 308 | const activeEditor = vscode.window.activeTextEditor; 309 | if (!activeEditor) { 310 | vscode.window.showErrorMessage("No active text editor!"); 311 | return; 312 | } 313 | 314 | activeEditor.edit((editBuilder) => { 315 | // Thank you coding assistant! 316 | if (!activeEditor.selection.isEmpty) { 317 | editBuilder.replace(activeEditor.selection, content); 318 | } else { 319 | editBuilder.insert(activeEditor.selection.active, content); 320 | } 321 | }); 322 | } 323 | 324 | // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. 325 | private async _getHtmlForWebview(webview: vscode.Webview) { 326 | // The baseSrc is just a URI declaring the root of the web app. 327 | // This is relevant for the interaction between the script and the stylesheet. 328 | // It is used in the `` tag below - see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base 329 | // This is a synthetic URI and doesn't need to refer to an actual file. 330 | const baseSrc = webview.asWebviewUri( 331 | vscode.Uri.joinPath(this._assetsUri, "..", "index.html") 332 | ); 333 | 334 | // This is the URI to the main application script. 335 | // We bundle this as a single javascript file and inject it directly into the HTML below, alongside the random nonce. 336 | // Note that we also include a hard-coded script to attach the VSCode API directly to the window. 337 | const appScriptSrc = webview.asWebviewUri( 338 | vscode.Uri.joinPath(this._assetsUri, "index.js") 339 | ); 340 | 341 | // This is the URI to the main application CSS file. 342 | // These are styles and themes handled by the web app itself. 343 | // We need to provide some additional CSS, using the `ide-styles.css` file below. 344 | // This is to ensure the web app's style matches that of the IDE. 345 | const appStylesSrc = webview.asWebviewUri( 346 | vscode.Uri.joinPath(this._assetsUri, "index.css") 347 | ); 348 | 349 | // This is the URI to the IDE styles. 350 | // This should be bundled as part of the extension (rather than the web app) and defines several colours to get the web app to match the IDE style. 351 | const ideStylesSrc = webview.asWebviewUri( 352 | vscode.Uri.joinPath(this._extensionUri, "media", "ide-styles.css") 353 | ); 354 | 355 | const appScriptNonce = getNonce(); 356 | const apiInjectionNonce = getNonce(); 357 | 358 | // language=html 359 | return ` 360 | 361 | 362 | 363 | 364 | 365 | Sourcery 366 | 367 | 368 | 369 | 370 | 371 |
372 | 373 | 393 | 394 | 395 | `; 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /src/executable.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as path from "path"; 4 | import * as fs from "fs"; 5 | 6 | export function getCodingAssistantAssetsPath(): string { 7 | // Allow complete local override 8 | if (process.env.SOURCERY_CODING_ASSISTANT_ASSETS_PATH) { 9 | console.log( 10 | "Environment-provided Sourcery Coding Assistant Assets Path: ", 11 | process.env.SOURCERY_CODING_ASSISTANT_ASSETS_PATH 12 | ); 13 | return process.env.SOURCERY_CODING_ASSISTANT_ASSETS_PATH; 14 | } 15 | 16 | // Otherwise, production subdirectory should be set up to match development subdirectory 17 | const executablePath = getExecutablePath(); 18 | const absoluteExecutablePath = path.isAbsolute(executablePath) 19 | ? executablePath 20 | : path.join(__dirname, "..", executablePath); 21 | 22 | let codingAssistantAssetsPath = path.join( 23 | absoluteExecutablePath, 24 | "..", 25 | "coding-assistant-app", 26 | "dist", 27 | "assets" 28 | ); 29 | 30 | console.log( 31 | "Derived Sourcery Coding Assistant Assets Path: ", 32 | codingAssistantAssetsPath 33 | ); 34 | 35 | return codingAssistantAssetsPath; 36 | } 37 | 38 | export function getExecutablePath(): string { 39 | if (process.env.SOURCERY_EXECUTABLE) { 40 | console.log( 41 | "Environment-provided Sourcery Executable Path: ", 42 | process.env.SOURCERY_EXECUTABLE 43 | ); 44 | return process.env.SOURCERY_EXECUTABLE; 45 | } 46 | const sourcery_binaries = path.join(__dirname, "..", "sourcery_binaries"); 47 | const activePath = path.join(sourcery_binaries, "active"); 48 | if (fs.existsSync(activePath)) { 49 | if (process.platform == "win32") { 50 | return path.join(activePath, "sourcery.exe"); 51 | } else if (process.platform == "darwin") { 52 | return path.join(activePath, "sourcery"); 53 | } else { 54 | // Assume everything else is linux compatible 55 | return path.join(activePath, "sourcery"); 56 | } 57 | } else { 58 | if (process.platform == "win32") { 59 | return path.join(sourcery_binaries, "install/win/sourcery.exe"); 60 | } else if (process.platform == "darwin") { 61 | return path.join(sourcery_binaries, "install/mac/sourcery"); 62 | } else { 63 | // Assume everything else is linux compatible 64 | return path.join(sourcery_binaries, "install/linux/sourcery"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/extension.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as path from "path"; 4 | import { getExecutablePath } from "./executable"; 5 | 6 | import * as vscode from "vscode"; 7 | import { 8 | commands, 9 | env, 10 | ExtensionContext, 11 | extensions, 12 | Range, 13 | StatusBarAlignment, 14 | TextEditorRevealType, 15 | TreeItem, 16 | TreeView, 17 | Uri, 18 | version, 19 | ViewColumn, 20 | WebviewPanel, 21 | window, 22 | workspace, 23 | } from "vscode"; 24 | import { 25 | ExecuteCommandParams, 26 | ExecuteCommandRequest, 27 | LanguageClient, 28 | LanguageClientOptions, 29 | ServerOptions, 30 | URI, 31 | } from "vscode-languageclient/node"; 32 | import { RuleInputProvider } from "./rule-search"; 33 | import { ScanResultProvider } from "./rule-search-results"; 34 | import { ChatProvider } from "./chat"; 35 | import { askSourceryCommand } from "./ask-sourcery"; 36 | 37 | function createLangServer(): LanguageClient { 38 | const token = workspace.getConfiguration("sourcery").get("token"); 39 | const showCodeLens = workspace 40 | .getConfiguration("sourcery") 41 | .get("codeLens"); 42 | 43 | const suggestFixes = workspace 44 | .getConfiguration("sourcery") 45 | .get("suggestFixes"); 46 | const packageJson = extensions.getExtension("sourcery.sourcery").packageJSON; 47 | const extensionVersion = packageJson.version; 48 | 49 | const command = getExecutablePath(); 50 | 51 | const serverOptions: ServerOptions = { 52 | command, 53 | args: ["lsp"], 54 | options: { 55 | env: { 56 | PYTHONHASHSEED: "0", 57 | ...process.env, 58 | }, 59 | }, 60 | }; 61 | 62 | const clientOptions: LanguageClientOptions = { 63 | markdown: { 64 | isTrusted: true, 65 | }, 66 | diagnosticCollectionName: "sourcery", 67 | documentSelector: [ 68 | { language: "*", scheme: "file" }, 69 | { language: "*", scheme: "untitled" }, 70 | { language: "*", scheme: "vscode-notebook-cell" }, 71 | ], 72 | synchronize: { 73 | configurationSection: "sourcery", 74 | }, 75 | initializationOptions: { 76 | token: token, 77 | editor_version: "vscode " + version, 78 | extension_version: extensionVersion, 79 | telemetry_enabled: env.isTelemetryEnabled, 80 | show_code_lens: showCodeLens, 81 | suggest_ai_fixes: suggestFixes, 82 | }, 83 | }; 84 | 85 | return new LanguageClient(command, serverOptions, clientOptions); 86 | } 87 | 88 | export function getSelectedText(): string | null { 89 | const editor = window.activeTextEditor; 90 | 91 | if (editor) { 92 | return editor.document.getText(editor.selection); 93 | } 94 | 95 | return null; 96 | } 97 | 98 | function showSourceryStatusBarItem(context: ExtensionContext) { 99 | // Create the status bar 100 | const myStatusBarItem = window.createStatusBarItem(StatusBarAlignment.Left); 101 | myStatusBarItem.command = "sourcery.hub.start"; 102 | myStatusBarItem.text = "Sourcery Analytics"; 103 | myStatusBarItem.tooltip = "See analytics for your repo"; 104 | context.subscriptions.push(myStatusBarItem); 105 | myStatusBarItem.show(); 106 | } 107 | 108 | function registerNotifications({ 109 | languageClient, 110 | scanResultTree, 111 | scanResultTreeView, 112 | chatProvider, 113 | context, 114 | }: { 115 | languageClient: LanguageClient; 116 | scanResultTree: ScanResultProvider; 117 | scanResultTreeView: TreeView; 118 | chatProvider: ChatProvider; 119 | context: ExtensionContext; 120 | }) { 121 | languageClient.onNotification("sourcery/vscode/executeCommand", (params) => { 122 | const command = params["command"]; 123 | const args = params["arguments"] || []; 124 | commands.executeCommand(command, ...args); 125 | }); 126 | 127 | languageClient.onNotification("sourcery/vscode/showSettings", (params) => { 128 | const section = params && params.section ? params.section : "sourcery"; 129 | commands.executeCommand("workbench.action.openSettings", section); 130 | }); 131 | 132 | languageClient.onNotification("sourcery/vscode/scanResults", (params) => { 133 | if (params.diagnostics.length > 0) { 134 | scanResultTree.update(params); 135 | } 136 | scanResultTreeView.title = 137 | "Results - " + params.results + " found in " + params.files + " files."; 138 | }); 139 | 140 | languageClient.onNotification("sourcery/codingAssistant", (params) => { 141 | chatProvider.postCommand(params); 142 | }); 143 | 144 | languageClient.onNotification("sourcery/vscode/viewProblems", () => { 145 | commands.executeCommand("workbench.actions.view.problems"); 146 | }); 147 | 148 | languageClient.onNotification("sourcery/vscode/accept_recommendation", () => { 149 | commands.executeCommand( 150 | "setContext", 151 | "acceptRecommendationContextKey", 152 | true 153 | ); 154 | }); 155 | 156 | languageClient.onNotification("sourcery/vscode/showUrl", (params) => { 157 | env.openExternal(Uri.parse(params["url"])); 158 | }); 159 | 160 | languageClient.onNotification("sourcery/vscode/showWelcomeFile", () => { 161 | openWelcomeFile(context); 162 | }); 163 | } 164 | 165 | function registerCommands( 166 | context: ExtensionContext, 167 | riProvider: RuleInputProvider, 168 | languageClient: LanguageClient, 169 | tree: ScanResultProvider, 170 | treeView: TreeView, 171 | hubWebviewPanel: WebviewPanel, 172 | chatProvider: ChatProvider 173 | ) { 174 | context.subscriptions.push( 175 | vscode.window.registerWebviewViewProvider( 176 | RuleInputProvider.viewType, 177 | riProvider, 178 | { webviewOptions: { retainContextWhenHidden: true } } 179 | ) 180 | ); 181 | 182 | context.subscriptions.push( 183 | commands.registerCommand("sourcery.selectCode", (open_uri, start, end) => { 184 | workspace.openTextDocument(open_uri).then((doc) => { 185 | window.showTextDocument(doc).then((e) => { 186 | e.selection = new vscode.Selection(start, end); 187 | e.revealRange(new Range(start, end), TextEditorRevealType.InCenter); 188 | }); 189 | }); 190 | }) 191 | ); 192 | 193 | context.subscriptions.push( 194 | commands.registerCommand("sourcery.scan.toggleAdvanced", () => { 195 | // Tell the rules webview to toggle 196 | riProvider.toggle(); 197 | }) 198 | ); 199 | 200 | context.subscriptions.push( 201 | commands.registerCommand("sourcery.chat.ask", (arg?) => { 202 | let contextRange = arg && "start" in arg ? arg : null; 203 | askSourceryCommand(chatProvider.recipes, contextRange); 204 | }) 205 | ); 206 | 207 | context.subscriptions.push( 208 | commands.registerCommand("sourcery.chat.toggleCodeLens", () => { 209 | const config = vscode.workspace.getConfiguration(); 210 | const currentValue = config.get("sourcery.codeLens"); 211 | config.update( 212 | "sourcery.codeLens", 213 | !currentValue, 214 | vscode.ConfigurationTarget.Global 215 | ); 216 | }) 217 | ); 218 | 219 | context.subscriptions.push( 220 | commands.registerCommand("sourcery.scan.selectLanguage", () => { 221 | const items = ["python", "javascript"]; 222 | 223 | window 224 | .showQuickPick(items, { 225 | canPickMany: false, 226 | placeHolder: "Select language", 227 | }) 228 | .then((selected) => { 229 | riProvider.setLanguage(selected); 230 | }); 231 | }) 232 | ); 233 | 234 | // Enable/disable effects 235 | context.subscriptions.push( 236 | commands.registerCommand("sourcery.effects.enable", () => 237 | effects_set_enabled(true) 238 | ) 239 | ); 240 | context.subscriptions.push( 241 | commands.registerCommand("sourcery.effects.disable", () => 242 | effects_set_enabled(false) 243 | ) 244 | ); 245 | function effects_set_enabled(enabled: boolean) { 246 | vscode.commands.executeCommand( 247 | "setContext", 248 | "sourcery.effects.enabled", 249 | enabled 250 | ); 251 | let request: ExecuteCommandParams = { 252 | command: "sourcery.effects.set_enabled", 253 | arguments: [enabled], 254 | }; 255 | languageClient.sendRequest(ExecuteCommandRequest.type, request); 256 | } 257 | 258 | context.subscriptions.push( 259 | commands.registerCommand("sourcery.scan.applyRule", (entry) => { 260 | workspace.openTextDocument(entry.resourceUri).then((doc) => { 261 | window.showTextDocument(doc).then((e) => { 262 | e.revealRange( 263 | new Range(entry.startPosition, entry.endPosition), 264 | TextEditorRevealType.InCenter 265 | ); 266 | for (let edit of entry.edits) { 267 | const workspaceEdit = new vscode.WorkspaceEdit(); 268 | 269 | const textEdit = new vscode.TextEdit( 270 | new vscode.Range( 271 | edit.range.start.line, 272 | edit.range.start.character, 273 | edit.range.end.line, 274 | edit.range.end.character 275 | ), 276 | edit.newText 277 | ); 278 | 279 | // Apply the edit to the current workspace 280 | workspaceEdit.set(entry.resourceUri, [textEdit]); 281 | 282 | workspace.applyEdit(workspaceEdit); 283 | } 284 | }); 285 | }); 286 | }) 287 | ); 288 | 289 | context.subscriptions.push( 290 | commands.registerCommand("sourcery.welcome.open", () => { 291 | openWelcomeFile(context); 292 | }) 293 | ); 294 | 295 | context.subscriptions.push( 296 | commands.registerCommand("sourcery.scan.open", () => { 297 | vscode.commands.executeCommand("setContext", "sourceryRulesActive", true); 298 | 299 | vscode.commands.executeCommand("sourcery.rules.focus").then(() => { 300 | const input = getSelectedText(); 301 | riProvider.setPattern(input); 302 | }); 303 | }) 304 | ); 305 | 306 | context.subscriptions.push( 307 | commands.registerCommand("sourcery.walkthrough.open", () => { 308 | openWelcomeFile(context); 309 | commands.executeCommand( 310 | "workbench.action.openWalkthrough", 311 | "sourcery.sourcery#sourcery.walkthrough", 312 | true 313 | ); 314 | }) 315 | ); 316 | 317 | context.subscriptions.push( 318 | commands.registerCommand("sourcery.config.create", () => { 319 | let request: ExecuteCommandParams = { 320 | command: "config/create", 321 | arguments: [], 322 | }; 323 | languageClient 324 | .sendRequest(ExecuteCommandRequest.type, request) 325 | .then((values) => { 326 | openDocument(path.join(workspace.rootPath, ".sourcery.yaml")); 327 | }); 328 | }) 329 | ); 330 | 331 | context.subscriptions.push( 332 | commands.registerCommand( 333 | "sourcery.rule.create", 334 | (rule, advanced: boolean, language: string) => { 335 | vscode.window 336 | .showInputBox({ 337 | title: "What would you like to call your rule?", 338 | prompt: 339 | "This should be lowercase, with words separated by hyphens (e.g. my-brilliant-rule)", 340 | }) 341 | .then((name) => { 342 | if (name) { 343 | let request: ExecuteCommandParams = { 344 | command: "config/rule/create", 345 | arguments: [ 346 | { 347 | rule_id: name, 348 | rule: rule, 349 | inplace: false, 350 | advanced: advanced, 351 | language: language, 352 | }, 353 | ], 354 | }; 355 | languageClient 356 | .sendRequest(ExecuteCommandRequest.type, request) 357 | .then((result) => { 358 | const openPath = Uri.file(result); 359 | workspace.openTextDocument(openPath).then((doc) => { 360 | window.showTextDocument(doc); 361 | }); 362 | }); 363 | } 364 | }); 365 | } 366 | ) 367 | ); 368 | 369 | context.subscriptions.push( 370 | commands.registerCommand( 371 | "sourcery.refactor.workspace", 372 | (resource: Uri, selected?: Uri[]) => { 373 | let request: ExecuteCommandParams = { 374 | command: "refactoring/scan", 375 | arguments: [ 376 | { 377 | uri: resource, 378 | all_uris: selected, 379 | }, 380 | ], 381 | }; 382 | languageClient.sendRequest(ExecuteCommandRequest.type, request); 383 | } 384 | ) 385 | ); 386 | 387 | context.subscriptions.push( 388 | commands.registerCommand( 389 | "sourcery.coding_assistant", 390 | ({ 391 | message, 392 | ideState, 393 | }: { 394 | message: any; // we don't care about the details of the message 395 | // the request may override any of the IDE fields if necessary 396 | ideState?: Partial; 397 | }) => { 398 | const localIDEState = getLocalIDEState(); 399 | 400 | let params: ExecuteCommandParams = { 401 | command: "sourcery.coding_assistant", 402 | arguments: [ 403 | { 404 | message, 405 | ideState: { 406 | ...localIDEState, 407 | ...ideState, 408 | selectionLocation: { 409 | ...localIDEState.selectionLocation, 410 | ...ideState?.selectionLocation, 411 | }, 412 | }, 413 | }, 414 | ], 415 | }; 416 | languageClient.sendRequest(ExecuteCommandRequest.type, params); 417 | } 418 | ) 419 | ); 420 | 421 | context.subscriptions.push( 422 | commands.registerCommand( 423 | "sourcery.scan.rule", 424 | (rule, advanced: boolean, fix: boolean, language: string) => { 425 | if (fix) { 426 | vscode.window 427 | .showInformationMessage("Are you sure?", "Yes", "No") 428 | .then((answer) => { 429 | if (answer === "Yes") { 430 | runScan(rule, advanced, fix, language); 431 | } 432 | }); 433 | } else { 434 | runScan(rule, advanced, fix, language); 435 | } 436 | } 437 | ) 438 | ); 439 | 440 | function runScan(rule, advanced: boolean, fix: boolean, language: string) { 441 | tree.clear(); 442 | treeView.title = "Results"; 443 | let request: ExecuteCommandParams = { 444 | command: "sourcery/rule/scan", 445 | arguments: [ 446 | { 447 | rule: rule, 448 | rule_id: "test-rule", 449 | advanced: advanced, 450 | inplace: fix, 451 | language: language, 452 | }, 453 | ], 454 | }; 455 | languageClient.sendRequest(ExecuteCommandRequest.type, request); 456 | } 457 | 458 | context.subscriptions.push( 459 | commands.registerCommand( 460 | "sourcery.clones.workspace", 461 | (resource: Uri, selected?: Uri[]) => { 462 | let request: ExecuteCommandParams = { 463 | command: "clone/scan", 464 | arguments: [ 465 | { 466 | uri: resource, 467 | all_uris: selected, 468 | }, 469 | ], 470 | }; 471 | languageClient.sendRequest(ExecuteCommandRequest.type, request); 472 | } 473 | ) 474 | ); 475 | 476 | // Create the "open hub" command 477 | // This is activated from the status bar (see below) 478 | context.subscriptions.push( 479 | commands.registerCommand("sourcery.hub.start", async () => { 480 | // Instruct the language server to start the hub server 481 | languageClient.sendRequest(ExecuteCommandRequest.type, { 482 | command: "sourcery.startHub", 483 | arguments: [], 484 | }); 485 | 486 | // Create or show the chat panel 487 | await chatProvider.createOrShowWebviewPanel(); 488 | }) 489 | ); 490 | } 491 | 492 | export function activate(context: ExtensionContext) { 493 | const languageClient = createLangServer(); 494 | let hubWebviewPanel: WebviewPanel | undefined = undefined; 495 | 496 | let tree = new ScanResultProvider(); 497 | 498 | let treeView = vscode.window.createTreeView("sourcery.rules.treeview", { 499 | treeDataProvider: tree, 500 | }); 501 | 502 | const riProvider = new RuleInputProvider(context); 503 | 504 | const chatProvider = new ChatProvider(context); 505 | 506 | context.subscriptions.push( 507 | vscode.window.registerWebviewViewProvider( 508 | ChatProvider.viewType, 509 | chatProvider, 510 | { webviewOptions: { retainContextWhenHidden: true } } 511 | ) 512 | ); 513 | 514 | registerCommands( 515 | context, 516 | riProvider, 517 | languageClient, 518 | tree, 519 | treeView, 520 | hubWebviewPanel, 521 | chatProvider 522 | ); 523 | 524 | showSourceryStatusBarItem(context); 525 | 526 | languageClient.start().then(() => { 527 | registerNotifications({ 528 | languageClient, 529 | scanResultTree: tree, 530 | scanResultTreeView: treeView, 531 | chatProvider, 532 | context, 533 | }); 534 | }); 535 | } 536 | 537 | function openWelcomeFile(context: ExtensionContext) { 538 | openDocument(path.join(context.extensionPath, "welcome-to-sourcery.py")); 539 | } 540 | 541 | function openDocument(document_path: string) { 542 | const openPath = Uri.file(document_path); 543 | workspace.openTextDocument(openPath).then((doc) => { 544 | window.showTextDocument(doc); 545 | }); 546 | } 547 | 548 | type IDEState = { 549 | activeFile?: URI; 550 | allOpenFiles: URI[]; 551 | selectionLocation?: { 552 | uri: URI; 553 | range: Range; 554 | }; 555 | }; 556 | 557 | function getLocalIDEState(): IDEState { 558 | const { activeTextEditor } = window; 559 | const activeFile = activeTextEditor?.document.uri.toString(); 560 | const allOpenFiles = vscode.window.tabGroups.all.flatMap((tabGroup) => 561 | tabGroup.tabs 562 | .map((tab) => tab.input) 563 | .filter( 564 | (input): input is vscode.TabInputText => 565 | input instanceof vscode.TabInputText 566 | ) 567 | .map((input) => input.uri.toString()) 568 | ); 569 | const selectionLocation = activeTextEditor 570 | ? { 571 | uri: activeTextEditor.document.uri.toString(), 572 | range: activeTextEditor.selection, 573 | } 574 | : undefined; 575 | 576 | return { activeFile, allOpenFiles, selectionLocation }; 577 | } 578 | -------------------------------------------------------------------------------- /src/renderMarkdownMessage.ts: -------------------------------------------------------------------------------- 1 | import { marked } from "marked"; 2 | 3 | import { markedHighlight } from "marked-highlight"; 4 | import hljs from "highlight.js"; 5 | import sanitizeHtml from "sanitize-html"; 6 | 7 | marked.use( 8 | markedHighlight({ 9 | langPrefix: "hljs language-", 10 | highlight(code, lang) { 11 | const language = hljs.getLanguage(lang) ? lang : "plaintext"; 12 | return hljs.highlight(code, { language }).value; 13 | }, 14 | }) 15 | ); 16 | export function renderMarkdownMessage(message: string) { 17 | // Send the whole message we've been streamed so far to the webview, 18 | // after converting from markdown to html 19 | 20 | const rendered = marked(message, { 21 | gfm: true, 22 | breaks: true, 23 | mangle: false, 24 | headerIds: false, 25 | }); 26 | 27 | // Allow any classes on span and code blocks or highlightjs classes get removed 28 | return sanitizeHtml(rendered, { 29 | allowedTags: sanitizeHtml.defaults.allowedTags.concat([ 30 | "details", 31 | "summary", 32 | ]), 33 | allowedClasses: { span: false, code: false }, 34 | allowedSchemes: sanitizeHtml.defaults.allowedSchemes.concat(["file"]), 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /src/rule-search-results.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { Position, TreeItemLabel, Uri } from "vscode"; 3 | 4 | class ScanResult extends vscode.TreeItem { 5 | children: undefined; 6 | startPosition: Position; 7 | endPosition: Position; 8 | edits: any[]; 9 | 10 | constructor( 11 | label: TreeItemLabel, 12 | uri: Uri, 13 | startPosition: Position, 14 | endPosition: Position, 15 | edits 16 | ) { 17 | super(label); 18 | this.resourceUri = uri; 19 | this.startPosition = startPosition; 20 | this.endPosition = endPosition; 21 | this.edits = edits; 22 | if (edits.length > 0) { 23 | this.contextValue = "editable"; 24 | } 25 | } 26 | } 27 | 28 | class FileResults extends vscode.TreeItem { 29 | children: ScanResult[] | undefined; 30 | startPosition: Position; 31 | endPosition: Position; 32 | constructor(label: string | undefined, uri: Uri, children?: ScanResult[]) { 33 | super( 34 | label, 35 | children === undefined 36 | ? vscode.TreeItemCollapsibleState.None 37 | : vscode.TreeItemCollapsibleState.Expanded 38 | ); 39 | this.children = children; 40 | this.resourceUri = uri; 41 | this.startPosition = new Position(0, 0); 42 | this.endPosition = new Position(0, 0); 43 | 44 | this.description = true; 45 | } 46 | } 47 | 48 | export class ScanResultProvider 49 | implements vscode.TreeDataProvider 50 | { 51 | // will hold our tree view data 52 | data: FileResults[] = []; 53 | 54 | private _onDidChangeTreeData: vscode.EventEmitter< 55 | FileResults | undefined | null | void 56 | > = new vscode.EventEmitter(); 57 | 58 | readonly onDidChangeTreeData: vscode.Event< 59 | FileResults | undefined | null | void 60 | > = this._onDidChangeTreeData.event; 61 | 62 | constructor() { 63 | this.data = []; 64 | } 65 | 66 | getTreeItem( 67 | element: FileResults | ScanResult 68 | ): vscode.TreeItem | Thenable { 69 | element.command = { 70 | command: "sourcery.selectCode", 71 | title: "Open", 72 | arguments: [ 73 | element.resourceUri, 74 | element.startPosition, 75 | element.endPosition, 76 | [], 77 | "goto", 78 | ], 79 | }; 80 | return element; 81 | } 82 | 83 | getChildren( 84 | element?: FileResults | ScanResult | undefined 85 | ): vscode.ProviderResult { 86 | if (element === undefined) { 87 | return this.data; 88 | } 89 | return element.children; 90 | } 91 | 92 | update(params): void { 93 | const uri = Uri.parse(params.uri); 94 | const scanResults = []; 95 | for (let result of params.diagnostics) { 96 | scanResults.push( 97 | new ScanResult( 98 | { 99 | label: result.first_line_code, 100 | highlights: [result.first_line_highlight], 101 | }, 102 | uri, 103 | new Position(result.range.start.line, result.range.start.character), 104 | new Position(result.range.end.line, result.range.end.character), 105 | result.text_edits 106 | ) 107 | ); 108 | } 109 | this.data.push(new FileResults(undefined, uri, scanResults)); 110 | this._onDidChangeTreeData.fire(); 111 | } 112 | 113 | clear(): void { 114 | this.data = []; 115 | this._onDidChangeTreeData.fire(); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/rule-search.ts: -------------------------------------------------------------------------------- 1 | import * as vscode from "vscode"; 2 | import { randomBytes } from "crypto"; 3 | import { getSelectedText } from "./extension"; 4 | 5 | export class RuleInputProvider implements vscode.WebviewViewProvider { 6 | public static readonly viewType = "sourcery.rules"; 7 | 8 | private _view?: vscode.WebviewView; 9 | 10 | private _extensionUri: vscode.Uri; 11 | 12 | private _languageId: string; 13 | 14 | constructor(private _context: vscode.ExtensionContext) { 15 | this._extensionUri = _context.extensionUri; 16 | } 17 | 18 | public toggle() { 19 | this._view.webview.postMessage({ command: "toggle" }); 20 | } 21 | 22 | public setLanguage(language) { 23 | this._languageId = language; 24 | this._setTitle(); 25 | } 26 | 27 | public setPattern(pattern) { 28 | this._view.webview.postMessage({ command: "setPattern", pattern: pattern }); 29 | } 30 | 31 | public async resolveWebviewView( 32 | webviewView: vscode.WebviewView, 33 | context: vscode.WebviewViewResolveContext, 34 | _token: vscode.CancellationToken 35 | ) { 36 | this._view = webviewView; 37 | 38 | webviewView.webview.options = { 39 | // Allow scripts in the webview 40 | enableScripts: true, 41 | 42 | localResourceRoots: [this._extensionUri], 43 | }; 44 | 45 | this._setViewState(); 46 | 47 | webviewView.onDidChangeVisibility(() => { 48 | if (this._view.visible) { 49 | this._setViewState(); 50 | } 51 | }); 52 | 53 | webviewView.webview.html = await this._getHtmlForWebview( 54 | webviewView.webview 55 | ); 56 | 57 | const input = getSelectedText(); 58 | this.setPattern(input); 59 | 60 | webviewView.webview.onDidReceiveMessage(async (data) => { 61 | switch (data.type) { 62 | case "scan": { 63 | vscode.commands.executeCommand( 64 | "sourcery.scan.rule", 65 | data.rule, 66 | data.advanced, 67 | false, 68 | this._languageId 69 | ); 70 | break; 71 | } 72 | case "replace": { 73 | vscode.commands.executeCommand( 74 | "sourcery.scan.rule", 75 | data.rule, 76 | data.advanced, 77 | true, 78 | this._languageId 79 | ); 80 | break; 81 | } 82 | case "save": { 83 | vscode.commands.executeCommand( 84 | "sourcery.rule.create", 85 | data.rule, 86 | data.advanced, 87 | this._languageId 88 | ); 89 | break; 90 | } 91 | } 92 | }); 93 | } 94 | 95 | private _setViewState() { 96 | this._languageId = this._resolveLanguage(); 97 | this._setTitle(); 98 | } 99 | 100 | private _setTitle() { 101 | this._view.title = "Rules - " + this._languageId; 102 | } 103 | 104 | private _resolveLanguage() { 105 | // Get the active editor in the workspace 106 | const activeEditor = vscode.window.activeTextEditor; 107 | 108 | // Check if there's an active editor 109 | if (activeEditor) { 110 | const languageId = activeEditor.document.languageId; 111 | switch (languageId) { 112 | case "python": { 113 | return languageId; 114 | } 115 | case "typescript": { 116 | return languageId; 117 | } 118 | case "typescriptreact": { 119 | return "typescript"; 120 | } 121 | case "javascriptreact": { 122 | return "javascript"; 123 | } 124 | case "javascript": { 125 | return languageId; 126 | } 127 | } 128 | return "python"; 129 | } 130 | 131 | // Default to python 132 | return "python"; 133 | } 134 | 135 | private async _getHtmlForWebview(webview: vscode.Webview) { 136 | // Get the local path to main script run in the webview, then convert it to a uri we can use in the webview. 137 | const scriptUri = webview.asWebviewUri( 138 | vscode.Uri.joinPath(this._extensionUri, "src", "webview", "search.js") 139 | ); 140 | 141 | // Do the same for the stylesheet. 142 | const styleResetUri = webview.asWebviewUri( 143 | vscode.Uri.joinPath(this._extensionUri, "media", "reset.css") 144 | ); 145 | const styleVSCodeUri = webview.asWebviewUri( 146 | vscode.Uri.joinPath(this._extensionUri, "media", "vscode.css") 147 | ); 148 | const styleMainUri = webview.asWebviewUri( 149 | vscode.Uri.joinPath(this._extensionUri, "media", "search.css") 150 | ); 151 | // Use a nonce to only allow a specific script to be run. 152 | const nonce = randomBytes(16).toString("base64"); 153 | 154 | /* eslint-disable @typescript-eslint/naming-convention */ 155 | let cspStr = Object.entries({ 156 | "default-src": "'none'", 157 | "style-src": `${webview.cspSource + ` 'nonce-${nonce}'`}`, 158 | "script-src": `'nonce-${nonce}'`, 159 | "img-src": "* 'self' https:;", 160 | }) 161 | .map(([key, value]) => { 162 | return `${key} ${value}`; 163 | }) 164 | .join("; "); 165 | /* eslint-enable @typescript-eslint/naming-convention */ 166 | 167 | return ` 168 | 169 | 170 | 171 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 |
184 |
185 | 186 | 190 | 191 | 195 | 196 | 200 |
201 | 209 |
210 |
211 | 212 |
213 |
214 | 215 |
216 |
217 | 218 |
219 | 220 | 221 | `; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/uninstall.ts: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | import * as path from "path"; 4 | const { exec } = require("child_process"); 5 | import { getExecutablePath } from "./executable"; 6 | const command = path.join( 7 | __dirname, 8 | "..", 9 | "sourcery_binaries/" + getExecutablePath() + " uninstall" 10 | ); 11 | 12 | exec(command); 13 | -------------------------------------------------------------------------------- /src/webview/search.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // This script will be run within the webview itself 4 | // It cannot access the main VS Code APIs directly. 5 | (function () { 6 | const vscode = acquireVsCodeApi(); 7 | 8 | document.querySelector(".scanner-button").addEventListener("click", () => { 9 | sendMessageToExtension("scan"); 10 | }); 11 | document.querySelector(".save-button").addEventListener("click", () => { 12 | sendMessageToExtension("save"); 13 | }); 14 | document.querySelector(".replace-button").addEventListener("click", () => { 15 | sendMessageToExtension("replace"); 16 | }); 17 | 18 | // Sort the height of the advanced rule input 19 | const basic = document.querySelector("#patternContainer"); 20 | const advanced = document.querySelector("#advancedContainer"); 21 | const advancedArea = document.querySelector("textarea.ruleInput"); 22 | 23 | advanced.style.height = basic.offsetHeight + "px"; 24 | advancedArea.style.height = basic.offsetHeight - 15 + "px"; 25 | 26 | window.addEventListener("message", (event) => { 27 | const message = event.data; 28 | 29 | if (message.command === "toggle") { 30 | basic.classList.toggle("hidden"); 31 | advanced.classList.toggle("hidden"); 32 | } else if (message.command === "setPattern") { 33 | const input = document.querySelector("textarea.patternInput"); 34 | input.value = message.pattern; 35 | } 36 | }); 37 | 38 | const input = document.querySelector("textarea.patternInput"); 39 | if (input) { 40 | input.focus(); 41 | } 42 | 43 | // This is handled by the RuleInputProvider 44 | function sendMessageToExtension(message_type) { 45 | let rule; 46 | let advanced; 47 | if (basic.classList.contains("hidden")) { 48 | const ruleInput = document.querySelector("textarea.ruleInput"); 49 | rule = { rule: ruleInput.value }; 50 | advanced = true; 51 | } else { 52 | const replacementInput = document.querySelector( 53 | "textarea.replacementInput" 54 | ); 55 | const conditionInput = document.querySelector("textarea.conditionInput"); 56 | rule = { 57 | pattern: input.value, 58 | replacement: replacementInput.value, 59 | condition: conditionInput.value, 60 | }; 61 | advanced = false; 62 | } 63 | 64 | vscode.postMessage({ type: message_type, advanced: advanced, rule: rule }); 65 | } 66 | })(); 67 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "ES2020", 5 | "outDir": "out", 6 | "lib": [ "ES2020" ], 7 | "sourceMap": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "server" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /walkthrough/Ask_Sourcery.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/walkthrough/Ask_Sourcery.gif -------------------------------------------------------------------------------- /walkthrough/Sourcery_3_Ways_to_Refactor_VS_Code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/walkthrough/Sourcery_3_Ways_to_Refactor_VS_Code.gif -------------------------------------------------------------------------------- /walkthrough/Sourcery_Code_Review.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/walkthrough/Sourcery_Code_Review.gif -------------------------------------------------------------------------------- /walkthrough/Sourcery_Login.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/walkthrough/Sourcery_Login.gif -------------------------------------------------------------------------------- /walkthrough/Sourcery_Metrics_VS_Code.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/walkthrough/Sourcery_Metrics_VS_Code.gif -------------------------------------------------------------------------------- /walkthrough/accept_recommendation.md: -------------------------------------------------------------------------------- 1 | ## In-Line Suggestions 2 | In addition to interacting with Sourcery through chat or through recipes and code lenses, Sourcery will also passively review your code and make suggestions to improve your code quality, readability, and maintainability. 3 | 4 | In Python, JavaScript, and TypeScript files you'll start to see Sourcery underline sections of your code where it has a reccomendation. You can hover over these underlined pieces of code to see the suggestion Sourcery is proposing. 5 | 6 | ![Refactoring code with Sourcery](Sourcery_3_Ways_to_Refactor_VS_Code.gif) 7 | 8 | ### Accept a Suggestion 9 | 10 | To accept a suggestion you can: 11 | 12 | - Press the quickfix shortcut (typically `Ctrl .` or `Cmd .`) when you have 13 | selected the underlined line and then choose to apply the refactoring 14 | - Click on the lightbulb icon next to the underlined line and then select to 15 | accept the refactoring 16 | - Open the Problems pane, right click on the suggested refactoring, and accept 17 | the suggested change. 18 | 19 | ### Skip a Suggestion 20 | 21 | You can skip a Sourcery suggestion once by choosing the skip option from the 22 | Sourcery action items menu: 23 | 24 | - Press the quickfix shortcut (typically `Ctrl .` or `Cmd .`) when you have 25 | selected the underlined line and then choose to skip the refactoring 26 | - Click on the lightbulb icon next to the underlined line and then select to 27 | skip the refactoring 28 | - Open the Problems pane, right click on the suggested refactoring, and select 29 | to skip the refactoring. 30 | 31 | You can also tell Sourcery not to suggest all refactorings or a specific type of 32 | refactoring for a function. 33 | 34 | - Add a comment `# sourcery skip` to a function to skip all reactorings for that 35 | function 36 | - Add a comment `# sourcery skip: ` to a function to skip the 37 | specific refactoring in that function. A full list of refactorings and their 38 | IDs are available at [Current Refactorings](https://docs.sourcery.ai/refactorings/index.md). 39 | 40 | 41 | ### Permanently Ignore a Type of Refactoring 42 | 43 | You can choose to never see a certain type of refactoring when it is suggested 44 | to you by Sourcery. Bring up the Quick Fix menu and select the third menu item 45 | `Sourcery - Never show me this refactoring`. 46 | 47 | Sourcery will then add this refactoring to your list of 48 | [excluded refactorings](https://docs.sourcery.ai/Configuration/Customizing-Refactorings/). You can 49 | reverse this by going to your project's sourcery.yaml file and removing the 50 | refactoring from the `skip` list. 51 | 52 | ### See Code Quality Metrics 53 | 54 | Sourcery gives each of your functions a quality score on 4 different metrics: 55 | 56 | - **Complexity** 57 | - **Method Length** 58 | - **Working Memory** 59 | - **Overall Quality** 60 | 61 | To see the metrics for any function, simply hover your mouse of the line 62 | defining the function and the Sourcery metrics will pop up. Each metric will 63 | have a numeric score, along with a quick qualitative guide ranging from bad to 64 | excellent. 65 | 66 | ![Sourcery's Quality Metrics in VS Code](Sourcery_Metrics_VS_Code.gif) 67 | 68 | Sourcery will also automatically flag functions with too low of an overall 69 | quality score. By default this is set for functions with a quality score under 70 | 25%, but you [can adjust this threshold](https://docs.sourcery.ai/Configuration/). 71 | -------------------------------------------------------------------------------- /walkthrough/code_review.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcery-ai/sourcery-vscode/9e92c9fe2ecc4283b70caa12b64ddb6bad647ab1/walkthrough/code_review.png -------------------------------------------------------------------------------- /walkthrough/code_reviews.md: -------------------------------------------------------------------------------- 1 | Sourcery can review your code directly in your IDE. 2 | 3 | ![Sourcery Code Reviews](Sourcery_Code_Review.png) 4 | 5 | Click the Sourcery logo in the sidebar to open the coding assistant and bring up the code review panel. 6 | 7 | Choose the context of the code you want to review. 8 | 9 | You can choose to review your current branch, your uncommitted changes, or the current file. 10 | 11 | Then click review and Sourcery will get to work, giving you feedback. 12 | 13 | -------------------------------------------------------------------------------- /walkthrough/configuration.md: -------------------------------------------------------------------------------- 1 | # Project Settings 2 | 3 | Sourcery reads project settings from `.sourcery.yaml` in the project directory. 4 | 5 | The config file uses YAML syntax. If you are new to YAML check out 6 | ["Learn YAML in 5 minutes"](https://www.codeproject.com/Articles/1214409/Learn-YAML-in-five-minutes). 7 | 8 | Here is the default set of values set for the file. If a config file isn't set 9 | or if a setting isn't specified then Sourcery will use these values: 10 | 11 | ```yaml 12 | ignore: [] 13 | 14 | refactor: 15 | include: [] 16 | skip: [] 17 | rule_types: 18 | - refactoring 19 | - suggestion 20 | - comment 21 | python_version: '3.9' 22 | 23 | rules: [] 24 | 25 | metrics: 26 | quality_threshold: 25.0 27 | 28 | clone_detection: 29 | min_lines: 3 30 | min_duplicates: 2 31 | identical_clones_only: false 32 | 33 | github: 34 | labels: [] 35 | ignore_labels: [sourcery-ignore] 36 | request_review: author 37 | sourcery_branch: sourcery/{base_branch} 38 | ``` 39 | 40 | Here is the full list of configurable settings 41 | 42 | ## Enabling or Disabling the Coding Assistant 43 | You can control whether or not the Sourcery Coding Assistant (Chat, Recipes, & Code Reviews) is enabled through your user level `sourcery.yaml` file or your project level `.sourcery.yaml` file. 44 | 45 | To enable the coding assistant add in the following lines: 46 | 47 | ```yaml 48 | coding_assistant: 49 | enabled: True 50 | ``` 51 | 52 | To disable it, update the code above to: 53 | 54 | ```yaml 55 | coding_assistant: 56 | enabled: False 57 | ``` 58 | 59 | ## Ignoring Paths or Files 60 | 61 | Any path or file you specify to be ignored will be ignored both in your IDE & in 62 | GitHub. You will not see refactoring suggestions in any of these files. 63 | 64 | By default any files in the path `.github/workflows/*` will be ignored within 65 | GitHub as third party GitHub applications can't update the workflows folder. 66 | 67 | Add paths or files as items in a list underneath the `ignore` setting. 68 | 69 | For example: 70 | 71 | ```yaml 72 | ignore: 73 | - data/* 74 | - .venv/* 75 | - '*_test.py' # Note that any strings beginning with * must be quoted 76 | ``` 77 | 78 | ## Skipping Types of Refactorings 79 | 80 | You can tell Sourcery to never suggest a specific refactoring type to you. 81 | Specify a list of refactoring IDs under the `refactor - skip` section of the 82 | config file. 83 | 84 | Individual refactoring ids are displayed in the plugin suggestions and GitHub 85 | Bot comments for easy lookup or you can find a full list in the 86 | [refactorings section](https://docs.sourcery.ai/refactorings/index.md) of the docs. 87 | 88 | For example: 89 | 90 | ```yaml 91 | refactor: 92 | skip: 93 | - assign-if-exp 94 | - de-morgan 95 | ``` 96 | 97 | Would make it so Sourcery never shows you the 98 | [Assign If Expression](https://docs.sourcery.ai/refactorings/assign-if-exp.md) and 99 | [De Morgan's Identity](https://docs.sourcery.ai/refactorings/de-morgan.md) refactorings. 100 | 101 | For more details on skipping or ignoring refactorings (including skipping single 102 | instances of a refactoring) check out our section on 103 | [customizing refactorings](https://docs.sourcery.ai/Customizing-Refactorings.md) 104 | 105 | ## Only applying certain types of Refactorings 106 | 107 | You can tell Sourcery to only suggest specific refactoring types, ignoring all 108 | others. Specify a list of refactoring IDs under the `refactor - include` section 109 | of the config file. This configuration is mutually exclusive with `skip` - any 110 | refactorings specified in `include` will override the `skip` configuration. 111 | 112 | Skip comments in the source code ruling out specific instances of the 113 | refactoring will still be respected. 114 | 115 | Individual refactoring ids are displayed in the plugin suggestions and GitHub 116 | Bot comments for easy lookup or you can find a full list in the 117 | [refactorings section](https://docs.sourcery.ai/refactorings/index.md) of the docs. 118 | 119 | For example: 120 | 121 | ```yaml 122 | refactor: 123 | include: 124 | - assign-if-exp 125 | ``` 126 | 127 | Would make it so Sourcery only shows you the 128 | [Assign If Expression](https://docs.sourcery.ai/refactorings/assign-if-exp.md) refactoring. 129 | 130 | ## Setting the Python version assumed by Sourcery 131 | 132 | You can specify a version of Python that your project uses. Sourcery will use 133 | this to ensure it doesn't offer any refactorings that require later versions of 134 | Python. 135 | 136 | To do this specify a `python_version` in the `refactor` section. 137 | 138 | For example: 139 | 140 | ```yaml 141 | refactor: 142 | python_version: '3.7' 143 | ``` 144 | 145 | Would make it show Sourcery did not show refactorings requiring Python version 146 | 3.8 or later (for example 147 | [Use Named Expression](https://docs.sourcery.ai/refactorings/use-named-expression.md)). 148 | 149 | ## Setting the rule types 150 | 151 | Sourcery provides code improvements via different types of rules: 152 | 153 | - **refactorings**, which will not change the functionality of the code 154 | - **suggestions**, which may address issues or improve design but could possibly 155 | change functionality in some instances, 156 | - **comments**, which highlight a possible issue, but don't provide a specific 157 | fix. 158 | 159 | You can configure which of these you want to see with the `rule_types` option in 160 | the `refactor` section. 161 | 162 | For example: 163 | 164 | ```yaml 165 | refactor: 166 | rule_types: 167 | - refactoring 168 | - suggestion 169 | ``` 170 | 171 | ## Add custom rules 172 | 173 | You can add custom rules to enforce coding standards in your project. 174 | 175 | Search for code using a `pattern` and optionally fix it with `replacement` code. 176 | 177 | ```yaml 178 | rules: 179 | - id: not-implemented 180 | description: NotImplemented is not an Exception, raise NotImplementedError instead 181 | pattern: raise NotImplemented 182 | replacement: raise NotImplementedError 183 | - id: remove-open-r 184 | description: Files are opened in read mode `r` by default 185 | # This pattern uses a capture ${file} to capture the first argument 186 | pattern: open(${file}, "r") 187 | # The capture is substituted into the replacement 188 | replacement: open(${file}) 189 | ``` 190 | 191 | ### Rule schema 192 | 193 | The following fields are supported in a rule: 194 | 195 | | Field | Type | Required | Description | 196 | | ----------- | ------ | -------- | -------------------------------------------------------------- | 197 | | id | string | Required | Unique, descriptive identifier, e.g. `raise-not-implemented` | 198 | | description | string | Required | A description of why this rule triggered and how to resolve it | 199 | | pattern | string | Required | Search for code matching this expression | 200 | | replacement | string | Optional | Replace the matched code with this | 201 | 202 | A pattern is valid Python code that can optionally contain captures like 203 | `${name}` (captures get their names from regular expression captures). e.g. 204 | `print(${name})` 205 | 206 | A replacement is valid Python code that can optionally contain substitutions 207 | that refer to captures declared in the pattern: 208 | `print(${name}, file=sys.stderr)` 209 | 210 | ## Setting Metrics Thresholds 211 | 212 | Sourcery's code quality metrics will show a warning in your IDE if the quality 213 | score for a function drops below a certain threshold. 214 | 215 | Note - You can control whether or not Sourcery's metrics are displayed in your 216 | IDE by toggling the switch within the IDE plugin settings. 217 | 218 | By default the warnings will appear if a function's score drops below 25. 219 | Changing the value in the `metrics - quality threshold` section of the config 220 | will change this threshold 221 | 222 | For example: 223 | 224 | ```yaml 225 | metrics: 226 | quality_threshold: 30.0 227 | ``` 228 | 229 | These warnings can be skipped in the same way as refactorings - by adding 230 | `low-code-quality` to the list of skipped refactorings in the config file or by 231 | adding a `# sourcery skip: low-code-quality` comment to a particular function. 232 | 233 | ## GitHub Specific Configuration Options 234 | 235 | There are a number of GitHub specific configuration options you can set. All of 236 | these are nested under the `github` section of the config file. 237 | 238 | ### Ignore Labels 239 | 240 | You can set a configuration to ignore pull requests that have certain labels. 241 | 242 | These labels are tracked as a list under `ignore_labels:` For example: 243 | 244 | ```yaml 245 | github: 246 | ignore_labels: 247 | - sourcery-ignore 248 | ``` 249 | 250 | This configuration setting would ignore all new pull requests tagged with the 251 | label `sourcery-ignore` 252 | 253 | ### Add Labels 254 | 255 | You can set custom labels that will be attached to all pull requests created by 256 | Sourcery. 257 | 258 | These can be used to tell other automations to ignore Sourcery's PRs. 259 | 260 | These labels are tracked as a list under `labels:` For example: 261 | 262 | ```yaml 263 | github: 264 | labels: 265 | - build-ignore 266 | ``` 267 | 268 | ### Request Reviews 269 | 270 | You can automatically trigger Sourcery PRs to request a review from a certain 271 | person. The possible set of values for this configuration are: 272 | 273 | - `author` - The author of the original PR or the sender of the refactoring 274 | request. This is the default 275 | - `none` - No review is requested 276 | - `owner` - The owner of the base repository. This option is not valid for 277 | organizations 278 | - `username` - A specific user 279 | - `org/teamname` - A specific team within an organization 280 | 281 | You can have one value that will apply to both origin and forked PRs: 282 | 283 | ```yaml 284 | github: 285 | request_review: owner 286 | ``` 287 | 288 | Or you can specify separate values for origin and forked PRs: 289 | 290 | ```yaml 291 | github: 292 | request_review: 293 | origin: owner 294 | forked: author 295 | ``` 296 | 297 | ### Sourcery Branch Name 298 | 299 | You can set a default name for the branch of Sourcery pull requests. 300 | 301 | This setting must contain \`{base_branch} which will be replaced with the branch 302 | name on theoriginal pull request. 303 | 304 | For example: 305 | 306 | ```yaml 307 | github: 308 | sourcery_branch: sourcery/{base_branch} 309 | ``` 310 | -------------------------------------------------------------------------------- /walkthrough/github_code_reviews.md: -------------------------------------------------------------------------------- 1 | You can add Sourcery to your GitHub or GitLab repos to get immediate code review feedback on every pull/merge request. 2 | 3 | ![Sourcery Code Reviews](Sourcery_Code_Review.gif) 4 | 5 | ## GitHub & GitLab Code Reviews 6 | You can add Sourcery to any [GitHub](https://app.sourcery.ai/login?connection=github) or [GitLab](https://app.sourcery.ai/login?connection=gitlab) repo to start getting code reviews on every PR. 7 | 8 | 9 | ## Code Reviews for Other Platforms 10 | Sourcery is not yet available for Bitbucket or other platforms, but you can [sign up for our waitlist](https://research.typeform.com/to/GoVM9jYf) to get early access when it's available. -------------------------------------------------------------------------------- /walkthrough/login.md: -------------------------------------------------------------------------------- 1 | ## Use Sourcery on all your code 2 | You can use Sourcery on open source code for free. In order to use it on closed source projects you'll need to sign up for a [Sourcery Pro account](https://sourcery.ai/signup/?utm_source=VS-Code). 3 | 4 | ## 14 day free trial of Sourcery Pro 5 | 6 | You can sign up for a 14 day free trial of Sourcery Pro (no credit card required) to try out Sourcery on all of your projects. 7 | 8 | After those 14 days you can choose to upgrade to Pro or you can keep using Sourcery for Free for open source projects. 9 | 10 | ## Free for students and educators 11 | 12 | Sourcery is free to use for students and educators on open source and closed source projects. Email us at [students@sourcery.ai](mailto:students@sourcery.ai) and we'll get you set up with a free plan. 13 | 14 | ### Log In 15 | 16 | After [signing up](https://sourcery.ai/signup/?utm_source=VS-Code), log in by 17 | opening the command palette (Ctrl/Cmd+Shift+P) and executing the 18 | `Sourcery: Login` command. Or open the Sourcery sidebar (click our hexagonal logo) and click the log in button. 19 | 20 | ![Logging in with Sourcery](Sourcery_Login.gif) 21 | 22 | ### Opting Into Sourcery's Coding Assistant 23 | 24 | There are two main ways you can use Sourcery as a pair programmer. One is through our rules-based in-line suggestions that run fully locally and that you'll see appear in your code. The other is through our Coding Assistant which you can ask questions to, can write new code or update your existing code, and more. The Coding Assistant relies on third party large language models and you need to opt into using it before you can start. 25 | 26 | To opt into use the Coding Assistant, open the Sourcery sidebar, and click the Opt In button. If you choose not to opt in, you can still use the rules-based suggestions from Sourcery fully locally 27 | -------------------------------------------------------------------------------- /walkthrough/view_welcome_file.md: -------------------------------------------------------------------------------- 1 | ## Sourcery - Your Code Reviewer & Coding Assistant 2 | 3 | Sourcery works in 2 ways: 4 | 1. Gives you code reviews while you work. Anytime you want a review just ask Sourcery and it will give you feedback like you'd expect from a peer review. 5 | 2. Acts as an AI powered pair programmer allowing you to ask it questions, write new code, and interact with existing code 6 | 7 | Let's take a look at how you can interact with it in VS Code 8 | 9 | ### Code reviews 10 | 11 | Sourcery can review your code directly in your IDE anytime you'd like some feedback. 12 | 13 | Click the Sourcery logo in the sidebar to open the coding assistant and bring up the code review panel. 14 | 15 | Then choose the context of the code you want to review, click review, and wait for Sourcery's feedback. 16 | 17 | To make the most of Sourcery's reviews you can also add Sourcery to your GitHub or GitLab repos. 18 | 19 | ### In the tutorial 20 | 21 | Once you open the tutorial you should see this example: 22 | 23 | ```python 24 | def days_between_dates(date1, date2): 25 | d1 = datetime.datetime.strptime(date1, '%Y-%m-%d').date() 26 | d2 = datetime.datetime.strptime(date2, '%Y-%m-%d').date() 27 | delta = d2 - d1 28 | return delta.days 29 | ``` 30 | 31 | Above the function you'll see a few commands - these are Code Lenses that you can use to interact with Sourcery. Try clicking the "Ask Sourcery" Code Lens and asking it to update the code to use `dateutil`. The answer will appear in the Sourcery sidebar chat. 32 | 33 | ![Sourcery updating code](Ask_Sourcery.gif) 34 | 35 | ### More ways Sourcery can help 36 | 37 | Sourcery can interact with your code in a number of other ways. On the same function try clicking the `Generate Docstrings`, `Generate Tests`, and `Explain Code`Code Lenses. 38 | 39 | -------------------------------------------------------------------------------- /welcome-to-sourcery.py: -------------------------------------------------------------------------------- 1 | # Welcome to Sourcery! We're here to be your pair programmer anytime you're 2 | # working in VS Code. 3 | 4 | # To get started log into your Sourcery account. Click on the Sourcery logo 5 | # (the hexagon) on your VS Code sidebar and click the login button, or open 6 | # the command palette (Ctrl/Cmd+Shift+P) and execute `Sourcery: Login`. 7 | 8 | # Let's start looking at how you can get a code review from Sourcery. 9 | 10 | # The `Review` tab allows you to get a code review straight away in your IDE 11 | # - You can always get a review of the current file 12 | # - If in Git you can review your current set of uncommitted changes, 13 | # or your current branch compared to the default branch 14 | 15 | # If you want reviews when you open a PR, you can add Sourcery to your GitHub or GitLab repos. 16 | 17 | 18 | # Now let's move on to the `Chat` tab. 19 | 20 | # Above each function you'll see a few commands - these are Code Lenses that 21 | # you can use to interact with Sourcery. Try clicking on "Ask Sourcery" and 22 | # asking it to update the code to use `dateutil`. The answer will appear in 23 | # the Sourcery sidebar chat. 24 | 25 | def days_between_dates(date1, date2): 26 | d1 = datetime.datetime.strptime(date1, '%Y-%m-%d').date() 27 | d2 = datetime.datetime.strptime(date2, '%Y-%m-%d').date() 28 | delta = d2 - d1 29 | return delta.days 30 | 31 | 32 | # With the Ask Sourcery command or the chat in the sidebar you can ask Sourcery 33 | # questions, have it write new code for you, or update existing code. 34 | 35 | # Sourcery also has a series of "recipes" to do different things with code. 36 | # Try clicking the Generate Docstrings lens above this next function: 37 | 38 | def calculate_weighted_moving_average(prices, weights): 39 | if not prices or not weights: 40 | raise ValueError("Both prices and weights must be provided.") 41 | 42 | if len(weights) > len(prices): 43 | raise ValueError("Length of weights must be less than or equal to length of prices.") 44 | 45 | total_weight = sum(weights) 46 | normalized_weights = [w / total_weight for w in weights] 47 | 48 | wma = [] 49 | for i in range(len(prices) - len(weights) + 1): 50 | weighted_sum = sum(prices[i + j] * normalized_weights[j] for j in range(len(weights))) 51 | wma.append(weighted_sum) 52 | 53 | return wma 54 | 55 | # Now try clicking Generate Tests or Explain Code for the same function! 56 | 57 | # There is also a recipe for generating diagrams. 58 | # You can access this by clicking Ask Sourcery and choosing it from the 59 | # dropdown or by selecting a section of code and clicking the recipes button 60 | # in the sidebar. 61 | 62 | # In your code you'll also see sections start to get underlined. 63 | # This means Sourcery has a suggestion to improve it. 64 | 65 | def refactoring_example(spellbook): 66 | result = [] 67 | for spell in spellbook: 68 | if spell.is_awesome: 69 | result.append(spell) 70 | print(result) 71 | 72 | # Hover over the underlined code to see details of the changes including a diff. 73 | 74 | # You can accept Sourcery's changes with the quick fix action. Put your cursor 75 | # on the highlighted line and click on the lightbulb. 76 | # 77 | # Or use the quick-fix hotkey (Ctrl .) or (Cmd .) and then choose 78 | # "Sourcery - Convert for loop...". This will instantly replace the code with 79 | # the improved version. 80 | 81 | # The Problems pane (Ctrl/Cmd+Shift+M) shows all of Sourcery's suggestions. 82 | 83 | # Sourcery also provides code metrics for each function to give you insight into 84 | # code quality - hover over the function definition below to see this report. 85 | 86 | def magical_hoist(magic): 87 | if is_powerful(magic): 88 | result = 'Magic' 89 | else: 90 | print("Not powerful.") 91 | result = 'Magic' 92 | print(result) 93 | 94 | # What if we don't want to make the change Sourcery suggests? 95 | 96 | # You can skip/ignore changes from Sourcery in a few ways: 97 | 98 | # 1) In the quick fix menu choose "Sourcery - Skip suggested refactoring" 99 | # This adds a comment to the function telling Sourcery not to make the change. 100 | 101 | # 2) In the quick fix menu choose "Sourcery - Never show me this refactoring" 102 | # This tells Sourcery to never suggest this type of suggestion. This config 103 | # is stored in a configuration file on your machine. 104 | 105 | # 3) Click on the Sourcery button in the Status Bar (typically the bottom of 106 | # the VS Code window) to bring up the Sourcery Hub. Click on "Settings" and 107 | # then you can toggle individual rule types on or off 108 | 109 | # For more details check out our documentation here: 110 | # https://docs.sourcery.ai/ 111 | 112 | # If you want to play around a bit more, here are some more examples of Sourcery's in-line suggestions. 113 | # These include cases where Sourcery has chained together suggestions to come 114 | # up with more powerful refactorings. 115 | 116 | def find_more(magicks): 117 | powerful_magic = [] 118 | for magic in magicks: 119 | if not is_powerful(magic): 120 | continue 121 | powerful_magic.append(magic) 122 | return powerful_magic 123 | 124 | 125 | def is_powerful(magic): 126 | if magic == 'Sourcery': 127 | return True 128 | elif magic == 'More Sourcery': 129 | return True 130 | else: 131 | return False 132 | 133 | 134 | def print_all(spells: list): 135 | for i in range(len(spells)): 136 | print(spells[i]) 137 | --------------------------------------------------------------------------------