├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ ├── jetbrains.yaml │ ├── pre-release.yaml │ ├── release-npm.yaml │ ├── release-please.yaml │ ├── release-vsix.yaml │ ├── rust.yaml │ └── ts.yaml ├── .gitignore ├── .release-please-manifest.json ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── .vscodeignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── assets ├── basic-throw.gif ├── calltothrow.gif ├── icon-128.png ├── icon-small.png └── icon.png ├── biome.json ├── build.ts ├── bun.lockb ├── client ├── .gitignore ├── package.json ├── src │ └── extension.ts └── tsconfig.json ├── crates ├── does-it-throw-wasm │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── src │ │ └── lib.rs └── does-it-throw │ ├── CHANGELOG.md │ ├── Cargo.toml │ └── src │ ├── call_finder.rs │ ├── fixtures │ ├── .vscode │ │ └── settings.json │ ├── callExpr.ts │ ├── class.js │ ├── class.ts │ ├── exports.js │ ├── exports.ts │ ├── ignoreStatements.js │ ├── ignoreStatements.ts │ ├── importIdentifiers.ts │ ├── jsx.jsx │ ├── objectLiteral.js │ ├── objectLiteral.ts │ ├── returnStatement.ts │ ├── sample.ts │ ├── something.ts │ ├── spreadExpr.ts │ ├── switchStatement.ts │ ├── tryStatement.ts │ ├── tryStatementNested.ts │ └── tsx.tsx │ ├── import_usage_finder.rs │ ├── lib.rs │ ├── main.rs │ └── throw_finder.rs ├── docs ├── neovim.md └── usage.md ├── jetbrains ├── .gitignore ├── .run │ ├── Run IDE for UI Tests.run.xml │ ├── Run Plugin.run.xml │ ├── Run Tests.run.xml │ └── Run Verifications.run.xml ├── CHANGELOG.md ├── build.gradle.kts ├── gradle.properties ├── gradle │ ├── libs.versions.toml │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── marketplace-zip-signer-cli.jar ├── qodana.yml ├── settings.gradle.kts ├── src │ ├── main │ │ ├── kotlin │ │ │ └── org │ │ │ │ └── michaelangeloio │ │ │ │ └── plugins │ │ │ │ └── dit │ │ │ │ ├── DoesItThrowBundle.kt │ │ │ │ ├── DoesItThrowUtils.kt │ │ │ │ ├── lsp │ │ │ │ ├── LspServerDescriptor.kt │ │ │ │ └── LspServerSupportProvider.kt │ │ │ │ ├── services │ │ │ │ └── DoesItThrowServerService.kt │ │ │ │ └── settings │ │ │ │ ├── DoesItThrowConfigurable.kt │ │ │ │ ├── DoesItThrowSettings.kt │ │ │ │ └── DoesItThrowSettingsState.kt │ │ └── resources │ │ │ ├── META-INF │ │ │ ├── plugin.xml │ │ │ └── pluginIcon.svg │ │ │ └── messages │ │ │ └── DoesItThrowBundle.properties │ └── test │ │ └── kotlin │ │ └── org │ │ └── michaelangeloio │ │ └── plugins │ │ └── dit │ │ └── DoesItThrowUtils.kt └── version.txt ├── package-lock.json ├── package.json ├── release-please-config.json ├── rustfmt.toml ├── server ├── .gitignore ├── CHANGELOG.md ├── bin │ └── does-it-throw ├── package.json ├── src │ └── server.ts └── tsconfig.json ├── tsconfig.build.json └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: ["michaelangeloio"] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | - package-ecosystem: "cargo" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "weekly" 16 | -------------------------------------------------------------------------------- /.github/workflows/jetbrains.yaml: -------------------------------------------------------------------------------- 1 | 2 | name: JetBrains 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | 8 | jobs: 9 | 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | outputs: 14 | version: ${{ steps.properties.outputs.version }} 15 | changelog: ${{ steps.properties.outputs.changelog }} 16 | pluginVerifierHomeDir: ${{ steps.properties.outputs.pluginVerifierHomeDir }} 17 | steps: 18 | 19 | - name: Fetch Sources 20 | uses: actions/checkout@v4 21 | 22 | - name: Gradle Wrapper Validation 23 | uses: gradle/wrapper-validation-action@v1.1.0 24 | 25 | - name: Setup Java 26 | uses: actions/setup-java@v4 27 | with: 28 | distribution: zulu 29 | java-version: 17 30 | 31 | - name: Setup Gradle 32 | uses: gradle/gradle-build-action@v2 33 | with: 34 | gradle-home-cache-cleanup: true 35 | 36 | - name: Install Bun 37 | uses: oven-sh/setup-bun@a1800f471a0bc25cddac36bb13e6f436ddf341d7 38 | 39 | - name: Install Rust 40 | uses: actions-rs/toolchain@v1 41 | with: 42 | toolchain: stable 43 | profile: minimal 44 | override: true 45 | 46 | - name: Install wasm-pack 47 | uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa 48 | with: 49 | version: 'latest' 50 | 51 | - name: Install dependencies 52 | run: bun install 53 | 54 | - name: Build With Bun 55 | run: bun run build 56 | 57 | # Set environment variables 58 | - name: Export Properties 59 | id: properties 60 | shell: bash 61 | working-directory: ./jetbrains 62 | run: | 63 | PROPERTIES="$(./gradlew properties --console=plain -q)" 64 | VERSION="$(echo "$PROPERTIES" | grep "^version:" | cut -f2- -d ' ')" 65 | CHANGELOG="$(./gradlew getChangelog --unreleased --no-header --console=plain -q)" 66 | 67 | echo "version=$VERSION" >> $GITHUB_OUTPUT 68 | echo "pluginVerifierHomeDir=~/.pluginVerifier" >> $GITHUB_OUTPUT 69 | 70 | echo "changelog<> $GITHUB_OUTPUT 71 | echo "$CHANGELOG" >> $GITHUB_OUTPUT 72 | echo "EOF" >> $GITHUB_OUTPUT 73 | 74 | ./gradlew listProductsReleases # prepare list of IDEs for Plugin Verifier 75 | 76 | - name: Build IDE plugin 77 | working-directory: ./jetbrains 78 | run: ./gradlew buildPlugin 79 | 80 | test: 81 | name: Test 82 | runs-on: ubuntu-latest 83 | steps: 84 | 85 | - name: Fetch Sources 86 | uses: actions/checkout@v4 87 | 88 | - name: Setup Java 89 | uses: actions/setup-java@v4 90 | with: 91 | distribution: zulu 92 | java-version: 17 93 | 94 | - name: Setup Gradle 95 | uses: gradle/gradle-build-action@v2 96 | with: 97 | gradle-home-cache-cleanup: true 98 | 99 | - name: Run Tests 100 | working-directory: ./jetbrains 101 | run: ./gradlew check 102 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yaml: -------------------------------------------------------------------------------- 1 | name: Pre-Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | pre-release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Prepare repository 15 | run: git fetch --unshallow --tags 16 | 17 | - name: Use Node.js 20.x 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | cache: 'npm' 22 | 23 | - name: Install Bun 24 | uses: oven-sh/setup-bun@a1800f471a0bc25cddac36bb13e6f436ddf341d7 25 | 26 | - name: Install Rust 27 | uses: actions-rs/toolchain@v1 28 | with: 29 | toolchain: stable 30 | profile: minimal 31 | override: true 32 | 33 | - name: Install wasm-pack 34 | uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa 35 | with: 36 | version: 'latest' 37 | 38 | - name: Install dependencies 39 | run: bun install 40 | 41 | - name: Build 42 | run: bun run vscode:package 43 | 44 | - uses: "marvinpinto/action-automatic-releases@919008cf3f741b179569b7a6fb4d8860689ab7f0" 45 | with: 46 | repo_token: "${{ secrets.GITHUB_TOKEN }}" 47 | automatic_release_tag: "vscode-canary" 48 | prerelease: true 49 | title: "VsCode Dev Build" 50 | files: | 51 | LICENSE.txt 52 | *.vsix 53 | -------------------------------------------------------------------------------- /.github/workflows/release-npm.yaml: -------------------------------------------------------------------------------- 1 | name: Release NPM 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Prepare repository 13 | run: git fetch --unshallow --tags 14 | 15 | - name: Use Node.js 20.x 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20.x 19 | cache: 'npm' 20 | registry-url: 'https://registry.npmjs.org' 21 | 22 | - name: Install Bun 23 | uses: oven-sh/setup-bun@a1800f471a0bc25cddac36bb13e6f436ddf341d7 24 | 25 | - name: Install Rust 26 | uses: actions-rs/toolchain@v1 27 | with: 28 | toolchain: stable 29 | profile: minimal 30 | override: true 31 | 32 | - name: Install wasm-pack 33 | uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa 34 | with: 35 | version: 'latest' 36 | 37 | - name: Install dependencies 38 | run: bun install 39 | 40 | - name: Build Language Server 41 | run: bun run build 42 | 43 | - name: Package Language Server 44 | run: bun run pack:server 45 | 46 | - name: Publish Language Server 47 | run: bun run publish:server 48 | env: 49 | NODE_AUTH_TOKEN: ${{ secrets.NPM_LSP_SERVER_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/release-please.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | 6 | permissions: 7 | contents: write 8 | pull-requests: write 9 | 10 | name: Release Please 11 | 12 | jobs: 13 | release-please: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Checkout Repo 17 | uses: actions/checkout@v2 18 | 19 | - name: Use Node.js 20.x 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20.x 23 | 24 | - name: Install Bun 25 | uses: oven-sh/setup-bun@a1800f471a0bc25cddac36bb13e6f436ddf341d7 26 | 27 | - name: Install Rust 28 | uses: actions-rs/toolchain@v1 29 | with: 30 | toolchain: stable 31 | profile: minimal 32 | override: true 33 | 34 | - name: Install wasm-pack 35 | uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa 36 | with: 37 | version: 'latest' 38 | 39 | - name: Install dependencies 40 | run: bun install 41 | 42 | - name: Release Please 43 | id: release 44 | uses: google-github-actions/release-please-action@v3 45 | with: 46 | command: manifest 47 | 48 | - name: Trigger VSIX Release 49 | if: ${{ steps.release.outputs.release_created }} 50 | run: gh workflow run release-vsix.yaml 51 | env: 52 | GITHUB_TOKEN: ${{ secrets.VSIX_DISPATCH_TOKEN }} 53 | 54 | - name: Trigger NPM Publish Release 55 | if: ${{ steps.release.outputs.release_created }} 56 | run: gh workflow run release-npm.yaml 57 | env: 58 | GITHUB_TOKEN: ${{ secrets.NPM_DISPATCH_TOKEN }} 59 | 60 | -------------------------------------------------------------------------------- /.github/workflows/release-vsix.yaml: -------------------------------------------------------------------------------- 1 | name: Release VSIX 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Prepare repository 13 | run: git fetch --unshallow --tags 14 | 15 | - name: Use Node.js 20.x 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: 20.x 19 | cache: 'npm' 20 | 21 | - name: Install Bun 22 | uses: oven-sh/setup-bun@a1800f471a0bc25cddac36bb13e6f436ddf341d7 23 | 24 | - name: Install Rust 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | toolchain: stable 28 | profile: minimal 29 | override: true 30 | 31 | - name: Install wasm-pack 32 | uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa 33 | with: 34 | version: 'latest' 35 | 36 | - name: Install dependencies 37 | run: bun install 38 | 39 | - name: Publish to Visual Studio Marketplace 40 | uses: HaaLeo/publish-vscode-extension@65512ae7dcf96159b51fdd7ed73eb17d5cacad33 41 | with: 42 | pat: ${{ secrets.VSCE_PAT }} 43 | registryUrl: https://marketplace.visualstudio.com 44 | -------------------------------------------------------------------------------- /.github/workflows/rust.yaml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build-and-test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Install Rust 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | 21 | - name: Test 22 | uses: actions-rs/cargo@v1 23 | with: 24 | command: test 25 | args: --verbose 26 | -------------------------------------------------------------------------------- /.github/workflows/ts.yaml: -------------------------------------------------------------------------------- 1 | name: Bun/Node CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - name: Install Bun 16 | uses: oven-sh/setup-bun@a1800f471a0bc25cddac36bb13e6f436ddf341d7 17 | 18 | - name: Install Rust 19 | uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | profile: minimal 23 | override: true 24 | 25 | - name: Install wasm-pack 26 | uses: jetli/wasm-pack-action@0d096b08b4e5a7de8c28de67e11e945404e9eefa 27 | with: 28 | version: 'latest' 29 | 30 | - name: Install dependencies 31 | run: bun install 32 | 33 | - name: Build 34 | run: bun run vscode:package -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # common rust ignores 2 | target 3 | node_modules/ 4 | **.vsix 5 | .DS_Store 6 | 7 | .env 8 | *.tgz 9 | .idea 10 | *.pem 11 | *.crt -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "crates/does-it-throw": "0.3.0", 3 | "crates/does-it-throw-wasm": "0.3.0", 4 | ".": "0.5.0", 5 | "server": "0.5.0", 6 | "jetbrains": "0.5.0" 7 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$note": "// A launch configuration that compiles the extension and then opens it inside a new window", 3 | "version": "0.2.0", 4 | "configurations": [ 5 | { 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "name": "Launch Client", 9 | "runtimeExecutable": "${execPath}", 10 | "args": [ 11 | "--extensionDevelopmentPath=${workspaceRoot}" 12 | ], 13 | "outFiles": [ 14 | "${workspaceRoot}/client/out/**/*.js" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.tabSize": 2, 3 | "typescript.tsc.autoDetect": "off", 4 | "typescript.preferences.quoteStyle": "single", 5 | "[json]": { 6 | "editor.defaultFormatter": "vscode.json-language-features" 7 | }, 8 | "[javascript]": { 9 | "editor.defaultFormatter": "biomejs.biome" 10 | }, 11 | "[javascriptreact]": { 12 | "editor.defaultFormatter": "biomejs.biome" 13 | }, 14 | "[typescript]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | }, 17 | "[typescriptreact]": { 18 | "editor.defaultFormatter": "biomejs.biome" 19 | }, 20 | "doesItThrow.trace.server": "verbose", 21 | "files.exclude": { 22 | "**/.git": true, 23 | "**/.svn": true, 24 | "**/.hg": true, 25 | "**/CVS": true, 26 | "**/.DS_Store": true, 27 | "**/Thumbs.db": true, 28 | "intellij/.gradle/**/*": true, 29 | "intellij/.idea/**/*": true, 30 | "intellij/build/**/*": true 31 | } 32 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "compile", 7 | "group": "build", 8 | "presentation": { 9 | "panel": "dedicated", 10 | "reveal": "never" 11 | }, 12 | "problemMatcher": [ 13 | "$tsc" 14 | ] 15 | }, 16 | { 17 | "type": "npm", 18 | "script": "watch", 19 | "isBackground": true, 20 | "group": { 21 | "kind": "build", 22 | "isDefault": true 23 | }, 24 | "presentation": { 25 | "panel": "dedicated", 26 | "reveal": "never" 27 | }, 28 | "problemMatcher": [ 29 | "$tsc-watch" 30 | ] 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | # Ignore everything. 2 | ** 3 | 4 | # Selectively un-ignore files we want included in the released extension. 5 | !CHANGELOG.md 6 | !language-configuration.json 7 | !LICENSE.txt 8 | !client/out/**/* 9 | !server/out/**/* 10 | !package.json 11 | !README.md 12 | !assets/**/* 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.5.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.4.0...does-it-throw-vscode-v0.5.0) (2024-01-25) 4 | 5 | 6 | ### Features 7 | 8 | * **deps-dev:** bump @biomejs/biome from 1.4.1 to 1.5.3 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 9 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.5 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 10 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.6 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 11 | * **deps-dev:** bump bun-types from 1.0.20 to 1.0.25 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 12 | * **deps-dev:** bump esbuild from 0.19.11 to 0.19.12 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 13 | * **deps:** bump serde from 1.0.193 to 1.0.195 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 14 | * **deps:** bump serde_json from 1.0.109 to 1.0.111 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 15 | * **deps:** bump swc_ecma_ast from 0.110.15 to 0.110.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 16 | * **deps:** bump swc_ecma_ast from 0.110.17 to 0.111.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 17 | * **deps:** bump swc_ecma_parser from 0.141.34 to 0.142.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 18 | * **deps:** bump swc_ecma_visit from 0.96 to 0.97.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 19 | * **deps:** bump swc_ecma_visit from 0.96.15 to 0.96.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 20 | * **deps:** bump vscode-languageserver-textdocument from 1.0.8 to 1.0.11 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 21 | * **deps:** bump wasm-bindgen from 0.2.89 to 0.2.90 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 22 | * **jetbrains:** jetbrains implementation ([#119](https://github.com/michaelangeloio/does-it-throw/issues/119)) ([e4d4153](https://github.com/michaelangeloio/does-it-throw/commit/e4d415336da8eb78ef650f2941185a3fa4dc5dd6)) 23 | * LSP now supports workspace folders ([#127](https://github.com/michaelangeloio/does-it-throw/issues/127)) ([960b486](https://github.com/michaelangeloio/does-it-throw/commit/960b486e8cfe4fd5165be4dd200457c7e5b90979)) 24 | 25 | 26 | ### Bug Fixes 27 | 28 | * enhance call to throw logic and handle return statements ([#140](https://github.com/michaelangeloio/does-it-throw/issues/140)) ([a1bfaf1](https://github.com/michaelangeloio/does-it-throw/commit/a1bfaf16c768aeb49ecaecb991ca6a2b57e71072)) 29 | 30 | ## [0.4.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.3.3...does-it-throw-vscode-v0.4.0) (2024-01-06) 31 | 32 | 33 | ### Features 34 | 35 | * **deps-dev:** bump esbuild from 0.19.10 to 0.19.11 ([#112](https://github.com/michaelangeloio/does-it-throw/issues/112)) ([15a95e2](https://github.com/michaelangeloio/does-it-throw/commit/15a95e2f6862b46fe1b6a80270b7e7a694427e5c)) 36 | * **deps-dev:** bump mockall from 0.12.0 to 0.12.1 ([#101](https://github.com/michaelangeloio/does-it-throw/issues/101)) ([211b21f](https://github.com/michaelangeloio/does-it-throw/commit/211b21f402d94b8bbf26e92b0185aeb5b1b0d196)) 37 | * **deps-dev:** bump typescript from 5.2.2 to 5.3.3 ([#102](https://github.com/michaelangeloio/does-it-throw/issues/102)) ([5f45eff](https://github.com/michaelangeloio/does-it-throw/commit/5f45eff8493f674470331c252bdfc2f558d96c3f)) 38 | * **deps:** bump serde_json from 1.0.108 to 1.0.109 ([#109](https://github.com/michaelangeloio/does-it-throw/issues/109)) ([5a28b3f](https://github.com/michaelangeloio/does-it-throw/commit/5a28b3f26992c4bca9d7bb276efdd27fa5b9a53a)) 39 | * **deps:** bump swc_ecma_parser from 0.141.33 to 0.141.34 ([#110](https://github.com/michaelangeloio/does-it-throw/issues/110)) ([482be78](https://github.com/michaelangeloio/does-it-throw/commit/482be78a20732f350377d4e534afae1053080e58)) 40 | * initial jetbrains/intellij support ([#104](https://github.com/michaelangeloio/does-it-throw/issues/104)) ([455d763](https://github.com/michaelangeloio/does-it-throw/commit/455d7635128646c57bbbc5811b75a526cb8adc64)) 41 | * user can now discard warnings with ignore statements ([#118](https://github.com/michaelangeloio/does-it-throw/issues/118)) ([3f8957c](https://github.com/michaelangeloio/does-it-throw/commit/3f8957c60fd90f9ab7b6646c04ec22dcecb21556)) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * adjust readme to adhere to jetbrains marketplace guidelines ([#115](https://github.com/michaelangeloio/does-it-throw/issues/115)) ([6d68139](https://github.com/michaelangeloio/does-it-throw/commit/6d68139151f43f06033fd4517baee5c3d53e287c)) 47 | * jetbrains build.gradle readme parsing logic ([#116](https://github.com/michaelangeloio/does-it-throw/issues/116)) ([a3cb052](https://github.com/michaelangeloio/does-it-throw/commit/a3cb052b5ac1db2dd8bdbda23eabb37a48de1bfa)) 48 | * release please setup for jetbrains ([#107](https://github.com/michaelangeloio/does-it-throw/issues/107)) ([df6b9bb](https://github.com/michaelangeloio/does-it-throw/commit/df6b9bba97d79c1bf0cdda6d306403cd2cd8707e)) 49 | * rename jetbrains release-please package name ([#108](https://github.com/michaelangeloio/does-it-throw/issues/108)) ([92791a2](https://github.com/michaelangeloio/does-it-throw/commit/92791a2abc7f29f3087460229f6c5a4ee93c62dc)) 50 | 51 | ## [0.3.3](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.3.2...does-it-throw-vscode-v0.3.3) (2023-12-24) 52 | 53 | 54 | ### Bug Fixes 55 | 56 | * catch with throw statement not included ([#95](https://github.com/michaelangeloio/does-it-throw/issues/95)) ([fd223db](https://github.com/michaelangeloio/does-it-throw/commit/fd223db4f56e87439999b9b33a393769bd2b7c5b)) 57 | * **deps-dev:** remove remaining eslint dev dependencies ([#97](https://github.com/michaelangeloio/does-it-throw/issues/97)) ([5f173a6](https://github.com/michaelangeloio/does-it-throw/commit/5f173a69cb86570a526a665d453b86ae776538d0)) 58 | * remove unused tsx, user-home, semver ([#100](https://github.com/michaelangeloio/does-it-throw/issues/100)) ([de8218c](https://github.com/michaelangeloio/does-it-throw/commit/de8218ce72e01d0092fc03141b26f44d28d5fc1b)) 59 | 60 | ## [0.3.2](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.3.1...does-it-throw-vscode-v0.3.2) (2023-12-17) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * add missing unit test for try statement ([#88](https://github.com/michaelangeloio/does-it-throw/issues/88)) ([290a323](https://github.com/michaelangeloio/does-it-throw/commit/290a323bae194d293ff8d0c826738f72dfef6212)) 66 | * gifs not populating in vscode marketplace ([#85](https://github.com/michaelangeloio/does-it-throw/issues/85)) ([15a93d7](https://github.com/michaelangeloio/does-it-throw/commit/15a93d70c94e7de3139e79516fbe43a31701dfa6)) 67 | * update server package.json keywords ([#87](https://github.com/michaelangeloio/does-it-throw/issues/87)) ([c19717d](https://github.com/michaelangeloio/does-it-throw/commit/c19717d96a09152d959bfd7d5c3a34ac62f5e26d)) 68 | 69 | ## [0.3.1](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.3.0...does-it-throw-vscode-v0.3.1) (2023-12-17) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * functions and throw statements are underlined even if caught ([#81](https://github.com/michaelangeloio/does-it-throw/issues/81)) ([16adf85](https://github.com/michaelangeloio/does-it-throw/commit/16adf85b05b92542fa6c09ac1611dd56c7603c99)) 75 | 76 | ## [0.3.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.2.5...does-it-throw-vscode-v0.3.0) (2023-12-16) 77 | 78 | 79 | ### Features 80 | 81 | * **deps:** bump vscode-languageclient from 8.1.0 to 9.0.1 ([#49](https://github.com/michaelangeloio/does-it-throw/issues/49)) ([b36a86b](https://github.com/michaelangeloio/does-it-throw/commit/b36a86b22757568dbfa82817d06956e5a15e0f65)) 82 | * neovim support ([#78](https://github.com/michaelangeloio/does-it-throw/issues/78)) ([6152786](https://github.com/michaelangeloio/does-it-throw/commit/61527869e70f54e99616375f7efd53b24e0fa01a)) 83 | * neovim/language server docs ([#80](https://github.com/michaelangeloio/does-it-throw/issues/80)) ([a00d92a](https://github.com/michaelangeloio/does-it-throw/commit/a00d92a3b13252025495dc811b21f84df3a38201)) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * add biome for standardization, ensure the builder reports errors correctly ([#72](https://github.com/michaelangeloio/does-it-throw/issues/72)) ([0d18392](https://github.com/michaelangeloio/does-it-throw/commit/0d18392268516abb79d015f90495dd331e7ef998)) 89 | * bump swc_ecma_ast to 0.110.9 ([#75](https://github.com/michaelangeloio/does-it-throw/issues/75)) ([0aa2e91](https://github.com/michaelangeloio/does-it-throw/commit/0aa2e91f4f1c0b9e352d052382c5a7f436cffeb9)) 90 | * remove unused eslint ([#74](https://github.com/michaelangeloio/does-it-throw/issues/74)) ([58ef6ae](https://github.com/michaelangeloio/does-it-throw/commit/58ef6aea9d4334eb0c42901c826ba69157994f77)) 91 | * results should still show even if file cannot resolve (calls to throws) ([#76](https://github.com/michaelangeloio/does-it-throw/issues/76)) ([f908556](https://github.com/michaelangeloio/does-it-throw/commit/f908556dfda8eca9195c87269fac71bc6d3e8bf9)) 92 | 93 | ## [0.2.5](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.2.4...does-it-throw-vscode-v0.2.5) (2023-11-10) 94 | 95 | 96 | ### Bug Fixes 97 | 98 | * add coverage for switch statements ([#43](https://github.com/michaelangeloio/does-it-throw/issues/43)) ([99fda18](https://github.com/michaelangeloio/does-it-throw/commit/99fda183a7ca813cbb5f5434f429cd79b594f139)) 99 | * re-organize primary crate into modules ([#42](https://github.com/michaelangeloio/does-it-throw/issues/42)) ([badb106](https://github.com/michaelangeloio/does-it-throw/commit/badb1061d0dfc679458d55609e43cccfdca01794)) 100 | * update details, fix logic in some call expressions, including spread operators ([#40](https://github.com/michaelangeloio/does-it-throw/issues/40)) ([cdfdf47](https://github.com/michaelangeloio/does-it-throw/commit/cdfdf47a2d657364abc1b3b3ce97e89405b842b3)) 101 | * update readme and contributing ([#44](https://github.com/michaelangeloio/does-it-throw/issues/44)) ([cf258cd](https://github.com/michaelangeloio/does-it-throw/commit/cf258cd8baffb9277a8039cdb7416378691d6684)) 102 | 103 | ## [0.2.4](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.2.3...does-it-throw-vscode-v0.2.4) (2023-11-09) 104 | 105 | 106 | ### Bug Fixes 107 | 108 | * update displayname ([#37](https://github.com/michaelangeloio/does-it-throw/issues/37)) ([cbaa0ad](https://github.com/michaelangeloio/does-it-throw/commit/cbaa0ad7a151559807985f6a4fde0dbd528cdd8a)) 109 | 110 | ## [0.2.3](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.2.2...does-it-throw-vscode-v0.2.3) (2023-11-09) 111 | 112 | 113 | ### Bug Fixes 114 | 115 | * release test 22 ([#35](https://github.com/michaelangeloio/does-it-throw/issues/35)) ([73becad](https://github.com/michaelangeloio/does-it-throw/commit/73becad3667a11ce65898843c050771d6a2a0d94)) 116 | 117 | ## [0.2.2](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.2.1...does-it-throw-vscode-v0.2.2) (2023-11-09) 118 | 119 | 120 | ### Bug Fixes 121 | 122 | * release test 21 ([#33](https://github.com/michaelangeloio/does-it-throw/issues/33)) ([3c04f87](https://github.com/michaelangeloio/does-it-throw/commit/3c04f87ffdebf63e4f274d107610507fc45edd04)) 123 | 124 | ## [0.2.1](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.2.0...does-it-throw-vscode-v0.2.1) (2023-11-09) 125 | 126 | 127 | ### Bug Fixes 128 | 129 | * move release to tag event ([#31](https://github.com/michaelangeloio/does-it-throw/issues/31)) ([082713a](https://github.com/michaelangeloio/does-it-throw/commit/082713afecc40c0d2bc230ffab22e1527298a54c)) 130 | 131 | ## [0.2.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-vscode-v0.1.6...does-it-throw-vscode-v0.2.0) (2023-11-09) 132 | 133 | 134 | ### Features 135 | 136 | * try release please manifest ([a8b6e14](https://github.com/michaelangeloio/does-it-throw/commit/a8b6e14dfbf4cc3c13baa84d9570d0421ca804b1)) 137 | 138 | 139 | ### Bug Fixes 140 | 141 | * add plugins ([22ed677](https://github.com/michaelangeloio/does-it-throw/commit/22ed6770f4cd4b4805351746768a46c83400e7a3)) 142 | * proper release manifest filename ([60cbfae](https://github.com/michaelangeloio/does-it-throw/commit/60cbfaee9f01e4aa12478f12559f9d05890cb232)) 143 | * testing release ([#26](https://github.com/michaelangeloio/does-it-throw/issues/26)) ([fe1bea4](https://github.com/michaelangeloio/does-it-throw/commit/fe1bea48ac278d2d4fa23aba775e9ea5fd51c59a)) 144 | * try updated config ([aeaa47f](https://github.com/michaelangeloio/does-it-throw/commit/aeaa47f6b9c7ecfed85187523478258ca5900217)) 145 | * try updated config again ([995dc18](https://github.com/michaelangeloio/does-it-throw/commit/995dc18dd10a0c816d6b34d621e765655a8e4ed7)) 146 | * try updating action ([d71bbae](https://github.com/michaelangeloio/does-it-throw/commit/d71bbaea624f9031d90a4a26f37ff0d2b4888042)) 147 | * try updating action again ([83dec44](https://github.com/michaelangeloio/does-it-throw/commit/83dec44c31dfd1f5603587e01b7ed09e87cdbc8e)) 148 | * try updating manifest ([ee86305](https://github.com/michaelangeloio/does-it-throw/commit/ee86305f424fa3d300c144305fd1e963f4c2084c)) 149 | * update action ([2269fe5](https://github.com/michaelangeloio/does-it-throw/commit/2269fe5c92787c6685f7fe8309afdb876064a888)) 150 | * update action ([843e77c](https://github.com/michaelangeloio/does-it-throw/commit/843e77c393db15569d8fa22c016a03f1a0ac78c1)) 151 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Welcome! 2 | 3 | This project hopes to be simple to contribute to. It's written in Rust and Typescript, and uses [Bun](https://bun.sh/) to manage the VsCode build process. 4 | 5 | ## Dev Setup 6 | 7 | ### Install Dependencies 8 | 9 | Please install the following dependencies: 10 | 11 | - Pre-reqs: 12 | 13 | | Dependency | Version | 14 | | ---------- | ------- | 15 | | Node | >20 | 16 | | Bun | > 1.0.0 | 17 | | Rust + Cargo | Edition 2021 | 18 | | wasm-pack | > 0.10.0 | 19 | 20 | > rustup is recommended - https://rustup.rs/ 21 | 22 | ---- 23 | 24 | - after installing the above, please run the following: 25 | 26 | ```bash 27 | bun install 28 | ``` 29 | 30 | - install wasm-pack: 31 | 32 | ```bash 33 | cargo install wasm-pack 34 | ``` 35 | 36 | ### Notes on Bun 37 | 38 | Why both package-lock and bun.lockb? The `@vscode/vsce` package uses npm under the hood to list dependencies. It's important that we keep both package-lock and bun.lock in sync. 39 | 40 | 41 | ### VsCode - How it works (for now) 42 | 43 | The main entry point of the project is of course the `package.json`. The `package.json` contains a `scripts` section that defines the build process. 44 | 45 | To run the build process, simply run: 46 | 47 | ```bash 48 | bun run build 49 | ``` 50 | 51 | To run the build process in watch mode, simply run: 52 | 53 | ```bash 54 | bun run watch 55 | ``` 56 | 57 | Both of these scripts run `build.ts`, which executes `wasm-pack` on any `.rs` change, and `esbuild` on any `.ts` change. 58 | 59 | > We're using `esbuild` for bundling until Bun can completely support CJS, which is required by VsCode's internal LSP Node servers. 60 | 61 | After running `build.ts`, the `out` files are copied to the `server/src/rust` dir and also `server/out` directories. This is so that the TS can pick up the types generated by wasm-pack and the WebAssembly module can be loaded by the LSP server after `vsce` packages the extension. 62 | 63 | 64 | ### VsCode - Debugging 65 | 66 | After you run the watch mode in a terminal, you can press `F5` to start debugging the extension. This will open a new VsCode window with the extension running in debug mode. 67 | 68 | 69 | ## Rust - How it works (for now) 70 | 71 | There are two crates in this project: 72 | - does-it-throw 73 | - does-it-throw-wasm 74 | 75 | The `does-it-throw` crate is a library crate that contains the core logic of the extension. It's a library crate because it can be used across other LSPs and the WebAssembly module. 76 | 77 | To run integration tests on the library crate, simply run the following at the project root: 78 | 79 | ```bash 80 | cargo test 81 | ``` 82 | 83 | `does-it-throw-wasm` is a binary crate that contains the WebAssembly module. It's a binary crate because it's compiled to WebAssembly. It depends on the `does-it-throw` crate. 84 | 85 | To run unit tests on the WebAssembly module, simply run the following at the project root: 86 | 87 | ```bash 88 | cargo test 89 | ``` 90 | 91 | 92 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | version = "0.1.6" 4 | authors = [ 5 | "michaelangeloio" 6 | ] 7 | 8 | members = [ 9 | "crates/does-it-throw", 10 | "crates/does-it-throw-wasm" 11 | ] 12 | 13 | exclude = [ 14 | "docs/examples/quickstart/rust" 15 | ] 16 | 17 | [profile.release] 18 | debug = false 19 | 20 | [profile.bench] 21 | debug = true 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Michael Angelo Rivera 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |

3 | 4 |

5 | 6 |

7 | Rust Build Status 8 | TypeScript Build Status 9 |

10 | 11 | 12 | # Well, Does it Throw? 13 | 14 | TLDR; This is a *blazingly* fast **lsp server** to find *throw statements* in your javascript code. It's written in Rust and based on [SWC](https://swc.rs/) 🔥. It works for most ECMAScript files, such as `.ts`, `.js`, `.tsx` and `.jsx`. 15 | 16 | 17 | 18 | ### Check it out in action: 19 | 20 | ![demo](./assets/basic-throw.gif) 21 | 22 | Also supports **call expressions**: 23 | 24 | ![demo](./assets/calltothrow.gif) 25 | 26 | 27 | ## Why? 28 | 29 | Maybe you're working on a large codebase riddled with throw statements everywhere, and you want a better control flow. Or perhaps you're just curious about how many throw statements you have in your codebase. This simple tool can help you with that. 30 | 31 | Untyped `throw` statements can be a pain for those who come from languages like Go, where errors are typically values and first-class citizens. Even Rust has the `Result` type. Knowing where throw statements are in your codebase might be helpful, even if their return types aren't [directly supported](https://github.com/microsoft/TypeScript/issues/13219). 32 | 33 | > This extension may not be for everyone, but it's definitely for me. I hope you find it useful too. 34 | 35 | 36 | ## Installation 37 | 38 | | Platform | Installation | 39 | | -------- | ------------ | 40 | | VsCode | [Marketplace](https://marketplace.visualstudio.com/items?itemName=michaelangeloio.does-it-throw-vscode) | 41 | | Neovim | [Neovim Installation Docs](https://github.com/michaelangeloio/does-it-throw/blob/main/docs/neovim.md) | 42 | | LSP Server | [NPM](https://www.npmjs.com/package/does-it-throw-lsp) | 43 | | JetBrains | [Marketplace](https://plugins.jetbrains.com/plugin/23434-does-it-throw-?noRedirect=true) | 44 | 45 | > This extension is built with security in mind. It doesn't send any data to any third party servers. All publishes are done via a signed immutable commit from the [CI pipeline](https://github.com/michaelangeloio/does-it-throw/actions/workflows/release-vsix.yaml). 46 | 47 | > The core of the code is written in Rust, and the LSP implementation for VsCode is written in Typescript. The Rust code is compiled to WASM and bundled with the VsCode extension. The extension is published to the VsCode marketplace, and the Rust code is published to [crates.io](https://crates.io/crates/does-it-throw). 48 | 49 | ## Usage 50 | 51 | For a usage and configuration guide, check out the [usage](https://github.com/michaelangeloio/does-it-throw/blob/main/docs/usage.md) page! 52 | 53 | 54 | ## Limitations 55 | 56 | - This extension is still in its early stages. It's not perfect, but hope to gain sufficient ECMAScript coverage over time. 57 | - Currently, it only supports ECMAScript files and the following file types: `.ts`, `.js`, `.tsx` and `.jsx`. 58 | - Call expression tracing (Aka "Calls to Throws") is now set to one level deep. Hope to make this configurable in the future! 59 | 60 | > To view all known limitations, please see the [issues](https://github.com/michaelangeloio/does-it-throw/issues) page. 61 | 62 | 63 | ## Contributing 64 | Contributions are certainly welcome! Please open an issue or submit a PR. If you find a use case that isn't supported yet, please search the open issues and open a new one if it doesn't exist. 65 | 66 | ### Dev Setup 67 | Everything can be found in the [CONTRIBUTING.md](https://github.com/michaelangeloio/does-it-throw/blob/main/CONTRIBUTING.md) file! 68 | 69 | ## Feedback & Suggestions 70 | Please use the GitHub discussions tab to provide feedback and suggestions. Thanks! 71 | 72 | ## Acknowledgements 73 | 74 | - [SWC](https://swc.rs/) - The blazing fast javascript compiler written in Rust. 75 | - [Oso Vscode Extension](https://github.com/osohq/oso) - For the WASM inspiration. Excellent article [here](https://www.osohq.com/post/building-vs-code-extension-with-rust-wasm-typescript). 76 | 77 | ## License 78 | MIT - See [LICENSE](https://github.com/michaelangeloio/does-it-throw/blob/main/LICENSE.txt) for more information. 79 | -------------------------------------------------------------------------------- /assets/basic-throw.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/assets/basic-throw.gif -------------------------------------------------------------------------------- /assets/calltothrow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/assets/calltothrow.gif -------------------------------------------------------------------------------- /assets/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/assets/icon-128.png -------------------------------------------------------------------------------- /assets/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/assets/icon-small.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/assets/icon.png -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.4.0/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "linter": { 7 | "enabled": true, 8 | "rules": { 9 | "recommended": true, 10 | "suspicious": { 11 | "noExplicitAny": "warn" 12 | } 13 | }, 14 | "ignore": [ 15 | "node_modules/**/*", 16 | "server/out", 17 | "client/out", 18 | "crates/does-it-throw/src/fixtures/**/*" 19 | ] 20 | }, 21 | "formatter": { 22 | "indentStyle": "space", 23 | "indentWidth": 2, 24 | "lineWidth": 120, 25 | "enabled": true 26 | }, 27 | "javascript": { 28 | "formatter": { 29 | "semicolons": "asNeeded", 30 | "quoteStyle": "single", 31 | "trailingComma": "none" 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /build.ts: -------------------------------------------------------------------------------- 1 | import { watch } from 'chokidar' 2 | import { build as esbuild } from 'esbuild' 3 | import copy from 'esbuild-plugin-copy' 4 | import { Errorlike, readableStreamToText } from 'bun' 5 | 6 | const log = (...args: any[]) => console.log('\x1b[36m%s\x1b[0m', '[builder]', ...args) 7 | const logError = (...args: any[]) => console.log('\x1b[31m%s\x1b[0m', '[builder]', ...args) 8 | 9 | const isError = (exitCode: number) => { 10 | return exitCode !== 0 11 | } 12 | 13 | const compile = async ({ 14 | path, 15 | ready, 16 | init 17 | }: { 18 | path: string 19 | ready: boolean 20 | init: boolean 21 | }) => { 22 | if (!ready) { 23 | return 24 | } 25 | const plugins: any = [] 26 | const errors = [] 27 | 28 | /** 29 | * Compile WASM if the file is a rust file or if this is the first time 30 | */ 31 | 32 | const compileWasm = async () => { 33 | // TODO - use bun to bundle the code when it supports CJS (or vscode supports ESM) 34 | const command = 'wasm-pack build crates/does-it-throw-wasm --target nodejs --out-dir ../../server/src/rust' 35 | log('building wasm') 36 | const { stdout, exited } = Bun.spawn(command.split(' '), { 37 | cwd: __dirname 38 | }) 39 | const [stdOut, exitCode] = await Promise.all([await readableStreamToText(stdout), exited]) 40 | if (isError(exitCode)) { 41 | return { 42 | isError: true, 43 | error: stdOut, 44 | type: 'wasm' 45 | } 46 | } 47 | log('done building wasm', stdOut) 48 | return { 49 | isError: false, 50 | error: null, 51 | type: 'wasm' 52 | } 53 | } 54 | 55 | const typeCheck = async (project: 'server' | 'client') => { 56 | const command = `tsc -p ${project}/tsconfig.json --noEmit` 57 | log('type checking') 58 | const { exited, stdout } = Bun.spawn(command.split(' '), { 59 | cwd: __dirname 60 | }) 61 | const [stdOut, exitCode] = await Promise.all([await readableStreamToText(stdout), exited]) 62 | if (isError(exitCode)) { 63 | return { 64 | isError: true, 65 | error: stdOut, 66 | type: `type-check-${project}` 67 | } 68 | } 69 | log('done type checking', stdOut) 70 | return { 71 | isError: false, 72 | error: null, 73 | type: `type-check-${project}` 74 | } 75 | } 76 | 77 | /** 78 | * Compile/Bundle the client 79 | */ 80 | if (path.includes('.rs') || init) { 81 | plugins.push( 82 | copy({ 83 | resolveFrom: 'cwd', 84 | assets: { 85 | from: ['./server/src/rust/**/*'], 86 | to: ['./server/out'] 87 | }, 88 | watch: true 89 | }) as any 90 | ) 91 | } 92 | const compileClient = async () => { 93 | try { 94 | log('compiling client ts') 95 | await esbuild({ 96 | minify: true, 97 | sourcemap: true, 98 | bundle: true, 99 | outdir: 'client/out', 100 | entryPoints: ['client/src/extension.ts'], 101 | external: ['vscode'], 102 | platform: 'node', 103 | format: 'cjs', 104 | tsconfig: 'client/tsconfig.json', 105 | plugins 106 | }) 107 | log('done compiling client') 108 | return { 109 | isError: false, 110 | error: null, 111 | type: 'client-compile' 112 | } 113 | } catch (error) { 114 | return { 115 | isError: true, 116 | error, 117 | type: 'client-compile' 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Compile/Bundle the server 124 | */ 125 | const compileServer = async () => { 126 | try { 127 | log('compiling server ts') 128 | await esbuild({ 129 | outdir: 'server/out', 130 | minify: true, 131 | bundle: true, 132 | sourcemap: true, 133 | platform: 'node', 134 | external: ['vscode'], 135 | entryPoints: ['server/src/server.ts'], 136 | format: 'cjs', 137 | tsconfig: 'server/tsconfig.json', 138 | plugins 139 | }) 140 | log('done compiling server') 141 | return { 142 | isError: false, 143 | error: null, 144 | type: 'server-compile' 145 | } 146 | } catch (error) { 147 | return { 148 | isError: true, 149 | error, 150 | type: 'server-compile' 151 | } 152 | } 153 | } 154 | 155 | const copyREADME = async () => { 156 | const [readMe, license] = await Promise.all([ 157 | await Bun.file('README.md').text(), 158 | await Bun.file('LICENSE.txt').text() 159 | ]) 160 | const baseUrl = 'https://github.com/michaelangeloio/does-it-throw/blob/main' 161 | function replaceLinks(markdown: string, prefix: string) { 162 | const regex = /!\[.*?\]\((\.\/assets\/(basic-throw|calltothrow).*?)\)/g 163 | return markdown.replace(regex, (match, p1) => { 164 | return match.replace(p1, prefix + p1.substring(1)) 165 | }) 166 | } 167 | const newReadMe = replaceLinks(readMe, baseUrl) 168 | log('writing new readme') 169 | await Promise.all([Bun.write('server/README.md', newReadMe), Bun.write('server/LICENSE.txt', license)]) 170 | return { 171 | isError: false, 172 | error: null, 173 | type: 'copy-readme' 174 | } 175 | } 176 | 177 | /** 178 | * Build everything in parallel after wasm 179 | */ 180 | if (path.includes('.rs') || init) { 181 | const result = await compileWasm() 182 | if (result.isError) { 183 | errors.push(result) 184 | return errors 185 | } 186 | } 187 | 188 | errors.push( 189 | ...(await Promise.all([compileClient(), compileServer(), typeCheck('server'), typeCheck('client'), copyREADME()])) 190 | ) 191 | return errors 192 | } 193 | 194 | async function main() { 195 | const isWatch = process.argv.includes('--watch') 196 | if (!isWatch) { 197 | log('not in watch mode, compiling once') 198 | const errors = await compile({ 199 | init: true, 200 | ready: true, 201 | path: '' 202 | }) 203 | const hasErrors = errors?.filter((error) => error.isError) 204 | if (hasErrors?.length) { 205 | logError('errors compiling') 206 | for (const error of hasErrors) { 207 | logError(error) 208 | } 209 | throw new Error('exiting') 210 | } 211 | return 212 | } 213 | 214 | await compile({ 215 | init: true, 216 | ready: true, 217 | path: '' 218 | }) 219 | 220 | const watcher = watch('**/*.{ts,rs}', { 221 | ignored: ['(node_modules|target)/**/*', 'server/out/**/*', 'client/out/**/*'] 222 | }) 223 | let ready = false 224 | 225 | watcher.on('ready', async () => { 226 | log('ready') 227 | ready = true 228 | }) 229 | 230 | watcher.on('change', async (path) => { 231 | log('change', path) 232 | await compile({ 233 | init: false, 234 | path, 235 | ready 236 | }) 237 | }) 238 | watcher.on('add', async (path) => { 239 | await compile({ 240 | path, 241 | ready, 242 | init: false 243 | }) 244 | }) 245 | } 246 | 247 | main() 248 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/bun.lockb -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vscode-does-it-throw-client", 3 | "description": "VSCode Client for the Does It Throw extension", 4 | "author": { 5 | "name": "Michael Angelo", 6 | "email": "email@michaelangelo.io" 7 | }, 8 | "license": "MIT", 9 | "version": "0.1.6", 10 | "publisher": "vscode", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/Microsoft/vscode-extension-samples" 14 | }, 15 | "engines": { 16 | "vscode": "^1.75.0" 17 | }, 18 | "dependencies": { 19 | "vscode-languageclient": "^9.0.1" 20 | }, 21 | "devDependencies": { 22 | "@types/vscode": "^1.85.0", 23 | "@vscode/test-electron": "^2.3.8" 24 | } 25 | } -------------------------------------------------------------------------------- /client/src/extension.ts: -------------------------------------------------------------------------------- 1 | /* -------------------------------------------------------------------------------------------- 2 | * Copyright (c) Microsoft Corporation. All rights reserved. 3 | * Licensed under the MIT License. See License.txt in the project root for license information. 4 | * ------------------------------------------------------------------------------------------ */ 5 | 6 | import * as path from 'path' 7 | import { ExtensionContext, workspace } from 'vscode' 8 | 9 | import { LanguageClient, LanguageClientOptions, ServerOptions, TransportKind } from 'vscode-languageclient/node' 10 | 11 | let client: LanguageClient 12 | 13 | export function activate(context: ExtensionContext) { 14 | // The server is implemented in node 15 | const serverModule = context.asAbsolutePath(path.join('server', 'out', 'server.js')) 16 | 17 | // If the extension is launched in debug mode then the debug server options are used 18 | // Otherwise the run options are used 19 | const serverOptions: ServerOptions = { 20 | run: { module: serverModule, transport: TransportKind.ipc }, 21 | debug: { 22 | module: serverModule, 23 | transport: TransportKind.ipc 24 | } 25 | } 26 | 27 | // Options to control the language client 28 | const clientOptions: LanguageClientOptions = { 29 | // Register the server for plain text documents 30 | documentSelector: [ 31 | { 32 | scheme: 'file', 33 | language: 'typescript' 34 | }, 35 | { 36 | scheme: 'file', 37 | language: 'javascript' 38 | }, 39 | { 40 | scheme: 'file', 41 | language: 'javascriptreact' 42 | }, 43 | { 44 | scheme: 'file', 45 | language: 'typescriptreact' 46 | } 47 | ], 48 | synchronize: { 49 | // Notify the server about file changes to '.clientrc files contained in the workspace 50 | fileEvents: workspace.createFileSystemWatcher('**/.clientrc') 51 | } 52 | } 53 | 54 | // Create the language client and start the client. 55 | client = new LanguageClient('doesItThrow', 'Does it Throw? Language Server', serverOptions, clientOptions) 56 | 57 | // Start the client. This will also launch the server 58 | client.start() 59 | } 60 | 61 | export function deactivate(): Thenable | undefined { 62 | if (!client) { 63 | return undefined 64 | } 65 | return client.stop() 66 | } 67 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "module": "commonjs", 5 | "target": "es2021", 6 | "lib": ["es2021"], 7 | "outDir": "out", 8 | "rootDir": "src", 9 | "sourceMap": true 10 | }, 11 | "include": ["src"], 12 | "exclude": ["node_modules", ".vscode-test"] 13 | } 14 | -------------------------------------------------------------------------------- /crates/does-it-throw-wasm/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | * The following workspace dependencies were updated 4 | * dependencies 5 | * does-it-throw bumped from 0.1.13 to 0.1.14 6 | 7 | * The following workspace dependencies were updated 8 | * dependencies 9 | * does-it-throw bumped from 0.1.14 to 0.1.15 10 | 11 | ## [0.3.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.2.0...does-it-throw-wasm-v0.3.0) (2024-01-25) 12 | 13 | 14 | ### Features 15 | 16 | * **deps-dev:** bump @biomejs/biome from 1.4.1 to 1.5.3 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 17 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.5 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 18 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.6 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 19 | * **deps-dev:** bump bun-types from 1.0.20 to 1.0.25 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 20 | * **deps-dev:** bump esbuild from 0.19.11 to 0.19.12 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 21 | * **deps:** bump serde from 1.0.193 to 1.0.195 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 22 | * **deps:** bump serde_json from 1.0.109 to 1.0.111 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 23 | * **deps:** bump swc_ecma_ast from 0.110.15 to 0.110.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 24 | * **deps:** bump swc_ecma_ast from 0.110.17 to 0.111.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 25 | * **deps:** bump swc_ecma_parser from 0.141.34 to 0.142.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 26 | * **deps:** bump swc_ecma_visit from 0.96 to 0.97.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 27 | * **deps:** bump swc_ecma_visit from 0.96.15 to 0.96.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 28 | * **deps:** bump vscode-languageserver-textdocument from 1.0.8 to 1.0.11 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 29 | * **deps:** bump wasm-bindgen from 0.2.89 to 0.2.90 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 30 | 31 | 32 | ### Dependencies 33 | 34 | * The following workspace dependencies were updated 35 | * dependencies 36 | * does-it-throw bumped from 0.2.0 to 0.3.0 37 | 38 | ## [0.2.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.15...does-it-throw-wasm-v0.2.0) (2024-01-06) 39 | 40 | 41 | ### Features 42 | 43 | * **deps-dev:** bump mockall from 0.12.0 to 0.12.1 ([#101](https://github.com/michaelangeloio/does-it-throw/issues/101)) ([211b21f](https://github.com/michaelangeloio/does-it-throw/commit/211b21f402d94b8bbf26e92b0185aeb5b1b0d196)) 44 | * **deps:** bump serde_json from 1.0.108 to 1.0.109 ([#109](https://github.com/michaelangeloio/does-it-throw/issues/109)) ([5a28b3f](https://github.com/michaelangeloio/does-it-throw/commit/5a28b3f26992c4bca9d7bb276efdd27fa5b9a53a)) 45 | * **deps:** bump swc_ecma_parser from 0.141.33 to 0.141.34 ([#110](https://github.com/michaelangeloio/does-it-throw/issues/110)) ([482be78](https://github.com/michaelangeloio/does-it-throw/commit/482be78a20732f350377d4e534afae1053080e58)) 46 | * user can now discard warnings with ignore statements ([#118](https://github.com/michaelangeloio/does-it-throw/issues/118)) ([3f8957c](https://github.com/michaelangeloio/does-it-throw/commit/3f8957c60fd90f9ab7b6646c04ec22dcecb21556)) 47 | 48 | 49 | ### Dependencies 50 | 51 | * The following workspace dependencies were updated 52 | * dependencies 53 | * does-it-throw bumped from 0.1.15 to 0.2.0 54 | 55 | ## [0.1.13](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.12...does-it-throw-wasm-v0.1.13) (2023-12-17) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * functions and throw statements are underlined even if caught ([#81](https://github.com/michaelangeloio/does-it-throw/issues/81)) ([16adf85](https://github.com/michaelangeloio/does-it-throw/commit/16adf85b05b92542fa6c09ac1611dd56c7603c99)) 61 | 62 | 63 | ### Dependencies 64 | 65 | * The following workspace dependencies were updated 66 | * dependencies 67 | * does-it-throw bumped from 0.1.12 to 0.1.13 68 | 69 | ## [0.1.12](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.11...does-it-throw-wasm-v0.1.12) (2023-12-16) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * bump swc_ecma_ast to 0.110.9 ([#75](https://github.com/michaelangeloio/does-it-throw/issues/75)) ([0aa2e91](https://github.com/michaelangeloio/does-it-throw/commit/0aa2e91f4f1c0b9e352d052382c5a7f436cffeb9)) 75 | 76 | 77 | ### Dependencies 78 | 79 | * The following workspace dependencies were updated 80 | * dependencies 81 | * does-it-throw bumped from 0.1.11 to 0.1.12 82 | 83 | ## [0.1.11](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.10...does-it-throw-wasm-v0.1.11) (2023-11-10) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * re-organize primary crate into modules ([#42](https://github.com/michaelangeloio/does-it-throw/issues/42)) ([badb106](https://github.com/michaelangeloio/does-it-throw/commit/badb1061d0dfc679458d55609e43cccfdca01794)) 89 | * update details, fix logic in some call expressions, including spread operators ([#40](https://github.com/michaelangeloio/does-it-throw/issues/40)) ([cdfdf47](https://github.com/michaelangeloio/does-it-throw/commit/cdfdf47a2d657364abc1b3b3ce97e89405b842b3)) 90 | 91 | 92 | ### Dependencies 93 | 94 | * The following workspace dependencies were updated 95 | * dependencies 96 | * does-it-throw bumped from 0.1.10 to 0.1.11 97 | 98 | ## [0.1.10](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.9...does-it-throw-wasm-v0.1.10) (2023-11-09) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * release test 22 ([#35](https://github.com/michaelangeloio/does-it-throw/issues/35)) ([73becad](https://github.com/michaelangeloio/does-it-throw/commit/73becad3667a11ce65898843c050771d6a2a0d94)) 104 | 105 | 106 | ### Dependencies 107 | 108 | * The following workspace dependencies were updated 109 | * dependencies 110 | * does-it-throw bumped from 0.1.9 to 0.1.10 111 | 112 | ## [0.1.9](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.8...does-it-throw-wasm-v0.1.9) (2023-11-09) 113 | 114 | 115 | ### Bug Fixes 116 | 117 | * release test 21 ([#33](https://github.com/michaelangeloio/does-it-throw/issues/33)) ([3c04f87](https://github.com/michaelangeloio/does-it-throw/commit/3c04f87ffdebf63e4f274d107610507fc45edd04)) 118 | 119 | 120 | ### Dependencies 121 | 122 | * The following workspace dependencies were updated 123 | * dependencies 124 | * does-it-throw bumped from 0.1.8 to 0.1.9 125 | 126 | ## [0.1.8](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.7...does-it-throw-wasm-v0.1.8) (2023-11-09) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * move release to tag event ([#31](https://github.com/michaelangeloio/does-it-throw/issues/31)) ([082713a](https://github.com/michaelangeloio/does-it-throw/commit/082713afecc40c0d2bc230ffab22e1527298a54c)) 132 | 133 | 134 | ### Dependencies 135 | 136 | * The following workspace dependencies were updated 137 | * dependencies 138 | * does-it-throw bumped from 0.1.7 to 0.1.8 139 | 140 | ## [0.1.7](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-wasm-v0.1.6...does-it-throw-wasm-v0.1.7) (2023-11-09) 141 | 142 | 143 | ### Bug Fixes 144 | 145 | * try updated config again ([995dc18](https://github.com/michaelangeloio/does-it-throw/commit/995dc18dd10a0c816d6b34d621e765655a8e4ed7)) 146 | 147 | 148 | ### Dependencies 149 | 150 | * The following workspace dependencies were updated 151 | * dependencies 152 | * does-it-throw bumped from 0.1.6 to 0.1.7 153 | -------------------------------------------------------------------------------- /crates/does-it-throw-wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "does-it-throw-wasm" 4 | version = "0.3.0" 5 | description = "a WASM binding for does-it-throw - using SWC" 6 | license = "MIT" 7 | documentation = "https://github.com/michaelangeloio/does-it-throw/blob/main/readme.md" 8 | repository = "https://github.com/michaelangeloio/does-it-throw" 9 | homepage = "https://github.com/michaelangeloio/does-it-throw/blob/main/readme.md" 10 | 11 | 12 | [lib] 13 | crate-type = ["cdylib"] 14 | 15 | [dependencies] 16 | does-it-throw = { path = "../does-it-throw", version = "0.3.0"} 17 | swc_common = "0.33" 18 | swc_ecma_parser = "0.142.1" 19 | swc_ecma_ast = "0.111.1" 20 | swc_ecma_visit = "0.97.1" 21 | wasm-bindgen = {version = "0.2.90"} 22 | serde-wasm-bindgen = "0.6.1" 23 | serde_json = "1.0.111" 24 | serde = { version = "1.0", features = ["derive"] } 25 | 26 | 27 | [dev-dependencies] 28 | mockall = "0.12.1" 29 | 30 | [profile.release] 31 | lto = true 32 | -------------------------------------------------------------------------------- /crates/does-it-throw/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.3.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.2.0...does-it-throw-v0.3.0) (2024-01-25) 4 | 5 | 6 | ### Features 7 | 8 | * **deps:** bump swc_ecma_ast from 0.110.17 to 0.111.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 9 | * **deps:** bump swc_ecma_parser from 0.141.34 to 0.142.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 10 | * **deps:** bump swc_ecma_visit from 0.96 to 0.97.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * enhance call to throw logic and handle return statements ([#140](https://github.com/michaelangeloio/does-it-throw/issues/140)) ([a1bfaf1](https://github.com/michaelangeloio/does-it-throw/commit/a1bfaf16c768aeb49ecaecb991ca6a2b57e71072)) 16 | 17 | ## [0.2.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.15...does-it-throw-v0.2.0) (2024-01-06) 18 | 19 | 20 | ### Features 21 | 22 | * user can now discard warnings with ignore statements ([#118](https://github.com/michaelangeloio/does-it-throw/issues/118)) ([3f8957c](https://github.com/michaelangeloio/does-it-throw/commit/3f8957c60fd90f9ab7b6646c04ec22dcecb21556)) 23 | 24 | ## [0.1.15](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.14...does-it-throw-v0.1.15) (2023-12-24) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * catch with throw statement not included ([#95](https://github.com/michaelangeloio/does-it-throw/issues/95)) ([fd223db](https://github.com/michaelangeloio/does-it-throw/commit/fd223db4f56e87439999b9b33a393769bd2b7c5b)) 30 | 31 | ## [0.1.14](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.13...does-it-throw-v0.1.14) (2023-12-17) 32 | 33 | 34 | ### Bug Fixes 35 | 36 | * add missing unit test for try statement ([#88](https://github.com/michaelangeloio/does-it-throw/issues/88)) ([290a323](https://github.com/michaelangeloio/does-it-throw/commit/290a323bae194d293ff8d0c826738f72dfef6212)) 37 | 38 | ## [0.1.13](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.12...does-it-throw-v0.1.13) (2023-12-17) 39 | 40 | 41 | ### Bug Fixes 42 | 43 | * functions and throw statements are underlined even if caught ([#81](https://github.com/michaelangeloio/does-it-throw/issues/81)) ([16adf85](https://github.com/michaelangeloio/does-it-throw/commit/16adf85b05b92542fa6c09ac1611dd56c7603c99)) 44 | 45 | ## [0.1.12](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.11...does-it-throw-v0.1.12) (2023-12-16) 46 | 47 | 48 | ### Bug Fixes 49 | 50 | * add biome for standardization, ensure the builder reports errors correctly ([#72](https://github.com/michaelangeloio/does-it-throw/issues/72)) ([0d18392](https://github.com/michaelangeloio/does-it-throw/commit/0d18392268516abb79d015f90495dd331e7ef998)) 51 | * results should still show even if file cannot resolve (calls to throws) ([#76](https://github.com/michaelangeloio/does-it-throw/issues/76)) ([f908556](https://github.com/michaelangeloio/does-it-throw/commit/f908556dfda8eca9195c87269fac71bc6d3e8bf9)) 52 | 53 | ## [0.1.11](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.10...does-it-throw-v0.1.11) (2023-11-10) 54 | 55 | 56 | ### Bug Fixes 57 | 58 | * add coverage for switch statements ([#43](https://github.com/michaelangeloio/does-it-throw/issues/43)) ([99fda18](https://github.com/michaelangeloio/does-it-throw/commit/99fda183a7ca813cbb5f5434f429cd79b594f139)) 59 | * re-organize primary crate into modules ([#42](https://github.com/michaelangeloio/does-it-throw/issues/42)) ([badb106](https://github.com/michaelangeloio/does-it-throw/commit/badb1061d0dfc679458d55609e43cccfdca01794)) 60 | * update details, fix logic in some call expressions, including spread operators ([#40](https://github.com/michaelangeloio/does-it-throw/issues/40)) ([cdfdf47](https://github.com/michaelangeloio/does-it-throw/commit/cdfdf47a2d657364abc1b3b3ce97e89405b842b3)) 61 | 62 | ## [0.1.10](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.9...does-it-throw-v0.1.10) (2023-11-09) 63 | 64 | 65 | ### Bug Fixes 66 | 67 | * release test 22 ([#35](https://github.com/michaelangeloio/does-it-throw/issues/35)) ([73becad](https://github.com/michaelangeloio/does-it-throw/commit/73becad3667a11ce65898843c050771d6a2a0d94)) 68 | 69 | ## [0.1.9](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.8...does-it-throw-v0.1.9) (2023-11-09) 70 | 71 | 72 | ### Bug Fixes 73 | 74 | * release test 21 ([#33](https://github.com/michaelangeloio/does-it-throw/issues/33)) ([3c04f87](https://github.com/michaelangeloio/does-it-throw/commit/3c04f87ffdebf63e4f274d107610507fc45edd04)) 75 | 76 | ## [0.1.8](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.7...does-it-throw-v0.1.8) (2023-11-09) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * move release to tag event ([#31](https://github.com/michaelangeloio/does-it-throw/issues/31)) ([082713a](https://github.com/michaelangeloio/does-it-throw/commit/082713afecc40c0d2bc230ffab22e1527298a54c)) 82 | 83 | ## [0.1.7](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-v0.1.6...does-it-throw-v0.1.7) (2023-11-09) 84 | 85 | 86 | ### Bug Fixes 87 | 88 | * try updated config again ([995dc18](https://github.com/michaelangeloio/does-it-throw/commit/995dc18dd10a0c816d6b34d621e765655a8e4ed7)) 89 | -------------------------------------------------------------------------------- /crates/does-it-throw/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "does-it-throw" 3 | version = "0.3.0" 4 | description = "a library for getting ES throw statements - using SWC" 5 | license = "MIT" 6 | documentation = "https://github.com/michaelangeloio/does-it-throw/blob/main/readme.md" 7 | repository = "https://github.com/michaelangeloio/does-it-throw" 8 | homepage = "https://github.com/michaelangeloio/does-it-throw/blob/main/readme.md" 9 | 10 | [lib] 11 | bench = false 12 | 13 | [dependencies] 14 | swc_common = "0.33" 15 | swc_ecma_parser = "0.142.1" 16 | swc_ecma_ast = "0.111.1" 17 | swc_ecma_visit = "0.97.1" 18 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/call_finder.rs: -------------------------------------------------------------------------------- 1 | extern crate swc_common; 2 | extern crate swc_ecma_ast; 3 | extern crate swc_ecma_parser; 4 | extern crate swc_ecma_visit; 5 | 6 | use std::collections::hash_map::DefaultHasher; 7 | use std::collections::{HashMap, HashSet}; 8 | use std::hash::{Hash, Hasher}; 9 | 10 | use swc_ecma_ast::{ 11 | ArrowExpr, AwaitExpr, BinExpr, BlockStmtOrExpr, Callee, ClassDecl, ClassMethod, Decl, FnDecl, 12 | JSXAttr, JSXAttrOrSpread, JSXAttrValue, JSXExpr, JSXOpeningElement, MemberExpr, OptChainBase, 13 | OptChainExpr, ParenExpr, Stmt, VarDeclarator, 14 | }; 15 | 16 | use crate::throw_finder::ThrowMap; 17 | 18 | use self::swc_common::Span; 19 | use self::swc_ecma_ast::{CallExpr, Expr, Function, MemberProp}; 20 | 21 | use self::swc_ecma_visit::Visit; 22 | 23 | pub struct CallToThrowMap { 24 | pub call_span: Span, 25 | pub call_function_or_method_name: String, 26 | pub call_class_name: Option, 27 | pub throw_map: ThrowMap, 28 | pub class_name: Option, 29 | pub id: String, 30 | } 31 | 32 | impl PartialEq for CallToThrowMap { 33 | fn eq(&self, other: &Self) -> bool { 34 | self.id == other.id 35 | } 36 | } 37 | 38 | impl Eq for CallToThrowMap {} 39 | 40 | impl Hash for CallToThrowMap { 41 | fn hash(&self, state: &mut H) { 42 | self.id.hash(state); 43 | self.call_span.lo.hash(state); 44 | self.call_span.hi.hash(state); 45 | } 46 | } 47 | 48 | pub struct InstantiationsMap { 49 | pub class_name: String, 50 | pub variable_name: String, 51 | pub instantiation_span: Span, 52 | } 53 | 54 | impl PartialEq for InstantiationsMap { 55 | fn eq(&self, other: &Self) -> bool { 56 | self.variable_name == other.variable_name 57 | } 58 | } 59 | 60 | impl Eq for InstantiationsMap {} 61 | 62 | impl Hash for InstantiationsMap { 63 | fn hash(&self, state: &mut H) { 64 | self.variable_name.hash(state); 65 | } 66 | } 67 | 68 | // ----- CallFinder Visitor implementation ----- 69 | // This module defines structures and implements functionality for identifying and mapping 70 | // function calls to their respective functions or methods that throw exceptions. It uses 71 | // SWC's visitor pattern to traverse the AST (Abstract Syntax Tree) of JavaScript or TypeScript code. 72 | /// 73 | // `CallToThrowMap` records the mapping of a function call to a function that throws. 74 | // It captures the span of the call, the name of the function/method being called, 75 | // the class name if the call is a method call, and the `ThrowMap` that provides details 76 | // about the throw statement in the called function/method. 77 | /// 78 | // `InstantiationsMap` keeps track of class instantiations by recording the class name 79 | // and the variable name that holds the instance. 80 | /// 81 | // `CallFinder` is the core structure that uses the Visitor pattern to traverse the AST nodes. 82 | // It maintains state as it goes through the code, keeping track of current class names, 83 | // function name stacks, and object property stacks. As it finds function calls, it tries 84 | // to match them with known functions or methods that throw exceptions using the data 85 | // accumulated in `functions_with_throws`. When a match is found, it records the mapping 86 | // in `calls`. It also tracks instantiations of classes to help resolve method calls. 87 | 88 | pub struct CallFinder { 89 | pub calls: HashSet, 90 | pub functions_with_throws: HashSet, 91 | pub current_class_name: Option, 92 | pub instantiations: HashMap, 93 | pub function_name_stack: Vec, 94 | pub object_property_stack: Vec, 95 | pub processed_calls: HashSet, 96 | } 97 | 98 | impl CallFinder { 99 | // use the lo and hi of the Span to generate a unique ID so that we don't 100 | // record duplicate calls 101 | // We also use the instantiation span for instances to ensure uniqueness 102 | // for calls to methods on instances 103 | fn generate_unique_call_id(&self, call: &CallExpr) -> u64 { 104 | let mut hasher = DefaultHasher::new(); 105 | 106 | if let Callee::Expr(expr) = &call.callee { 107 | if let Expr::Member(member_expr) = &**expr { 108 | if let Expr::Ident(instance_ident) = &*member_expr.obj { 109 | if let Some(inst_map) = self.instantiations.get(&instance_ident.sym.to_string()) { 110 | // Use the instantiation span for instances 111 | inst_map.instantiation_span.hash(&mut hasher); 112 | } 113 | } 114 | } 115 | } 116 | 117 | // Also hash the call span to ensure uniqueness 118 | call.span.hash(&mut hasher); 119 | hasher.finish() 120 | } 121 | 122 | fn handle_bin_expr(&mut self, bin_expr: &BinExpr) { 123 | if let Expr::Call(call_expr) = &*bin_expr.left { 124 | self.visit_call_expr(call_expr); 125 | } 126 | if let Expr::Call(call_expr) = &*bin_expr.right { 127 | self.visit_call_expr(call_expr); 128 | } 129 | if let Expr::Await(await_expr) = &*bin_expr.left { 130 | self.handle_await_expr(await_expr); 131 | } 132 | if let Expr::Await(await_expr) = &*bin_expr.right { 133 | self.handle_await_expr(await_expr); 134 | } 135 | if let Expr::OptChain(opt_chain_expr) = &*bin_expr.left { 136 | self.handle_opt_chain_expr(opt_chain_expr); 137 | } 138 | if let Expr::OptChain(opt_chain_expr) = &*bin_expr.right { 139 | self.handle_opt_chain_expr(opt_chain_expr); 140 | } 141 | if let Expr::Paren(paren_expr) = &*bin_expr.left { 142 | self.handle_paren_expr(paren_expr); 143 | } 144 | if let Expr::Paren(paren_expr) = &*bin_expr.right { 145 | self.handle_paren_expr(paren_expr); 146 | } 147 | } 148 | 149 | fn handle_paren_expr(&mut self, paren_expr: &ParenExpr) { 150 | if let Expr::Call(call_expr) = &*paren_expr.expr { 151 | self.visit_call_expr(call_expr); 152 | } 153 | if let Expr::Await(await_expr) = &*paren_expr.expr { 154 | self.handle_await_expr(await_expr); 155 | } 156 | if let Expr::OptChain(opt_chain_expr) = &*paren_expr.expr { 157 | self.handle_opt_chain_expr(opt_chain_expr); 158 | } 159 | } 160 | 161 | fn handle_opt_chain_expr(&mut self, opt_chain_expr: &OptChainExpr) { 162 | if let OptChainBase::Member(expr) = &*opt_chain_expr.base { 163 | if let Expr::Call(call_expr) = &*expr.obj { 164 | self.visit_call_expr(call_expr); 165 | } 166 | } 167 | } 168 | 169 | fn handle_await_expr(&mut self, await_expr: &AwaitExpr) { 170 | if let Expr::Call(call_expr) = &*await_expr.arg { 171 | self.visit_call_expr(call_expr); 172 | } 173 | } 174 | } 175 | 176 | impl Visit for CallFinder { 177 | fn visit_class_decl(&mut self, class_decl: &ClassDecl) { 178 | self.current_class_name = Some(class_decl.ident.sym.to_string()); 179 | self.visit_class(&class_decl.class); 180 | self.current_class_name = None; 181 | } 182 | 183 | fn visit_fn_decl(&mut self, fn_decl: &FnDecl) { 184 | let function_name = fn_decl.ident.sym.to_string(); 185 | self.function_name_stack.push(function_name); 186 | 187 | swc_ecma_visit::visit_fn_decl(self, fn_decl); 188 | 189 | self.function_name_stack.pop(); 190 | } 191 | 192 | fn visit_member_expr(&mut self, member_expr: &MemberExpr) { 193 | if let MemberProp::Ident(ident) = &member_expr.prop { 194 | self.object_property_stack.push(ident.sym.to_string()); 195 | } 196 | swc_ecma_visit::visit_member_expr(self, member_expr); 197 | self.object_property_stack.pop(); 198 | } 199 | 200 | fn visit_class_method(&mut self, method: &ClassMethod) { 201 | if let Some(method_ident) = method.key.as_ident() { 202 | self 203 | .object_property_stack 204 | .push(method_ident.sym.to_string()); 205 | } 206 | 207 | swc_ecma_visit::visit_class_method(self, method); 208 | 209 | self.object_property_stack.pop(); 210 | } 211 | 212 | fn visit_jsx_opening_element(&mut self, jsx_opening_element: &JSXOpeningElement) { 213 | for attr in &jsx_opening_element.attrs { 214 | if let JSXAttrOrSpread::JSXAttr(attr) = attr { 215 | self.visit_jsx_attr(attr); 216 | } 217 | } 218 | } 219 | 220 | fn visit_jsx_attr(&mut self, jsx_attr: &JSXAttr) { 221 | if let Some(JSXAttrValue::JSXExprContainer(expr_container)) = &jsx_attr.value { 222 | if let JSXExpr::Expr(expr) = &expr_container.expr { 223 | // Check if the expression is a function call 224 | if let Expr::Call(call_expr) = &**expr { 225 | self.visit_call_expr(call_expr) 226 | } 227 | } 228 | } 229 | } 230 | 231 | fn visit_call_expr(&mut self, call: &CallExpr) { 232 | if let Callee::Expr(expr) = &call.callee { 233 | let call_id = self.generate_unique_call_id(call); 234 | // If we've already processed this call, skip it 235 | if !self.processed_calls.insert(call_id) { 236 | // This call was already processed, so return early 237 | return; 238 | } 239 | match &**expr { 240 | Expr::Member(member_expr) => { 241 | let mut possible_class_name = None; 242 | if let Expr::Ident(object_ident) = &*member_expr.obj { 243 | possible_class_name = Some(object_ident.sym.to_string()); 244 | } else if let Expr::This(_) = &*member_expr.obj { 245 | possible_class_name = self.current_class_name.clone(); 246 | } 247 | if let Some(ref obj_name) = possible_class_name { 248 | let mut new_class_name = None; 249 | if let Some(instantiation) = self.instantiations.get(obj_name) { 250 | new_class_name = Some(instantiation.class_name.clone()); 251 | } 252 | if let Some(class_name) = new_class_name { 253 | possible_class_name = Some(class_name); 254 | } 255 | } 256 | 257 | if let MemberProp::Ident(method_ident) = &member_expr.prop { 258 | let called_method_name = method_ident.sym.to_string(); 259 | for throw_map in self.functions_with_throws.iter() { 260 | let call_function_or_method_name = 261 | if let Some(function_name) = self.function_name_stack.last() { 262 | function_name.clone() 263 | } else if let Some(property_name) = self.object_property_stack.last() { 264 | property_name.clone() 265 | } else { 266 | "".to_string() 267 | }; 268 | if throw_map.function_or_method_name == called_method_name { 269 | let class_name_or_not_set = self 270 | .current_class_name 271 | .clone() 272 | .or(possible_class_name.clone()) 273 | .unwrap_or_else(|| "NOT_SET".to_string()); 274 | let call_to_throw_map = CallToThrowMap { 275 | call_span: call.span, 276 | throw_map: throw_map.clone(), 277 | call_class_name: Some(class_name_or_not_set.clone()), 278 | call_function_or_method_name: call_function_or_method_name.clone(), 279 | class_name: possible_class_name.clone(), 280 | id: format!( 281 | "{}-{}", 282 | class_name_or_not_set, 283 | call_function_or_method_name.clone() 284 | ), 285 | }; 286 | self.calls.insert(call_to_throw_map); 287 | break; 288 | } 289 | } 290 | for arg in &call.args { 291 | self.function_name_stack.push(method_ident.sym.to_string()); 292 | self.current_class_name = possible_class_name.clone(); 293 | if let Expr::Arrow(arrow_expr) = &*arg.expr { 294 | self.visit_arrow_expr(arrow_expr); 295 | } 296 | if let Expr::Fn(fn_expr) = &*arg.expr { 297 | self.visit_function(&fn_expr.function); 298 | } 299 | self.function_name_stack.pop(); 300 | self.current_class_name = None; 301 | } 302 | } 303 | } 304 | Expr::Ident(ident) => { 305 | let called_function_name = ident.sym.to_string(); 306 | for throw_map in self.functions_with_throws.iter() { 307 | let potential_throw_id = format!( 308 | "{}-{}", 309 | self 310 | .current_class_name 311 | .clone() 312 | .unwrap_or_else(|| "NOT_SET".to_string()), 313 | called_function_name 314 | ); 315 | if throw_map.id == potential_throw_id { 316 | let call_function_or_method_name = self 317 | .function_name_stack 318 | .last() 319 | .cloned() 320 | .unwrap_or_else(|| "".to_string()); 321 | // The function being called is known to throw 322 | let call_to_throw_map = CallToThrowMap { 323 | call_span: call.span, 324 | throw_map: throw_map.clone(), 325 | call_class_name: self.current_class_name.clone(), 326 | call_function_or_method_name: call_function_or_method_name.clone(), 327 | class_name: None, 328 | id: format!( 329 | "{}-{}", 330 | self 331 | .current_class_name 332 | .clone() 333 | .unwrap_or_else(|| "NOT_SET".to_string()), 334 | call_function_or_method_name 335 | ), 336 | }; 337 | self.calls.insert(call_to_throw_map); 338 | break; 339 | } 340 | } 341 | for arg in &call.args { 342 | self.function_name_stack.push(called_function_name.clone()); 343 | if let Expr::Arrow(arrow_expr) = &*arg.expr { 344 | self.visit_arrow_expr(arrow_expr); 345 | } 346 | if let Expr::Fn(fn_expr) = &*arg.expr { 347 | self.visit_function(&fn_expr.function); 348 | } 349 | self.function_name_stack.pop(); 350 | } 351 | } 352 | _ => {} 353 | } 354 | } 355 | 356 | } 357 | 358 | fn visit_var_declarator(&mut self, var_declarator: &VarDeclarator) { 359 | if let Some(init_expr) = &var_declarator.init { 360 | match &**init_expr { 361 | Expr::New(new_expr) => { 362 | if let Expr::Ident(expr) = &*new_expr.callee { 363 | let class_name = expr.sym.to_string(); 364 | if let Some(var_ident) = &var_declarator.name.as_ident() { 365 | let var_name = var_ident.sym.to_string(); 366 | let instantiation_span = var_ident.span; 367 | self.instantiations.insert(var_name.clone(), InstantiationsMap { 368 | class_name, 369 | variable_name: var_name, 370 | instantiation_span, 371 | }); 372 | } 373 | } 374 | } 375 | _ => {} 376 | } 377 | if let Expr::Bin(bin_expr) = &**init_expr { 378 | self.handle_bin_expr(bin_expr) 379 | } 380 | } 381 | if let Some(ident) = &var_declarator.name.as_ident() { 382 | if let Some(init) = &var_declarator.init { 383 | if let Expr::Await(await_expr) = &**init { 384 | if let Expr::Call(call_expr) = &*await_expr.arg { 385 | self.visit_call_expr(call_expr); 386 | } 387 | } 388 | if let Expr::Bin(bin_expr) = &**init { 389 | if let Expr::Call(call_expr) = &*bin_expr.left { 390 | self.visit_call_expr(call_expr); 391 | } 392 | if let Expr::Call(call_expr) = &*bin_expr.right { 393 | self.visit_call_expr(call_expr); 394 | } 395 | } 396 | if let Expr::OptChain(opt_chain_expr) = &**init { 397 | if let OptChainBase::Member(expr) = &*opt_chain_expr.base { 398 | if let Expr::Call(call_expr) = &*expr.obj { 399 | self.visit_call_expr(call_expr); 400 | } 401 | } 402 | } 403 | if let Expr::Arrow(arrow_expr) = &**init { 404 | self.function_name_stack.push(ident.sym.to_string()); 405 | self.visit_arrow_expr(arrow_expr); 406 | self.function_name_stack.pop(); 407 | } 408 | if let Expr::Fn(fn_expr) = &**init { 409 | self.function_name_stack.push(ident.sym.to_string()); 410 | self.visit_function(&fn_expr.function); 411 | self.function_name_stack.pop(); 412 | } 413 | } 414 | } 415 | 416 | swc_ecma_visit::visit_var_declarator(self, var_declarator); 417 | } 418 | 419 | fn visit_arrow_expr(&mut self, arrow_expr: &ArrowExpr) { 420 | match &*arrow_expr.body { 421 | BlockStmtOrExpr::BlockStmt(block_stmt) => { 422 | for stmt in &block_stmt.stmts { 423 | self.visit_stmt(stmt); 424 | } 425 | } 426 | BlockStmtOrExpr::Expr(expr) => { 427 | if let Expr::Call(call_expr) = &**expr { 428 | self.visit_call_expr(call_expr); 429 | } else { 430 | // use default implementation for other kinds of expressions (for now) 431 | self.visit_expr(expr); 432 | } 433 | } 434 | } 435 | } 436 | 437 | fn visit_function(&mut self, function: &Function) { 438 | if let Some(block_stmt) = &function.body { 439 | for stmt in &block_stmt.stmts { 440 | self.visit_stmt(stmt); 441 | } 442 | } 443 | } 444 | 445 | fn visit_stmt(&mut self, stmt: &Stmt) { 446 | match stmt { 447 | Stmt::Decl(decl) => { 448 | if let Decl::Var(var_decl) = decl { 449 | for decl in &var_decl.decls { 450 | self.visit_var_declarator(decl); 451 | } 452 | } 453 | if let Decl::Fn(fn_decl) = decl { 454 | self.visit_fn_decl(fn_decl); 455 | } 456 | if let Decl::Class(class_decl) = decl { 457 | self.visit_class_decl(class_decl); 458 | } 459 | } 460 | Stmt::Expr(expr_stmt) => { 461 | self.visit_expr(&expr_stmt.expr); 462 | } 463 | Stmt::Block(block_stmt) => { 464 | for stmt in &block_stmt.stmts { 465 | self.visit_stmt(stmt); 466 | } 467 | } 468 | Stmt::If(if_stmt) => { 469 | self.visit_expr(&if_stmt.test); 470 | self.visit_stmt(&if_stmt.cons); 471 | if let Some(alt) = &if_stmt.alt { 472 | self.visit_stmt(alt); 473 | } 474 | } 475 | Stmt::Return(return_stmt) => { 476 | if let Some(expr) = &return_stmt.arg { 477 | self.visit_expr(expr); 478 | } 479 | // Handle returning an object expression 480 | if let Some(block_stmt) = &return_stmt.arg.as_ref().and_then(|arg| match arg.as_ref() { 481 | Expr::Object(object_expr) => Some(Box::new(object_expr.clone())), 482 | _ => None, 483 | }) { 484 | for prop_or_spread in &block_stmt.props { 485 | match prop_or_spread { 486 | swc_ecma_ast::PropOrSpread::Prop(boxed_prop) => { 487 | match &**boxed_prop { 488 | swc_ecma_ast::Prop::KeyValue(key_value_prop) => { 489 | // Handle KeyValue 490 | self.visit_expr(&key_value_prop.value); 491 | } 492 | swc_ecma_ast::Prop::Assign(assign_prop) => { 493 | // Handle Assign 494 | self.visit_expr(&assign_prop.value); 495 | } 496 | swc_ecma_ast::Prop::Getter(getter_prop) => { 497 | // Handle Getter 498 | if let Some(body) = &getter_prop.body { 499 | for stmt in &body.stmts { 500 | self.visit_stmt(stmt); 501 | } 502 | } 503 | } 504 | swc_ecma_ast::Prop::Setter(setter_prop) => { 505 | // Handle Setter 506 | if let Some(body) = &setter_prop.body { 507 | for stmt in &body.stmts { 508 | self.visit_stmt(stmt); 509 | } 510 | } 511 | } 512 | _ => {} 513 | } 514 | } 515 | swc_ecma_ast::PropOrSpread::Spread(spread) => { 516 | // Handle Spread syntax 517 | self.visit_expr(&spread.expr); 518 | } 519 | _ => {} 520 | } 521 | } 522 | } 523 | } 524 | _ => { 525 | // For other kinds of statements, we continue with the default implementation (for now) 526 | swc_ecma_visit::visit_stmt(self, stmt); 527 | } 528 | } 529 | } 530 | } 531 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "doesItThrow.trace.server": "verbose", 3 | "doesItThrow.includeTryStatementThrows": false, 4 | "doesItThrow.ignoreStatements": [ 5 | "@it-throws", 6 | "@does-it-throw-ignore", 7 | "@some-random-ignore" 8 | ] 9 | } -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/callExpr.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const connection = {} 3 | 4 | const SomeThrow = () => { 5 | throw new Error('hi khue') 6 | } 7 | 8 | function SomeThrow2() { 9 | throw new Error('hi khue') 10 | } 11 | 12 | connection.onInitialized(() => { 13 | if (hasConfigurationCapability) { 14 | // Register for all configuration changes. 15 | connection.client.register(DidChangeConfigurationNotification.type, undefined) 16 | } 17 | if (hasWorkspaceFolderCapability) { 18 | connection.workspace.onDidChangeWorkspaceFolders((_event) => { 19 | connection.console.log(`Workspace folder change event received. ${JSON.stringify(_event)}`) 20 | }) 21 | } 22 | SomeThrow() 23 | SomeThrow2() 24 | }) 25 | 26 | connection.onInitialized2(() => { 27 | throw new Error('hi khue') 28 | }) 29 | 30 | SomeRandomCall(() => { 31 | throw new Error('hi khue') 32 | }) 33 | 34 | SomeRandomCall2(() => { 35 | SomeThrow() 36 | SomeThrow2() 37 | }) 38 | 39 | connection.oneWithASecondArg({}, () => { 40 | throw new Error('hi khue') 41 | }) 42 | 43 | const testGetter = { 44 | get test() { 45 | SomeThrow() 46 | } 47 | } 48 | 49 | const array = [SomeThrow(), SomeThrow2()] 50 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/class.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const someCondition = true 3 | export class Something { 4 | constructor() { 5 | throw new Error('hi khue') 6 | } 7 | 8 | someMethodThatThrows() { 9 | throw new Error('hi khue') 10 | } 11 | 12 | someMethodThatDoesNotThrow() { 13 | console.log('hi khue') 14 | } 15 | 16 | someMethodThatThrows2() { 17 | if (someCondition) { 18 | throw new Error('hi khue') 19 | } 20 | } 21 | 22 | nestedThrow() { 23 | if (someCondition) { 24 | return true 25 | } 26 | throw new Error('hi khue') 27 | } 28 | 29 | callNestedThrow() { 30 | if (someCondition) { 31 | return true 32 | } 33 | if (someCondition) { 34 | return true 35 | } 36 | this.nestedThrow() 37 | } 38 | } 39 | 40 | const _somethingCall = () => { 41 | const something = new Something() 42 | something.someMethodThatThrows() 43 | } 44 | 45 | export const somethingCall = () => { 46 | const something = new Something() 47 | something.someMethodThatThrows() 48 | } 49 | 50 | function _somethingCall2() { 51 | const something = new Something() 52 | something.someMethodThatThrows() 53 | } 54 | 55 | export function somethingCall2() { 56 | const something = new Something() 57 | something.someMethodThatThrows() 58 | } 59 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/class.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const someCondition = true 3 | export class Something { 4 | constructor() { 5 | throw new Error('hi khue') 6 | } 7 | 8 | someMethodThatThrows({ someParam }: { someParam: string }) { 9 | throw new Error('hi khue') 10 | } 11 | 12 | someMethodThatDoesNotThrow() { 13 | console.log('hi khue') 14 | } 15 | 16 | someMethodThatThrows2({ someParam }: { someParam: string }) { 17 | if (someParam) { 18 | throw new Error('hi khue') 19 | } 20 | } 21 | 22 | nestedThrow() { 23 | if (someCondition) { 24 | return true 25 | } 26 | throw new Error('hi khue') 27 | } 28 | 29 | callNestedThrow() { 30 | if (someCondition) { 31 | return true 32 | } 33 | if (someCondition) { 34 | return true 35 | } 36 | this.nestedThrow() 37 | } 38 | } 39 | 40 | const _somethingCall = () => { 41 | const something = new Something() 42 | something.someMethodThatThrows() 43 | } 44 | 45 | export const somethingCall = () => { 46 | const something = new Something() 47 | something.someMethodThatThrows() 48 | } 49 | 50 | function _somethingCall2() { 51 | const something = new Something() 52 | something.someMethodThatThrows() 53 | } 54 | 55 | export function somethingCall2() { 56 | const something = new Something() 57 | something.someMethodThatThrows() 58 | } 59 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/exports.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | export function hiKhue() { 4 | throw new Error('hi khue') 5 | } 6 | 7 | export const someConstThatThrows = () => { 8 | throw new Error('hi khue') 9 | } 10 | 11 | const _ConstThatDoesNotThrow = ({}) => { 12 | console.log('hi khue') 13 | someCondition.hiKhue 14 | } 15 | 16 | const _ConstThatThrows = () => { 17 | throw new Error('hi khue') 18 | } 19 | 20 | const callToConstThatThrows = () => { 21 | someConstThatThrows() 22 | } 23 | 24 | export const someConstThatThrows2 = () => { 25 | if (someCondition) { 26 | throw new Error('hi khue') 27 | } 28 | } 29 | 30 | export const callToConstThatThrows2 = () => { 31 | someConstThatThrows2() 32 | } 33 | 34 | export function callToConstThatThrows3() { 35 | someConstThatThrows2() 36 | } 37 | 38 | function callToConstThatThrows4() { 39 | someConstThatThrows2() 40 | } 41 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/exports.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | type SomeRandomType = { 4 | hiKhue: string 5 | } 6 | 7 | export function hiKhue({ hiKhue }: { hiKhue: string }) { 8 | throw new Error('hi khue') 9 | } 10 | 11 | export const someConstThatThrows = () => { 12 | throw new Error('hi khue') 13 | } 14 | 15 | const _ConstThatDoesNotThrow = ({ 16 | someCondition 17 | }: { 18 | someCondition: { 19 | hiKhue: string 20 | } 21 | }) => { 22 | console.log('hi khue') 23 | someCondition.hiKhue 24 | } 25 | 26 | const _ConstThatThrows = () => { 27 | throw new Error('hi khue') 28 | } 29 | 30 | const callToConstThatThrows = () => { 31 | someConstThatThrows() 32 | } 33 | 34 | export const someConstThatThrows2 = () => { 35 | if (someCondition) { 36 | throw new Error('hi khue') 37 | } 38 | } 39 | 40 | export const callToConstThatThrows2 = () => { 41 | someConstThatThrows2() 42 | } 43 | 44 | export function callToConstThatThrows3() { 45 | const hello: SomeRandomType = { 46 | hiKhue: 'hi khue' 47 | } 48 | someConstThatThrows2() 49 | } 50 | 51 | function callToConstThatThrows4() { 52 | someConstThatThrows2() 53 | } 54 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/ignoreStatements.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const someCondition = true 3 | export class Something { 4 | constructor() { 5 | //@it-throws 6 | throw new Error('hi khue') 7 | } 8 | 9 | someMethodThatThrows() { 10 | //@it-throws 11 | throw new Error('hi khue') 12 | } 13 | 14 | someMethodThatDoesNotThrow() { 15 | console.log('hi khue') 16 | } 17 | 18 | someMethodThatThrows2() { 19 | if (someCondition) { 20 | //@it-throws 21 | throw new Error('hi khue') 22 | } 23 | } 24 | 25 | nestedThrow() { 26 | if (someCondition) { 27 | return true 28 | } 29 | //@it-throws 30 | throw new Error('hi khue') 31 | } 32 | 33 | callNestedThrow() { 34 | if (someCondition) { 35 | return true 36 | } 37 | if (someCondition) { 38 | return true 39 | } 40 | this.nestedThrow() 41 | } 42 | } 43 | 44 | const _somethingCall = () => { 45 | const something = new Something() 46 | something.someMethodThatThrows() 47 | } 48 | 49 | export const somethingCall = () => { 50 | const something = new Something() 51 | something.someMethodThatThrows() 52 | } 53 | 54 | 55 | const someRandomThrow = () => { 56 | //@it-throws 57 | throw new Error('some random throw') 58 | } 59 | 60 | const server = http.createServer(async (req, res) => { 61 | switch (req.url) { 62 | case '/api/pong': 63 | console.log('pong!', INSTANCE_ID, PRIVATE_IP) 64 | //@it-throws 65 | throw new Error('') 66 | break 67 | case '/api/ping': 68 | console.log('ping!', INSTANCE_ID, PRIVATE_IP) 69 | const ips = await SomeThrow() 70 | someObjectLiteral.objectLiteralThrow() 71 | const others = ips.filter((ip) => ip !== PRIVATE_IP) 72 | 73 | others.forEach((ip) => { 74 | http.get(`http://[${ip}]:8080/api/pong`) 75 | }) 76 | break 77 | case '/api/throw': 78 | someRandomThrow() 79 | break 80 | } 81 | 82 | res.end() 83 | }) 84 | 85 | const wss = new WebSocketServer({ noServer: true }) 86 | 87 | 88 | function _somethingCall2() { 89 | const something = new Something() 90 | something.someMethodThatThrows() 91 | } 92 | 93 | export function somethingCall2() { 94 | const something = new Something() 95 | something.someMethodThatThrows() 96 | } 97 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/ignoreStatements.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const someCondition = true 3 | export class Something { 4 | constructor() { 5 | // @it-throws 6 | throw new Error('hi khue') 7 | } 8 | 9 | someMethodThatThrows() { 10 | // @it-throws 11 | throw new Error('hi khue') 12 | } 13 | 14 | someMethodThatDoesNotThrow() { 15 | console.log('hi khue') 16 | } 17 | 18 | someMethodThatThrows2() { 19 | if (someCondition) { 20 | // @some-random-ignore 21 | throw new Error('hi khue') 22 | } 23 | } 24 | 25 | nestedThrow() { 26 | if (someCondition) { 27 | return true 28 | } 29 | // @it-throws-ignore 30 | throw new Error('hi khue') 31 | } 32 | 33 | callNestedThrow() { 34 | if (someCondition) { 35 | return true 36 | } 37 | if (someCondition) { 38 | return true 39 | } 40 | this.nestedThrow() 41 | } 42 | } 43 | 44 | const _somethingCall = () => { 45 | const something = new Something() 46 | something.someMethodThatThrows() 47 | } 48 | 49 | export const somethingCall = () => { 50 | const something = new Something() 51 | something.someMethodThatThrows() 52 | } 53 | 54 | 55 | const someRandomThrow = () => { 56 | //@it-throws 57 | throw new Error('some random throw') 58 | } 59 | 60 | const server = http.createServer(async (req, res) => { 61 | switch (req.url) { 62 | case '/api/pong': 63 | console.log('pong!', INSTANCE_ID, PRIVATE_IP) 64 | //@it-throws 65 | throw new Error('') 66 | break 67 | case '/api/ping': 68 | console.log('ping!', INSTANCE_ID, PRIVATE_IP) 69 | const ips = await SomeThrow() 70 | someObjectLiteral.objectLiteralThrow() 71 | const others = ips.filter((ip) => ip !== PRIVATE_IP) 72 | 73 | others.forEach((ip) => { 74 | http.get(`http://[${ip}]:8080/api/pong`) 75 | }) 76 | break 77 | case '/api/throw': 78 | someRandomThrow() 79 | break 80 | } 81 | 82 | res.end() 83 | }) 84 | 85 | const wss = new WebSocketServer({ noServer: true }) 86 | 87 | 88 | function _somethingCall2() { 89 | const something = new Something() 90 | something.someMethodThatThrows() 91 | } 92 | 93 | export function somethingCall2() { 94 | const something = new Something() 95 | something.someMethodThatThrows() 96 | } 97 | 98 | 99 | // @ts-nocheck 100 | const connection = {} 101 | 102 | const SomeThrow = () => { 103 | //@it-throws 104 | throw new Error('hi khue') 105 | } 106 | 107 | function SomeThrow2() { 108 | //@it-throws 109 | throw new Error('hi khue') 110 | } 111 | 112 | connection.onInitialized(() => { 113 | if (hasConfigurationCapability) { 114 | // Register for all configuration changes. 115 | connection.client.register(DidChangeConfigurationNotification.type, undefined) 116 | } 117 | if (hasWorkspaceFolderCapability) { 118 | connection.workspace.onDidChangeWorkspaceFolders((_event) => { 119 | connection.console.log(`Workspace folder change event received. ${JSON.stringify(_event)}`) 120 | }) 121 | } 122 | SomeThrow() 123 | SomeThrow2() 124 | }) 125 | 126 | connection.onInitialized2(() => { 127 | //@it-throws 128 | throw new Error('hi khue') 129 | }) 130 | 131 | SomeRandomCall(() => { 132 | //@it-throws 133 | throw new Error('hi khue') 134 | }) 135 | 136 | SomeRandomCall2(() => { 137 | SomeThrow() 138 | SomeThrow2() 139 | }) 140 | 141 | connection.oneWithASecondArg({}, () => { 142 | //@it-throws 143 | throw new Error('hi khue') 144 | }) 145 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/importIdentifiers.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import { resolve } from 'path' 3 | import { SomeThrow, SomeThrow as SomeThrow2, Something, testing as Test, testing as Test2 } from './something' 4 | import Testing from './something3' 5 | import { SomethingElse } from './somethingElse' 6 | import { SomethingElse as SomethingElse2 } from './somethingElse2' 7 | 8 | export function test() { 9 | try { 10 | SomethingElse() 11 | } catch (e) { 12 | console.log(e) 13 | } 14 | try { 15 | SomethingElse2() 16 | } catch (e) { 17 | console.log(e) 18 | } 19 | try { 20 | Testing() 21 | } catch (e) { 22 | console.log(e) 23 | } 24 | resolve() 25 | try { 26 | SomeThrow() 27 | } catch (e) { 28 | console.log(e) 29 | } 30 | try { 31 | Test() 32 | } catch (e) { 33 | console.log(e) 34 | } 35 | try { 36 | SomeThrow2() 37 | } catch (e) { 38 | console.log(e) 39 | } 40 | try { 41 | Test2() 42 | } catch (e) { 43 | console.log(e) 44 | } 45 | try { 46 | Something() 47 | } catch (e) { 48 | console.log(e) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/jsx.jsx: -------------------------------------------------------------------------------- 1 | export const someThrow = () => { 2 | throw new Error('some error') 3 | } 4 | export function someThrow2() { 5 | throw new Error('some error') 6 | } 7 | 8 | export const someTsx = () => { 9 | if (something) { 10 | throw new Error() 11 | } 12 | return
some tsx
13 | } 14 | 15 | export async function someAsyncTsx() { 16 | if (something) { 17 | throw new Error() 18 | } 19 | return
some tsx
20 | } 21 | 22 | export async function callToThrow() { 23 | someThrow() 24 | someThrow2() 25 | return
some tsx
26 | } 27 | 28 | export const someTsxWithJsx = async () => { 29 | someThrow() 30 | someThrow2() 31 | return
some tsx
32 | } 33 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/objectLiteral.js: -------------------------------------------------------------------------------- 1 | export const someObjectLiteral = { 2 | objectLiteralThrow() { 3 | throw new Error('hi khue') 4 | }, 5 | nestedObjectLiteral: { 6 | nestedObjectLiteralThrow: () => { 7 | throw new Error('hi khue') 8 | } 9 | } 10 | } 11 | 12 | export const SomeObject = { 13 | someExampleThrow: () => { 14 | throw new Error('hi khue') 15 | } 16 | } 17 | 18 | export function callToLiteral() { 19 | someObjectLiteral.objectLiteralThrow() 20 | } 21 | 22 | export const callToLiteral2 = () => { 23 | someObjectLiteral.objectLiteralThrow() 24 | } 25 | 26 | export const callToLiteral3 = () => { 27 | someObjectLiteral.nestedObjectLiteral.nestedObjectLiteralThrow() 28 | SomeObject.someExampleThrow() 29 | } 30 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/objectLiteral.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export const someObjectLiteral = { 3 | objectLiteralThrow({ someArg }: { someArg: string }) { 4 | throw new Error('hi khue') 5 | }, 6 | nestedObjectLiteral: { 7 | nestedObjectLiteralThrow: () => { 8 | throw new Error('hi khue') 9 | } 10 | } 11 | } 12 | 13 | export const SomeObject = { 14 | someExampleThrow: () => { 15 | throw new Error('hi khue') 16 | } 17 | } 18 | 19 | export function callToLiteral() { 20 | someObjectLiteral.objectLiteralThrow({ someArg: 'hi' }) 21 | } 22 | 23 | export const callToLiteral2 = () => { 24 | someObjectLiteral.objectLiteralThrow({ someArg: 'hi' }) 25 | } 26 | 27 | export const callToLiteral3 = () => { 28 | someObjectLiteral.nestedObjectLiteral.nestedObjectLiteralThrow() 29 | SomeObject.someExampleThrow() 30 | } 31 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/returnStatement.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const someThrow = () => { 3 | if (something) { 4 | while (true) { 5 | throw new Error("oh no"); 6 | } 7 | } else { 8 | for (let i = 0; i < 10; i++) { 9 | throw new Error("oh no"); 10 | } 11 | } 12 | } 13 | class Test { 14 | badMethod() { 15 | throw new Error("oh no"); 16 | } 17 | } 18 | 19 | const callToSomeThrow = () => { 20 | const testMethod = new Test(); 21 | return { 22 | test: someThrow(), 23 | testing: () => someThrow(), 24 | array: [someThrow(), someThrow()], 25 | object: { test: someThrow() }, 26 | class: testMethod.badMethod(), 27 | } 28 | } 29 | 30 | function test() { 31 | return someThrow(); 32 | } -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/sample.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | export const someConstThatThrows = () => { 4 | throw new Error('hi khue') 5 | } 6 | 7 | function callToConstThatThrows4() { 8 | someConstThatThrows() 9 | } 10 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/something.ts: -------------------------------------------------------------------------------- 1 | export const SomeThrow = () => { 2 | throw new Error('never gonna let you down'); 3 | } 4 | 5 | export function Something () { 6 | throw new Error('never gonna run around and desert you') 7 | } -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/spreadExpr.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | // should work for private class 3 | class SomeClass { 4 | constructor(public x: number) {} 5 | 6 | async _contextFromWorkflow() { 7 | throw new Error('Some error') 8 | } 9 | 10 | async someCallToThrow() { 11 | const { user, stravaUser, streakContext } = opts?.contextFromWorkFlow ?? (await this._contextFromWorkflow(job)) 12 | } 13 | } 14 | 15 | // should work for exported class 16 | // biome-ignore lint/suspicious/noRedeclare: 17 | export class SomeClass { 18 | constructor(public x: number) {} 19 | 20 | async _contextFromWorkflow() { 21 | throw new Error('Some error') 22 | } 23 | 24 | async someCallToThrow() { 25 | const { user, stravaUser, streakContext } = opts?.contextFromWorkFlow ?? (await this._contextFromWorkflow(job)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/switchStatement.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | import { SomeThrow, someObjectLiteral } from './something' 3 | 4 | const someRandomThrow = () => { 5 | throw new Error('some random throw') 6 | } 7 | 8 | const server = http.createServer(async (req, res) => { 9 | switch (req.url) { 10 | case '/api/pong': 11 | console.log('pong!', INSTANCE_ID, PRIVATE_IP) 12 | throw new Error('') 13 | break 14 | case '/api/ping': 15 | console.log('ping!', INSTANCE_ID, PRIVATE_IP) 16 | const ips = await SomeThrow() 17 | someObjectLiteral.objectLiteralThrow() 18 | const others = ips.filter((ip) => ip !== PRIVATE_IP) 19 | 20 | others.forEach((ip) => { 21 | http.get(`http://[${ip}]:8080/api/pong`) 22 | }) 23 | break 24 | case '/api/throw': 25 | someRandomThrow() 26 | break 27 | } 28 | 29 | res.end() 30 | }) 31 | 32 | const wss = new WebSocketServer({ noServer: true }) 33 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/tryStatement.ts: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export const someConstThatThrows = () => { 3 | try { 4 | throw new Error('never gonna give you up') 5 | } catch (e) { 6 | console.log(e) 7 | } 8 | } 9 | 10 | function callToConstThatThrows4() { 11 | someConstThatThrows() 12 | } 13 | 14 | const someCondition = true 15 | export class Something { 16 | constructor() { 17 | try { 18 | throw new Error('hi khue') 19 | } catch (e) { 20 | console.log(e) 21 | } 22 | } 23 | 24 | someMethodThatThrows() { 25 | try { 26 | throw new Error('hi khue') 27 | } catch (e) { 28 | console.log(e) 29 | } 30 | } 31 | 32 | someMethodThatDoesNotThrow() { 33 | console.log('hi khue') 34 | } 35 | 36 | someMethodThatThrows2() { 37 | if (someCondition) { 38 | throw new Error('hi khue') 39 | } 40 | } 41 | 42 | nestedThrow() { 43 | try { 44 | if (someCondition) { 45 | return true 46 | } 47 | throw new Error('hi khue') 48 | } catch (e) { 49 | console.log(e) 50 | } 51 | } 52 | 53 | callNestedThrow() { 54 | if (someCondition) { 55 | return true 56 | } 57 | if (someCondition) { 58 | return true 59 | } 60 | this.nestedThrow() 61 | } 62 | } 63 | 64 | const _somethingCall = () => { 65 | const something = new Something() 66 | something.someMethodThatThrows() 67 | } 68 | 69 | export const somethingCall = () => { 70 | const something = new Something() 71 | something.someMethodThatThrows() 72 | } 73 | 74 | function _somethingCall2() { 75 | const something = new Something() 76 | something.someMethodThatThrows() 77 | } 78 | 79 | export function somethingCall2() { 80 | const something = new Something() 81 | something.someMethodThatThrows() 82 | } 83 | 84 | // @ts-nocheck 85 | // should work for private class 86 | class SomeClass { 87 | constructor(public x: number) {} 88 | 89 | async _contextFromWorkflow() { 90 | try { 91 | throw new Error('Some error') 92 | } catch (e) { 93 | console.log(e) 94 | } 95 | } 96 | 97 | async someCallToThrow() { 98 | const { user, stravaUser, streakContext } = opts?.contextFromWorkFlow ?? (await this._contextFromWorkflow(job)) 99 | } 100 | } 101 | 102 | // should work for exported class 103 | // biome-ignore lint/suspicious/noRedeclare: 104 | export class SomeClass2 { 105 | constructor(public x: number) {} 106 | 107 | async _contextFromWorkflow() { 108 | try { 109 | throw new Error('Some error') 110 | } catch (e) { 111 | console.log(e) 112 | } 113 | } 114 | 115 | async someCallToThrow() { 116 | const { user, stravaUser, streakContext } = opts?.contextFromWorkFlow ?? (await this._contextFromWorkflow(job)) 117 | } 118 | } 119 | 120 | const server = http.createServer(async (req, res) => { 121 | switch (req.url) { 122 | case '/api/pong': 123 | console.log('pong!', INSTANCE_ID, PRIVATE_IP) 124 | try { 125 | throw new Error('') 126 | } catch (e) { 127 | console.log(e) 128 | } 129 | break 130 | case '/api/ping': 131 | console.log('ping!', INSTANCE_ID, PRIVATE_IP) 132 | const ips = await SomeThrow() 133 | someObjectLiteral.objectLiteralThrow() 134 | const others = ips.filter((ip) => ip !== PRIVATE_IP) 135 | 136 | others.forEach((ip) => { 137 | http.get(`http://[${ip}]:8080/api/pong`) 138 | }) 139 | break 140 | case '/api/throw': 141 | someRandomThrow() 142 | break 143 | } 144 | 145 | res.end() 146 | }) 147 | 148 | const wss = new WebSocketServer({ noServer: true }) 149 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/tryStatementNested.ts: -------------------------------------------------------------------------------- 1 | //@ts-nocheck 2 | async function throwInsideCatch() { 3 | try { 4 | throw new Error('hi') 5 | } catch (e) { 6 | throw e 7 | } 8 | } 9 | 10 | async function parentCatchThatisNotCaught() { 11 | try { 12 | try { 13 | something() 14 | } 15 | catch (e) { 16 | throw new Error() 17 | } 18 | } catch (e) { 19 | throw new Error() 20 | } 21 | } 22 | 23 | async function noThrowInsideCatch() { 24 | try { 25 | throw new Error('hi') 26 | } catch (e) { 27 | console.log(e) 28 | } 29 | } 30 | 31 | async function parentCatchWithoutThrow() { 32 | try { 33 | throw new Error('hi') 34 | } catch (e) { 35 | try { 36 | throw new Error('hi') 37 | } catch (e) { 38 | console.log(e) 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /crates/does-it-throw/src/fixtures/tsx.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | export const someThrow = () => { 3 | throw new Error('some error') 4 | } 5 | export function someThrow2() { 6 | throw new Error('some error') 7 | } 8 | 9 | export const someTsx = () => { 10 | if (something) { 11 | throw new Error() 12 | } 13 | return
some tsx
14 | } 15 | 16 | export async function someAsyncTsx({ something }: { something: boolean }) { 17 | if (something) { 18 | throw new Error() 19 | } 20 | return
some tsx
21 | } 22 | 23 | export async function callToThrow() { 24 | someThrow() 25 | someThrow2() 26 | return
some tsx
27 | } 28 | 29 | export const someTsxWithJsx = async () => { 30 | someThrow() 31 | someThrow2() 32 | return
some tsx
33 | } 34 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/import_usage_finder.rs: -------------------------------------------------------------------------------- 1 | extern crate swc_common; 2 | extern crate swc_ecma_ast; 3 | extern crate swc_ecma_parser; 4 | extern crate swc_ecma_visit; 5 | 6 | use std::collections::HashSet; 7 | 8 | use swc_ecma_ast::Callee; 9 | 10 | use crate::throw_finder::IdentifierUsage; 11 | 12 | use self::swc_ecma_ast::{CallExpr, Expr, MemberProp}; 13 | 14 | use self::swc_ecma_visit::Visit; 15 | 16 | pub struct ImportUsageFinder { 17 | pub imported_identifiers: Vec, 18 | pub imported_identifier_usages: HashSet, 19 | pub current_class_name: Option, 20 | pub current_method_name: Option, 21 | pub function_name_stack: Vec, 22 | } 23 | 24 | impl Visit for ImportUsageFinder { 25 | fn visit_call_expr(&mut self, call: &CallExpr) { 26 | if let Callee::Expr(expr) = &call.callee { 27 | match &**expr { 28 | Expr::Member(member_expr) => { 29 | if let Expr::Ident(object_ident) = &*member_expr.obj { 30 | self.current_class_name = Some(object_ident.sym.to_string()); 31 | } 32 | 33 | if let MemberProp::Ident(method_ident) = &member_expr.prop { 34 | self.current_method_name = Some(method_ident.sym.to_string()); 35 | } 36 | 37 | if let (Some(current_class_name), Some(current_method_name)) = ( 38 | self.current_class_name.clone(), 39 | self.current_method_name.clone(), 40 | ) { 41 | if self.imported_identifiers.contains(¤t_class_name) { 42 | let usage_context = current_method_name.clone(); 43 | let id = format!( 44 | "{}-{}", 45 | self 46 | .current_class_name 47 | .clone() 48 | .unwrap_or_else(|| "NOT_SET".to_string()), 49 | usage_context 50 | ); 51 | // Create and store the identifier usage information 52 | let usage_map = IdentifierUsage::new( 53 | call.span, 54 | current_class_name.clone(), 55 | usage_context.clone(), 56 | id.clone(), 57 | ); 58 | self.imported_identifier_usages.insert(usage_map); 59 | } 60 | } 61 | for arg in &call.args { 62 | self.function_name_stack.push( 63 | self 64 | .current_method_name 65 | .clone() 66 | .unwrap_or_else(|| "".to_string()), 67 | ); 68 | if let Expr::Arrow(arrow_expr) = &*arg.expr { 69 | self.visit_arrow_expr(arrow_expr) 70 | } 71 | if let Expr::Fn(fn_expr) = &*arg.expr { 72 | self.visit_function(&fn_expr.function) 73 | } 74 | self.function_name_stack.pop(); 75 | } 76 | self.current_class_name = None; 77 | self.current_method_name = None; 78 | } 79 | 80 | Expr::Ident(ident) => { 81 | let called_function_name = ident.sym.to_string(); 82 | if self.imported_identifiers.contains(&called_function_name) { 83 | let usage_context = self 84 | .function_name_stack 85 | .last() 86 | .cloned() 87 | .unwrap_or_else(|| "".to_string()); 88 | let id = format!( 89 | "{}-{}", 90 | self 91 | .current_class_name 92 | .clone() 93 | .unwrap_or_else(|| "NOT_SET".to_string()), 94 | called_function_name 95 | ); 96 | let usage_map = IdentifierUsage::new( 97 | call.span, 98 | called_function_name.clone(), 99 | usage_context.clone(), 100 | id.clone(), 101 | ); 102 | self.imported_identifier_usages.insert(usage_map); 103 | } 104 | for arg in &call.args { 105 | self.function_name_stack.push(called_function_name.clone()); 106 | if let Expr::Arrow(arrow_expr) = &*arg.expr { 107 | self.visit_arrow_expr(arrow_expr); 108 | } 109 | if let Expr::Fn(fn_expr) = &*arg.expr { 110 | self.visit_function(&fn_expr.function); 111 | } 112 | self.function_name_stack.pop(); 113 | } 114 | self.current_class_name = None; 115 | self.current_method_name = None; 116 | } 117 | 118 | Expr::Arrow(_arrow_expr) => {} 119 | _ => {} 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crates/does-it-throw/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod call_finder; 2 | pub mod import_usage_finder; 3 | pub mod throw_finder; 4 | use call_finder::{CallFinder, CallToThrowMap}; 5 | use import_usage_finder::ImportUsageFinder; 6 | use swc_common::comments::SingleThreadedComments; 7 | use throw_finder::{IdentifierUsage, ThrowAnalyzer, ThrowMap, ThrowFinderSettings}; 8 | extern crate swc_common; 9 | extern crate swc_ecma_ast; 10 | extern crate swc_ecma_parser; 11 | extern crate swc_ecma_visit; 12 | 13 | use std::collections::{HashMap, HashSet}; 14 | 15 | use std::vec; 16 | 17 | use self::swc_common::{sync::Lrc, SourceMap}; 18 | use self::swc_ecma_ast::EsVersion; 19 | use self::swc_ecma_parser::{lexer::Lexer, Parser, StringInput, Syntax}; 20 | use self::swc_ecma_visit::Visit; 21 | 22 | #[derive(Default)] 23 | pub struct AnalysisResult { 24 | pub functions_with_throws: HashSet, 25 | pub calls_to_throws: HashSet, 26 | pub json_parse_calls: Vec, 27 | pub fs_access_calls: Vec, 28 | pub import_sources: HashSet, 29 | pub imported_identifiers: Vec, 30 | pub imported_identifier_usages: HashSet, 31 | } 32 | 33 | struct CombinedAnalyzers<'throwfinder_settings> { 34 | throw_analyzer: ThrowAnalyzer<'throwfinder_settings>, 35 | call_finder: CallFinder, 36 | import_usage_finder: ImportUsageFinder, 37 | } 38 | 39 | impl <'throwfinder_settings> From> for AnalysisResult { 40 | fn from(analyzers: CombinedAnalyzers) -> Self { 41 | Self { 42 | functions_with_throws: analyzers.throw_analyzer.functions_with_throws, 43 | calls_to_throws: analyzers.call_finder.calls, 44 | json_parse_calls: analyzers.throw_analyzer.json_parse_calls, 45 | fs_access_calls: analyzers.throw_analyzer.fs_access_calls, 46 | import_sources: analyzers.throw_analyzer.import_sources, 47 | imported_identifiers: analyzers.throw_analyzer.imported_identifiers, 48 | imported_identifier_usages: analyzers.import_usage_finder.imported_identifier_usages, 49 | } 50 | } 51 | } 52 | 53 | pub struct UserSettings { 54 | pub include_try_statement_throws: bool, 55 | pub ignore_statements: Vec, 56 | } 57 | 58 | pub fn analyze_code( 59 | content: &str, 60 | cm: Lrc, 61 | user_settings: &UserSettings, 62 | ) -> (AnalysisResult, Lrc) { 63 | let fm = cm.new_source_file(swc_common::FileName::Anon, content.into()); 64 | let comments = Lrc::new(SingleThreadedComments::default()); 65 | let lexer = Lexer::new( 66 | Syntax::Typescript(swc_ecma_parser::TsConfig { 67 | tsx: true, 68 | decorators: true, 69 | dts: false, 70 | no_early_errors: false, 71 | disallow_ambiguous_jsx_like: false, 72 | }), 73 | EsVersion::latest(), 74 | StringInput::from(&*fm), 75 | Some(&comments), 76 | ); 77 | 78 | let mut parser = Parser::new_from(lexer); 79 | let module = parser.parse_module().expect("Failed to parse module"); 80 | let mut throw_collector = ThrowAnalyzer { 81 | comments: comments.clone(), 82 | functions_with_throws: HashSet::new(), 83 | json_parse_calls: vec![], 84 | fs_access_calls: vec![], 85 | import_sources: HashSet::new(), 86 | imported_identifiers: Vec::new(), 87 | function_name_stack: vec![], 88 | current_class_name: None, 89 | current_method_name: None, 90 | throwfinder_settings: ThrowFinderSettings { 91 | ignore_statements: &user_settings.ignore_statements.clone(), 92 | include_try_statements: &user_settings.include_try_statement_throws.clone(), 93 | } 94 | }; 95 | throw_collector.visit_module(&module); 96 | let mut call_collector = CallFinder { 97 | processed_calls: HashSet::new(), 98 | functions_with_throws: throw_collector.functions_with_throws.clone(), 99 | calls: HashSet::new(), 100 | current_class_name: None, 101 | instantiations: HashMap::new(), 102 | function_name_stack: vec![], 103 | object_property_stack: vec![], 104 | }; 105 | call_collector.visit_module(&module); 106 | 107 | let mut import_usages_collector = ImportUsageFinder { 108 | imported_identifiers: throw_collector.imported_identifiers.clone(), 109 | imported_identifier_usages: HashSet::new(), 110 | current_class_name: None, 111 | current_method_name: None, 112 | function_name_stack: vec![], 113 | }; 114 | import_usages_collector.visit_module(&module); 115 | 116 | let combined_analyzers = CombinedAnalyzers { 117 | throw_analyzer: throw_collector, 118 | call_finder: call_collector, 119 | import_usage_finder: import_usages_collector, 120 | }; 121 | 122 | (combined_analyzers.into(), cm) 123 | } 124 | -------------------------------------------------------------------------------- /docs/neovim.md: -------------------------------------------------------------------------------- 1 | > *There are probably ways to improve the Neovim experience. If you have suggestions, please open an issue!* 2 | 3 | # Install the LSP Server 4 | 5 | First, install (globally) the `does-it-throw-lsp` package from NPM: 6 | ```bash 7 | npm i -g does-it-throw-lsp 8 | ``` 9 | 10 | > You can use your favorite package manager, eg `bun install` 11 | 12 | This package contains the same LSP Server VSCode runs under the hood. The server itself uses Node.js, but the core of the code is written in Rust and compiled to WASM. The server is published to NPM, and the Rust code is published to [crates.io](https://crates.io/crates/does-it-throw). 13 | 14 | > If anyone has ideas on how to run the server via bun with some shim, let me know! The current `bin` script is located [here](https://github.com/michaelangeloio/does-it-throw/blob/main/server/bin/does-it-throw). 15 | 16 | # Lua Setup 17 | 18 | (optional) install Lazy.nvim: 19 | ```lua 20 | -- init.lua 21 | local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim' 22 | local uv = vim.uv or vim.loop 23 | 24 | -- Auto-install lazy.nvim if not present 25 | if not uv.fs_stat(lazypath) then 26 | print('Installing lazy.nvim....') 27 | vim.fn.system( 28 | {'git', 'clone', '--filter=blob:none', 'https://github.com/folke/lazy.nvim.git', '--branch=stable', -- latest stable release 29 | lazypath}) 30 | print('Done.') 31 | end 32 | 33 | vim.opt.rtp:prepend(lazypath) 34 | ``` 35 | ## Starting the Server 36 | 37 | You can start the server manually by running the following command: 38 | ```bash 39 | does-it-throw-lsp --stdio 40 | ``` 41 | 42 | ### Lua Setup (cont'd) 43 | 44 | Tell Neovim to use the LSP server: 45 | ```lua 46 | require('lazy').setup({{'neovim/nvim-lspconfig'}}) 47 | 48 | local lsp_configurations = require('lspconfig.configs') 49 | 50 | local server_config = { 51 | ["doesItThrow"] = { 52 | throwStatementSeverity = "Hint", 53 | functionThrowSeverity = "Hint", 54 | callToThrowSeverity = "Hint", 55 | callToImportedThrowSeverity = "Hint", 56 | includeTryStatementThrows = false, 57 | maxNumberOfProblems = 10000 58 | } 59 | } 60 | 61 | -- Setup doesItThrow 62 | if not lsp_configurations.does_it_throw_server then 63 | lsp_configurations.does_it_throw_server = { 64 | default_config = { 65 | cmd = {"does-it-throw-lsp", "--stdio"}, 66 | filetypes = {"typescript", "javascript"}, 67 | root_dir = function(fname) 68 | return vim.fn.getcwd() 69 | end 70 | } 71 | } 72 | end 73 | 74 | require'lspconfig'.does_it_throw_server.setup { 75 | on_init = function(client) 76 | client.config.settings = server_config 77 | end, 78 | -- important to set this up so that the server can find your desired settings 79 | handlers = { 80 | ["workspace/configuration"] = function(_, params, _, _) 81 | local configurations = {} 82 | for _, item in ipairs(params.items) do 83 | if item.section then 84 | table.insert(configurations, server_config[item.section]) 85 | end 86 | end 87 | return configurations 88 | end 89 | } 90 | } 91 | ``` 92 | ## Customizations 93 | Notice the above lua config: 94 | ```lua 95 | ["doesItThrow"] = { 96 | throwStatementSeverity = "Hint", 97 | functionThrowSeverity = "Hint", 98 | callToThrowSeverity = "Hint", 99 | callToImportedThrowSeverity = "Hint", 100 | maxNumberOfProblems = 10000 101 | } 102 | ``` 103 | The settings correspond to the same VSCode settings. These settings and descriptions can be found under [package.json](https://github.com/michaelangeloio/does-it-throw/blob/main/package.json). 104 | 105 | 106 | 107 | ### (optional) Customize your diagnostics: 108 | ```lua 109 | vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(vim.lsp.diagnostic.on_publish_diagnostics, { 110 | -- Enable underline, use default values 111 | underline = true, 112 | -- Enable virtual text, override spacing to 4 113 | virtual_text = { 114 | spacing = 4, 115 | prefix = '●' -- This can be any character you prefer 116 | }, 117 | -- Use a function to define signs 118 | signs = true, 119 | -- Disable a feature 120 | update_in_insert = false 121 | }) 122 | local signs = { 123 | Error = " ", 124 | Warn = " ", 125 | Hint = " ", 126 | Info = " " 127 | } 128 | for type, icon in pairs(signs) do 129 | local hl = "LspDiagnosticsSign" .. type 130 | vim.fn.sign_define(hl, { 131 | text = icon, 132 | texthl = hl, 133 | numhl = hl 134 | }) 135 | end 136 | ``` 137 | 138 | If you need to debug the server for whatever reason, you can set the log level to `debug`: 139 | ```lua 140 | -- Enable server log diagnostics if you want 141 | vim.lsp.set_log_level("debug") 142 | ``` -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | ## Configuration Options 2 | 3 | Here's a table of all the configuration options: 4 | 5 | | Option | Description | Default | 6 | | ------ | ----------- | ------- | 7 | | `throwStatementSeverity` | The severity of the throw statement diagnostics. | `Hint` | 8 | | `functionThrowSeverity` | The severity of the function throw diagnostics. | `Hint` | 9 | | `callToThrowSeverity` | The severity of the call to throw diagnostics. | `Hint` | 10 | | `callToImportedThrowSeverity` | The severity of the call to imported throw diagnostics. | `Hint` | 11 | | `includeTryStatementThrows` | Whether to include throw statements inside try statements. | `false` | 12 | | `maxNumberOfProblems` | The maximum number of problems to report. | `10000` | 13 | | `ignoreStatements` | A list/array of statements to ignore. | `["@it-throws", "@does-it-throw-ignore"]` | 14 | 15 | ## Ignoring Throw Statement Warnings 16 | 17 | You can ignore throw statement warnings by adding the following comment to the line above the throw statement: 18 | 19 | ```typescript 20 | const someThrow = () => { 21 | // @does-it-throw-ignore 22 | throw new Error("This will not be reported"); 23 | }; 24 | ``` 25 | 26 | Any calls to functions/methods that `throw` that are marked with the `@it-throws` or `@does-it-throw-ignore` comment will also be ignored as a result. For example: 27 | 28 | ```typescript 29 | const someThrow = () => { 30 | // @does-it-throw-ignore 31 | throw new Error("This will not be reported"); 32 | }; 33 | 34 | const callToThrow = () => { 35 | someThrow(); // This will not be reported 36 | }; 37 | ``` -------------------------------------------------------------------------------- /jetbrains/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | .qodana 4 | build 5 | signed -------------------------------------------------------------------------------- /jetbrains/.run/Run IDE for UI Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /jetbrains/.run/Run Plugin.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 25 | -------------------------------------------------------------------------------- /jetbrains/.run/Run Tests.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 18 | 20 | true 21 | true 22 | false 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /jetbrains/.run/Run Verifications.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 17 | 19 | true 20 | true 21 | false 22 | false 23 | 24 | 26 | 27 | -------------------------------------------------------------------------------- /jetbrains/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | ## [0.5.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-jetbrains-v0.4.0...does-it-throw-jetbrains-v0.5.0) (2024-01-25) 6 | 7 | 8 | ### Features 9 | 10 | * **deps-dev:** bump @biomejs/biome from 1.4.1 to 1.5.3 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 11 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.5 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 12 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.6 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 13 | * **deps-dev:** bump bun-types from 1.0.20 to 1.0.25 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 14 | * **deps-dev:** bump esbuild from 0.19.11 to 0.19.12 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 15 | * **deps:** bump serde from 1.0.193 to 1.0.195 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 16 | * **deps:** bump serde_json from 1.0.109 to 1.0.111 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 17 | * **deps:** bump swc_ecma_ast from 0.110.15 to 0.110.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 18 | * **deps:** bump swc_ecma_ast from 0.110.17 to 0.111.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 19 | * **deps:** bump swc_ecma_parser from 0.141.34 to 0.142.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 20 | * **deps:** bump swc_ecma_visit from 0.96 to 0.97.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 21 | * **deps:** bump swc_ecma_visit from 0.96.15 to 0.96.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 22 | * **deps:** bump vscode-languageserver-textdocument from 1.0.8 to 1.0.11 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 23 | * **deps:** bump wasm-bindgen from 0.2.89 to 0.2.90 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 24 | * **jetbrains:** jetbrains implementation ([#119](https://github.com/michaelangeloio/does-it-throw/issues/119)) ([e4d4153](https://github.com/michaelangeloio/does-it-throw/commit/e4d415336da8eb78ef650f2941185a3fa4dc5dd6)) 25 | * LSP now supports workspace folders ([#127](https://github.com/michaelangeloio/does-it-throw/issues/127)) ([960b486](https://github.com/michaelangeloio/does-it-throw/commit/960b486e8cfe4fd5165be4dd200457c7e5b90979)) 26 | 27 | 28 | ### Bug Fixes 29 | 30 | * enhance call to throw logic and handle return statements ([#140](https://github.com/michaelangeloio/does-it-throw/issues/140)) ([a1bfaf1](https://github.com/michaelangeloio/does-it-throw/commit/a1bfaf16c768aeb49ecaecb991ca6a2b57e71072)) 31 | 32 | ## [0.4.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-jetbrains-v0.3.3...does-it-throw-jetbrains-v0.4.0) (2024-01-06) 33 | 34 | 35 | ### Features 36 | 37 | * initial jetbrains/intellij support ([#104](https://github.com/michaelangeloio/does-it-throw/issues/104)) ([455d763](https://github.com/michaelangeloio/does-it-throw/commit/455d7635128646c57bbbc5811b75a526cb8adc64)) 38 | 39 | 40 | ### Bug Fixes 41 | 42 | * adjust readme to adhere to jetbrains marketplace guidelines ([#115](https://github.com/michaelangeloio/does-it-throw/issues/115)) ([6d68139](https://github.com/michaelangeloio/does-it-throw/commit/6d68139151f43f06033fd4517baee5c3d53e287c)) 43 | * jetbrains build.gradle readme parsing logic ([#116](https://github.com/michaelangeloio/does-it-throw/issues/116)) ([a3cb052](https://github.com/michaelangeloio/does-it-throw/commit/a3cb052b5ac1db2dd8bdbda23eabb37a48de1bfa)) 44 | * release please setup for jetbrains ([#107](https://github.com/michaelangeloio/does-it-throw/issues/107)) ([df6b9bb](https://github.com/michaelangeloio/does-it-throw/commit/df6b9bba97d79c1bf0cdda6d306403cd2cd8707e)) 45 | 46 | ## 0.3.3(https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.2...does-it-throw-lsp-v0.3.3) (2023-12-24) 47 | 48 | ### Bug Fixes 49 | 50 | - catch with throw statement not included ([#95](https://github.com/michaelangeloio/does-it-throw/issues/95)) ([fd223db](https://github.com/michaelangeloio/does-it-throw/commit/fd223db4f56e87439999b9b33a393769bd2b7c5b)) 51 | - **deps-dev:** remove remaining eslint dev dependencies ([#97](https://github.com/michaelangeloio/does-it-throw/issues/97)) ([5f173a6](https://github.com/michaelangeloio/does-it-throw/commit/5f173a69cb86570a526a665d453b86ae776538d0)) 52 | 53 | ## 0.3.2(https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.1...does-it-throw-lsp-v0.3.2) (2023-12-17) 54 | 55 | ### Bug Fixes 56 | 57 | - update server package.json keywords ([#87](https://github.com/michaelangeloio/does-it-throw/issues/87)) ([c19717d](https://github.com/michaelangeloio/does-it-throw/commit/c19717d96a09152d959bfd7d5c3a34ac62f5e26d)) 58 | 59 | ## 0.3.1(https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.0...does-it-throw-lsp-v0.3.1) (2023-12-17) 60 | 61 | ### Bug Fixes 62 | 63 | - functions and throw statements are underlined even if caught ([#81](https://github.com/michaelangeloio/does-it-throw/issues/81)) ([16adf85](https://github.com/michaelangeloio/does-it-throw/commit/16adf85b05b92542fa6c09ac1611dd56c7603c99)) 64 | 65 | ## 0.3.0(https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.2.5...does-it-throw-lsp-v0.3.0) (2023-12-16) 66 | 67 | ### Features 68 | 69 | - neovim support ([#78](https://github.com/michaelangeloio/does-it-throw/issues/78)) ([6152786](https://github.com/michaelangeloio/does-it-throw/commit/61527869e70f54e99616375f7efd53b24e0fa01a)) 70 | 71 | ### Bug Fixes 72 | 73 | - add biome for standardization, ensure the builder reports errors correctly ([#72](https://github.com/michaelangeloio/does-it-throw/issues/72)) ([0d18392](https://github.com/michaelangeloio/does-it-throw/commit/0d18392268516abb79d015f90495dd331e7ef998)) 74 | - re-organize primary crate into modules ([#42](https://github.com/michaelangeloio/does-it-throw/issues/42)) ([badb106](https://github.com/michaelangeloio/does-it-throw/commit/badb1061d0dfc679458d55609e43cccfdca01794)) 75 | - results should still show even if file cannot resolve (calls to throws) ([#76](https://github.com/michaelangeloio/does-it-throw/issues/76)) ([f908556](https://github.com/michaelangeloio/does-it-throw/commit/f908556dfda8eca9195c87269fac71bc6d3e8bf9)) 76 | - update details, fix logic in some call expressions, including spread operators ([#40](https://github.com/michaelangeloio/does-it-throw/issues/40)) ([cdfdf47](https://github.com/michaelangeloio/does-it-throw/commit/cdfdf47a2d657364abc1b3b3ce97e89405b842b3)) 77 | -------------------------------------------------------------------------------- /jetbrains/build.gradle.kts: -------------------------------------------------------------------------------- 1 | import org.jetbrains.changelog.Changelog 2 | import org.jetbrains.changelog.markdownToHTML 3 | 4 | fun properties(key: String) = providers.gradleProperty(key) 5 | fun environment(key: String) = providers.environmentVariable(key) 6 | 7 | plugins { 8 | id("java") // Java support 9 | alias(libs.plugins.kotlin) // Kotlin support 10 | alias(libs.plugins.changelog) // Gradle Changelog Plugin 11 | alias(libs.plugins.qodana) // Gradle Qodana Plugin 12 | alias(libs.plugins.kover) // Gradle Kover Plugin 13 | id("org.jetbrains.intellij") version "1.16.1" 14 | } 15 | 16 | group = properties("pluginGroup").get() 17 | version = properties("pluginVersion").get() 18 | 19 | // Configure project's dependencies 20 | repositories { 21 | mavenCentral() 22 | } 23 | 24 | tasks.test { 25 | useJUnitPlatform() 26 | } 27 | 28 | // Dependencies are managed with Gradle version catalog - read more: https://docs.gradle.org/current/userguide/platforms.html#sub:version-catalog 29 | dependencies { 30 | // implementation(libs.annotations) 31 | testImplementation("org.junit.jupiter:junit-jupiter:5.8.1") 32 | 33 | // Mockito for mocking in tests 34 | testImplementation("org.mockito:mockito-core:4.5.1") // adjust the version as needed 35 | testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1") 36 | 37 | } 38 | // Set the JVM language level used to build the project. Use Java 11 for 2020.3+, and Java 17 for 2022.2+. 39 | kotlin { 40 | @Suppress("UnstableApiUsage") 41 | jvmToolchain { 42 | languageVersion = JavaLanguageVersion.of(17) 43 | vendor = JvmVendorSpec.JETBRAINS 44 | } 45 | } 46 | 47 | intellij { 48 | pluginName = properties("pluginName") 49 | version = properties("platformVersion") 50 | type = properties("platformType") 51 | 52 | // Plugin Dependencies. Uses `platformPlugins` property from the gradle.properties file. 53 | plugins = properties("platformPlugins").map { it.split(',').map(String::trim).filter(String::isNotEmpty) } 54 | } 55 | 56 | // Configure Gradle Changelog Plugin - read more: https://github.com/JetBrains/gradle-changelog-plugin 57 | changelog { 58 | groups.empty() 59 | repositoryUrl = properties("pluginRepositoryUrl") 60 | } 61 | 62 | 63 | // Configure Gradle Kover Plugin - read more: https://github.com/Kotlin/kotlinx-kover#configuration 64 | koverReport { 65 | defaults { 66 | xml { 67 | onCheck = true 68 | } 69 | } 70 | } 71 | 72 | tasks { 73 | 74 | prepareSandbox { 75 | doLast { 76 | val pluginName = project.ext.get("pluginName") ?: throw GradleException("Plugin name not set.") 77 | 78 | val sourceDir = file("${project.projectDir}/../server/out") 79 | println("language server sourceDir: $sourceDir") 80 | val destDir = file("${destinationDir.path}/${pluginName}/language-server") 81 | println("language server destDir: $destDir") 82 | 83 | if (sourceDir.exists()) { 84 | copy { 85 | from(sourceDir) 86 | into(destDir) 87 | } 88 | } else { 89 | throw GradleException("Source directory does not exist: $sourceDir") 90 | } 91 | } 92 | } 93 | 94 | wrapper { 95 | gradleVersion = properties("gradleVersion").get() 96 | } 97 | 98 | patchPluginXml { 99 | sinceBuild.set("233") 100 | untilBuild.set("240.*") 101 | 102 | // Extract the section from README.md and provide for the plugin's manifest 103 | val readMe = layout.projectDirectory.file(file("${project.projectDir}/../README.md").path) 104 | println("readMe sourceDir: $readMe") 105 | pluginDescription = providers.fileContents(readMe).asText.map { 106 | val start1 = "" 107 | val end1 = "" 108 | val start2 = "" 109 | val end2 = "" 110 | 111 | val lines = it.lines() 112 | 113 | // Function to extract and convert content between start and end markers 114 | fun extractAndConvert(start: String, end: String): String { 115 | if (!lines.containsAll(listOf(start, end))) { 116 | throw GradleException("Plugin description section not found in README.md:\n$start ... $end") 117 | } 118 | return lines.subList(lines.indexOf(start) + 1, lines.indexOf(end)).joinToString("\n").let(::markdownToHTML) 119 | } 120 | 121 | // Extract and convert both sections 122 | val content1 = extractAndConvert(start1, end1) 123 | val content2 = extractAndConvert(start2, end2) 124 | 125 | val readMeContent = "$content1\n$content2" 126 | println("readMe content: $readMeContent") 127 | 128 | // Combine both contents 129 | readMeContent 130 | } 131 | 132 | val changelog = project.changelog // local variable for configuration cache compatibility 133 | // Get the latest available change notes from the changelog file 134 | changeNotes = properties("pluginVersion").map { pluginVersion -> 135 | with(changelog) { 136 | renderItem( 137 | (getOrNull(pluginVersion) ?: getUnreleased()) 138 | .withHeader(false) 139 | .withEmptySections(true), 140 | Changelog.OutputType.HTML, 141 | ) 142 | } 143 | } 144 | } 145 | 146 | // Configure UI tests plugin 147 | // Read more: https://github.com/JetBrains/intellij-ui-test-robot 148 | runIdeForUiTests { 149 | systemProperty("robot-server.port", "8082") 150 | systemProperty("ide.mac.message.dialogs.as.sheets", "false") 151 | systemProperty("jb.privacy.policy.text", "") 152 | systemProperty("jb.consents.confirmation.enabled", "false") 153 | } 154 | 155 | signPlugin { 156 | certificateChain = environment("CERTIFICATE_CHAIN") 157 | privateKey = environment("PRIVATE_KEY") 158 | password = environment("PRIVATE_KEY_PASSWORD") 159 | } 160 | 161 | publishPlugin { 162 | dependsOn("patchChangelog") 163 | token = environment("JETBRAINS_PUBLISH_TOKEN") 164 | // The pluginVersion is based on the SemVer (https://semver.org) and supports pre-release labels, like 2.1.7-alpha.3 165 | // Specify pre-release label to publish the plugin in a custom Release Channel automatically. Read more: 166 | // https://plugins.jetbrains.com/docs/intellij/deployment.html#specifying-a-release-channel 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /jetbrains/gradle.properties: -------------------------------------------------------------------------------- 1 | # IntelliJ Platform Artifacts Repositories -> https://plugins.jetbrains.com/docs/intellij/intellij-artifacts.html 2 | 3 | pluginGroup=github.com.michaelangelio.does-it-throw 4 | pluginName=Does it Throw? 5 | # x-release-please-start-version 6 | pluginVersion=0.5.0 7 | # x-release-please-end 8 | pluginSinceBuild=223 9 | pluginUntilBuild=223.* 10 | platformType=IU 11 | platformVersion=2023.3.2 12 | platformPlugins=JavaScript 13 | pluginVerifierIdeVersions=IU-2022.3 14 | gradleVersion=7.5.1 15 | kotlin.stdlib.default.dependency = false 16 | jvmVersion=17 17 | 18 | # Enable Gradle Kotlin DSL Lazy Property Assignment -> https://docs.gradle.org/current/userguide/kotlin_dsl.html#kotdsl:assignment 19 | systemProp.org.gradle.unsafe.kotlin.assignment = true 20 | -------------------------------------------------------------------------------- /jetbrains/gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | # libraries 3 | annotations = "24.1.0" 4 | 5 | # plugins 6 | kotlin = "1.9.21" 7 | changelog = "2.2.0" 8 | gradleIntelliJPlugin = "1.16.1" 9 | qodana = "0.1.13" 10 | kover = "0.7.5" 11 | 12 | [libraries] 13 | annotations = { group = "org.jetbrains", name = "annotations", version.ref = "annotations" } 14 | 15 | [plugins] 16 | changelog = { id = "org.jetbrains.changelog", version.ref = "changelog" } 17 | gradleIntelliJPlugin = { id = "org.jetbrains.intellij", version.ref = "gradleIntelliJPlugin" } 18 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 19 | kover = { id = "org.jetbrains.kotlinx.kover", version.ref = "kover" } 20 | qodana = { id = "org.jetbrains.qodana", version.ref = "qodana" } 21 | -------------------------------------------------------------------------------- /jetbrains/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/jetbrains/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /jetbrains/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /jetbrains/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /jetbrains/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /jetbrains/marketplace-zip-signer-cli.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michaelangeloio/does-it-throw/ff9a325ff0cfba282d38c38db06922760b9a3b18/jetbrains/marketplace-zip-signer-cli.jar -------------------------------------------------------------------------------- /jetbrains/qodana.yml: -------------------------------------------------------------------------------- 1 | # Qodana configuration: 2 | # https://www.jetbrains.com/help/qodana/qodana-yaml.html 3 | 4 | version: 1.0 5 | linter: jetbrains/qodana-jvm-community:latest 6 | projectJDK: "17" 7 | profile: 8 | name: qodana.recommended 9 | exclude: 10 | - name: All 11 | paths: 12 | - .qodana 13 | -------------------------------------------------------------------------------- /jetbrains/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0" 3 | } 4 | 5 | rootProject.name = "Does it Throw" 6 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/DoesItThrowBundle.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit 2 | 3 | import com.intellij.DynamicBundle 4 | import org.jetbrains.annotations.NonNls 5 | import org.jetbrains.annotations.PropertyKey 6 | 7 | @NonNls 8 | private const val BUNDLE = "messages.DoesItThrowBundle" 9 | 10 | object DoesItThrowBundle : DynamicBundle(BUNDLE) { 11 | 12 | @Suppress("SpreadOperator") 13 | @JvmStatic 14 | fun message(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 15 | getMessage(key, *params) 16 | 17 | @Suppress("SpreadOperator", "unused") 18 | @JvmStatic 19 | fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any) = 20 | getLazyMessage(key, *params) 21 | } 22 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/DoesItThrowUtils.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit 2 | 3 | import com.intellij.openapi.vfs.VirtualFile 4 | 5 | object DoesItThrowUtils { 6 | fun isSupportedFileType(file: VirtualFile): Boolean = when (file.extension) { 7 | "js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx", "d.ts", "json", "jsonc" -> true 8 | else -> false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/lsp/LspServerDescriptor.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit.lsp 2 | 3 | import com.intellij.execution.ExecutionException 4 | import com.intellij.execution.configurations.GeneralCommandLine 5 | import com.intellij.javascript.nodejs.interpreter.NodeCommandLineConfigurator 6 | import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager 7 | import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter 8 | import com.intellij.javascript.nodejs.interpreter.wsl.WslNodeInterpreter 9 | import com.intellij.lang.javascript.service.JSLanguageServiceUtil 10 | import com.intellij.openapi.diagnostic.logger 11 | import com.intellij.openapi.diagnostic.thisLogger 12 | import com.intellij.openapi.project.Project 13 | import com.intellij.openapi.vfs.VirtualFile 14 | import com.intellij.platform.lsp.api.ProjectWideLspServerDescriptor 15 | import org.eclipse.lsp4j.* 16 | import org.michaelangeloio.plugins.dit.DoesItThrowUtils 17 | import org.michaelangeloio.plugins.dit.settings.DoesItThrowSettings 18 | 19 | private val LOG = logger() 20 | 21 | class DoesItThrowLspServerDescriptor(project: Project) : ProjectWideLspServerDescriptor(project, "Does it Throw?") { 22 | override val clientCapabilities: ClientCapabilities = super.clientCapabilities.apply { 23 | textDocument = TextDocumentClientCapabilities().apply { 24 | publishDiagnostics = PublishDiagnosticsCapabilities().apply { 25 | versionSupport = true 26 | } 27 | hover = HoverCapabilities().apply { 28 | contentFormat = listOf(MarkupKind.MARKDOWN, MarkupKind.PLAINTEXT) 29 | } 30 | } 31 | workspace = workspace?.apply { 32 | configuration = true 33 | workspaceFolders = true 34 | didChangeWatchedFiles = DidChangeWatchedFilesCapabilities().apply { 35 | relativePatternSupport = true 36 | } 37 | } 38 | } 39 | 40 | override fun getWorkspaceConfiguration(item: ConfigurationItem): Any { 41 | LOG.info(item.scopeUri) 42 | return mapOf( 43 | "throwStatementSeverity" to DoesItThrowSettings.getInstance(project).throwStatementSeverity, 44 | "functionThrowSeverity" to DoesItThrowSettings.getInstance(project).functionThrowSeverity, 45 | "callToThrowSeverity" to DoesItThrowSettings.getInstance(project).callToThrowSeverity, 46 | "callToImportedThrowSeverity" to DoesItThrowSettings.getInstance(project).callToImportedThrowSeverity, 47 | "includeTryStatementThrows" to DoesItThrowSettings.getInstance(project).includeTryStatementThrows, 48 | "maxNumberOfProblems" to DoesItThrowSettings.getInstance(project).maxNumberOfProblems, 49 | "ignoreStatements" to DoesItThrowSettings.getInstance(project).ignoreStatements 50 | ) 51 | } 52 | override fun isSupportedFile(file: VirtualFile) = DoesItThrowUtils.isSupportedFileType(file) 53 | 54 | override fun createCommandLine(): GeneralCommandLine { 55 | val interpreter = NodeJsInterpreterManager.getInstance(project).interpreter 56 | if (interpreter !is NodeJsLocalInterpreter && interpreter !is WslNodeInterpreter) { 57 | // shouldn't happen 58 | throw ExecutionException("no local node interpreter ") 59 | } 60 | 61 | val lsp = JSLanguageServiceUtil.getPluginDirectory(javaClass, "language-server/server.js") 62 | thisLogger().info("language server loaded") 63 | thisLogger().info(lsp.path) 64 | if (lsp == null || !lsp.exists()) { 65 | // broken plugin installation? 66 | throw ExecutionException("could not find language server") 67 | } 68 | 69 | return GeneralCommandLine().apply { 70 | withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE) 71 | withCharset(Charsets.UTF_8) 72 | addParameter(lsp.path) 73 | addParameter("--stdio") 74 | 75 | NodeCommandLineConfigurator.find(interpreter) 76 | .configure(this, NodeCommandLineConfigurator.defaultOptions(project)) 77 | } 78 | } 79 | 80 | // references resolution is implemented without using the LSP server 81 | override val lspGoToDefinitionSupport = false 82 | 83 | // code completion is implemented without using the LSP server 84 | override val lspCompletionSupport = null 85 | 86 | // code formatting is implemented without using the LSP server 87 | override val lspFormattingSupport = null 88 | } 89 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/lsp/LspServerSupportProvider.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit.lsp 2 | 3 | import com.intellij.javascript.nodejs.interpreter.NodeJsInterpreterManager 4 | import com.intellij.javascript.nodejs.interpreter.local.NodeJsLocalInterpreter 5 | import com.intellij.javascript.nodejs.interpreter.wsl.WslNodeInterpreter 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.openapi.vfs.VirtualFile 8 | import com.intellij.platform.lsp.api.LspServerSupportProvider 9 | import org.michaelangeloio.plugins.dit.DoesItThrowUtils 10 | 11 | 12 | class DoesItThrowLspServerSupportProvider : LspServerSupportProvider { 13 | override fun fileOpened(project: Project, file: VirtualFile, serverStarter: LspServerSupportProvider.LspServerStarter) { 14 | if (!DoesItThrowUtils.isSupportedFileType(file)) return 15 | 16 | val node = NodeJsInterpreterManager.getInstance(project).interpreter 17 | if (node !is NodeJsLocalInterpreter && node !is WslNodeInterpreter) return 18 | 19 | serverStarter.ensureServerStarted(DoesItThrowLspServerDescriptor(project)) 20 | } 21 | } -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/services/DoesItThrowServerService.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit.services 2 | 3 | import com.intellij.notification.NotificationGroupManager 4 | import com.intellij.notification.NotificationType 5 | import com.intellij.openapi.components.Service 6 | import com.intellij.openapi.project.Project 7 | import com.intellij.platform.lsp.api.LspServerManager 8 | import org.michaelangeloio.plugins.dit.DoesItThrowBundle 9 | import org.michaelangeloio.plugins.dit.lsp.DoesItThrowLspServerSupportProvider 10 | 11 | @Service(Service.Level.PROJECT) 12 | class DoesItThrowServerService(private val project: Project) { 13 | 14 | fun restartDoesItThrowServer() { 15 | LspServerManager.getInstance(project).stopAndRestartIfNeeded(DoesItThrowLspServerSupportProvider::class.java) 16 | } 17 | 18 | fun notifyRestart() { 19 | NotificationGroupManager.getInstance() 20 | .getNotificationGroup("DoesItThrow") 21 | .createNotification( 22 | DoesItThrowBundle.message("does-it-throw.language.server.restarted"), 23 | "", 24 | NotificationType.INFORMATION 25 | ) 26 | .notify(project) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/settings/DoesItThrowConfigurable.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit.settings 2 | 3 | import com.intellij.openapi.components.service 4 | import com.intellij.openapi.options.BoundSearchableConfigurable 5 | import com.intellij.openapi.project.Project 6 | import com.intellij.openapi.ui.DialogPanel 7 | import com.intellij.ui.dsl.builder.* 8 | import org.michaelangeloio.plugins.dit.DoesItThrowBundle 9 | import org.michaelangeloio.plugins.dit.services.DoesItThrowServerService 10 | 11 | class DoesItThrowSettingsConfigurable(private val project: Project) : 12 | BoundSearchableConfigurable( 13 | DoesItThrowBundle.message("does-it-throw.settings.name"), 14 | DoesItThrowBundle.message("does-it-throw.settings.name") 15 | ) { 16 | private val settings: DoesItThrowSettings = DoesItThrowSettings.getInstance(project) 17 | private val doesItThrowServerService = project.service() 18 | 19 | override fun createPanel(): DialogPanel = panel { 20 | 21 | 22 | row(DoesItThrowBundle.message("does-it-throw.settings.includeTryStatementThrows.label")) { 23 | checkBox("").bindSelected(settings::includeTryStatementThrows) 24 | } 25 | 26 | row(DoesItThrowBundle.message("does-it-throw.settings.maxNumberOfProblems.label")) { 27 | textField().bindIntText(settings::maxNumberOfProblems) 28 | } 29 | 30 | row("Ignore Statements:") { 31 | val textArea = textArea().bindText( 32 | getter = { settings.ignoreStatements.joinToString("\n") }, 33 | setter = { text -> settings.ignoreStatements = text.split("\n").filter { it.isNotBlank() } } 34 | ) 35 | textArea.rows(10) // Sets the number of visible rows in the text area. 36 | } 37 | 38 | group("Severity Configuration") { 39 | row{ 40 | label("Allowed Values: Information, Hint, Warning, Error") 41 | } 42 | row(DoesItThrowBundle.message("does-it-throw.settings.throwStatementSeverity.label")) { 43 | textField().bindText(settings::throwStatementSeverity) 44 | } 45 | row (DoesItThrowBundle.message("does-it-throw.settings.functionThrowSeverity.label")) { 46 | textField().bindText(settings::functionThrowSeverity) 47 | } 48 | row (DoesItThrowBundle.message("does-it-throw.settings.callToThrowSeverity.label")) { 49 | textField().bindText(settings::callToThrowSeverity) 50 | } 51 | row (DoesItThrowBundle.message("does-it-throw.settings.callToImportedThrowSeverity.label")) { 52 | textField().bindText(settings::callToImportedThrowSeverity) 53 | } 54 | } 55 | 56 | onApply { 57 | doesItThrowServerService.restartDoesItThrowServer() 58 | doesItThrowServerService.notifyRestart() 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/settings/DoesItThrowSettings.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit.settings 2 | 3 | import com.intellij.openapi.components.* 4 | import com.intellij.openapi.project.Project 5 | 6 | @Service(Service.Level.PROJECT) 7 | @State(name = "DoesItThrowSettings", storages = [(Storage("does-it-throw.xml"))]) 8 | class DoesItThrowSettings : 9 | SimplePersistentStateComponent(DoesItThrowSettingsState()) { 10 | 11 | var throwStatementSeverity: String 12 | get() = state.throwStatementSeverity ?: "Hint" 13 | set(value) { 14 | state.throwStatementSeverity = value 15 | } 16 | 17 | var functionThrowSeverity: String 18 | get() = state.functionThrowSeverity ?: "Hint" 19 | set(value) { 20 | state.functionThrowSeverity = value 21 | } 22 | var callToThrowSeverity: String 23 | get() = state.callToThrowSeverity ?: "Hint" 24 | set(value) { 25 | state.callToThrowSeverity = value 26 | } 27 | var callToImportedThrowSeverity: String 28 | get() = state.callToImportedThrowSeverity ?: "Hint" 29 | set(value) { 30 | state.callToImportedThrowSeverity = value 31 | } 32 | var includeTryStatementThrows: Boolean 33 | get() = state.includeTryStatementThrows ?: false 34 | set(value) { 35 | state.includeTryStatementThrows = value 36 | } 37 | var maxNumberOfProblems: Int 38 | get() = state.maxNumberOfProblems ?: 1000 39 | set(value) { 40 | state.maxNumberOfProblems = value 41 | } 42 | var ignoreStatements: List 43 | get() = state.ignoreStatements ?: listOf( 44 | "@it-throws", 45 | "@does-it-throw-ignore" 46 | ) 47 | set(value) { 48 | state.ignoreStatements = value 49 | } 50 | 51 | companion object { 52 | @JvmStatic 53 | fun getInstance(project: Project): DoesItThrowSettings = project.service() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /jetbrains/src/main/kotlin/org/michaelangeloio/plugins/dit/settings/DoesItThrowSettingsState.kt: -------------------------------------------------------------------------------- 1 | package org.michaelangeloio.plugins.dit.settings 2 | 3 | import com.intellij.openapi.components.BaseState 4 | import com.intellij.openapi.components.Service 5 | import org.jetbrains.annotations.ApiStatus 6 | 7 | @Service 8 | @ApiStatus.Internal 9 | class DoesItThrowSettingsState : BaseState() { 10 | var throwStatementSeverity by string("Hint") 11 | var functionThrowSeverity by string("Hint") 12 | var callToThrowSeverity by string("Hint") 13 | var callToImportedThrowSeverity by string("Hint") 14 | var includeTryStatementThrows by property(false) 15 | var maxNumberOfProblems by property(1000) 16 | var ignoreStatements = listOf( 17 | "@it-throws", 18 | "@does-it-throw-ignore" 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /jetbrains/src/main/resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | com.intellij.modules.platform 4 | com.intellij.modules.ultimate 5 | JavaScript 6 | 7 | messages.DoesItThrowBundle 8 | 9 | org.michaelangeloio.plugins.dit 10 | Does it Throw? 11 | michaelangeloio 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /jetbrains/src/main/resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.10, written by Peter Selinger 2001-2011 9 | 10 | 12 | 212 | 219 | 221 | 224 | 227 | 229 | 231 | 233 | 237 | 239 | 241 | 243 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /jetbrains/src/main/resources/messages/DoesItThrowBundle.properties: -------------------------------------------------------------------------------- 1 | name=Does It Throw? 2 | does-it-throw.settings.name = Does It Throw Settings 3 | 4 | does-it-throw.settings.throwStatementSeverity.label = Throw Statement Severity 5 | does-it-throw.settings.functionThrowSeverity.label = Function Throw Severity 6 | does-it-throw.settings.callToThrowSeverity.label = Call to Throw Severity 7 | does-it-throw.settings.callToImportedThrowSeverity.label = Call to Imported Throw Severity 8 | does-it-throw.settings.includeTryStatementThrows.label = Include Try Statement Throws 9 | does-it-throw.settings.maxNumberOfProblems.label = Max Number of Problems 10 | does-it-throw.settings.ignoreStatements.label = Ignore Statements 11 | does-it-throw.language.server.restarted=Does it Throw? server restarted -------------------------------------------------------------------------------- /jetbrains/src/test/kotlin/org/michaelangeloio/plugins/dit/DoesItThrowUtils.kt: -------------------------------------------------------------------------------- 1 | import com.intellij.openapi.vfs.VirtualFile 2 | import org.junit.jupiter.api.Assertions.assertFalse 3 | import org.junit.jupiter.api.Assertions.assertTrue 4 | import org.junit.jupiter.api.Test 5 | import org.michaelangeloio.plugins.dit.DoesItThrowUtils 6 | import org.mockito.kotlin.mock 7 | import org.mockito.kotlin.whenever 8 | 9 | class DoesItThrowUtilsTest { 10 | 11 | private val mockFile: VirtualFile = mock() 12 | 13 | @Test 14 | fun `should return true for supported file extensions`() { 15 | val supportedExtensions = listOf("js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx", "d.ts", "json", "jsonc") 16 | supportedExtensions.forEach { extension -> 17 | whenever(mockFile.extension).thenReturn(extension) 18 | assertTrue(DoesItThrowUtils.isSupportedFileType(mockFile)) 19 | } 20 | } 21 | 22 | @Test 23 | fun `should return false for unsupported file extensions`() { 24 | whenever(mockFile.extension).thenReturn("unsupported") 25 | assertFalse(DoesItThrowUtils.isSupportedFileType(mockFile)) 26 | } 27 | 28 | @Test 29 | fun `should return false for null or empty extensions`() { 30 | whenever(mockFile.extension).thenReturn(null) 31 | assertFalse(DoesItThrowUtils.isSupportedFileType(mockFile)) 32 | 33 | whenever(mockFile.extension).thenReturn("") 34 | assertFalse(DoesItThrowUtils.isSupportedFileType(mockFile)) 35 | } 36 | 37 | @Test 38 | fun `should correctly handle case sensitivity`() { 39 | // Assuming the function is case-insensitive 40 | whenever(mockFile.extension).thenReturn("JS") 41 | assertFalse(DoesItThrowUtils.isSupportedFileType(mockFile)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /jetbrains/version.txt: -------------------------------------------------------------------------------- 1 | 0.5.0 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "does-it-throw-vscode", 3 | "version": "0.5.0", 4 | "main": "./client/out/extension.js", 5 | "displayName": "Does It Throw?", 6 | "description": "easily find throw statements in your JS, TS, and JSX files!", 7 | "repository": { 8 | "url": "https://github.com/michaelangeloio/does-it-throw" 9 | }, 10 | "icon": "assets/icon-small.png", 11 | "author": { 12 | "name": "Michael Angelo", 13 | "email": "email@michaelangelo.io" 14 | }, 15 | "categories": [ 16 | "Programming Languages", 17 | "Linters", 18 | "Debuggers" 19 | ], 20 | "keywords": [ 21 | "does it throw", 22 | "throw finder", 23 | "throw", 24 | "javascript", 25 | "typescript", 26 | "lsp", 27 | "language server", 28 | "exceptions", 29 | "extension", 30 | "exception finder" 31 | ], 32 | "galleryBanner": { 33 | "color": "#050b1f", 34 | "theme": "dark" 35 | }, 36 | "badges": [ 37 | { 38 | "url": "https://github.com/michaelangeloio/does-it-throw/actions/workflows/rust.yaml/badge.svg", 39 | "href": "https://github.com/michaelangeloio/does-it-throw", 40 | "description": "Rust Build Status" 41 | }, 42 | { 43 | "url": "https://github.com/michaelangeloio/does-it-throw/actions/workflows/ts.yaml/badge.svg", 44 | "href": "https://github.com/michaelangeloio/does-it-throw", 45 | "description": "Node/Bun Build Status" 46 | } 47 | ], 48 | "qna": "https://github.com/michaelangeloio/does-it-throw/discussions", 49 | "licenses": [ 50 | { 51 | "type": "MIT", 52 | "url": "https://github.com/michaelangeloio/does-it-throw/blob/main/LICENSE" 53 | } 54 | ], 55 | "devDependencies": { 56 | "@biomejs/biome": "^1.5.3", 57 | "@types/node": "^20.11.6", 58 | "@vscode/vsce": "^2.22.0", 59 | "bun-types": "^1.0.25", 60 | "chokidar": "^3.5.3", 61 | "esbuild": "^0.19.12", 62 | "esbuild-plugin-copy": "^2.1.1", 63 | "typescript": "5.3.3" 64 | }, 65 | "activationEvents": [ 66 | "onLanguage:typescript", 67 | "onLanguage:typescriptreact", 68 | "onLanguage:javascript", 69 | "onLanguage:javascriptreact" 70 | ], 71 | "contributes": { 72 | "configuration": { 73 | "type": "object", 74 | "title": "Example configuration", 75 | "properties": { 76 | "doesItThrow.throwStatementSeverity": { 77 | "scope": "resource", 78 | "type": "string", 79 | "enum": [ 80 | "Error", 81 | "Warning", 82 | "Information", 83 | "Hint" 84 | ], 85 | "default": "Hint", 86 | "description": "Controls the severity level of a throw statement." 87 | }, 88 | "doesItThrow.functionThrowSeverity": { 89 | "scope": "resource", 90 | "type": "string", 91 | "enum": [ 92 | "Error", 93 | "Warning", 94 | "Information", 95 | "Hint" 96 | ], 97 | "default": "Hint", 98 | "description": "Controls the severity level of a function/const that throws." 99 | }, 100 | "doesItThrow.callToThrowSeverity": { 101 | "scope": "resource", 102 | "type": "string", 103 | "enum": [ 104 | "Error", 105 | "Warning", 106 | "Information", 107 | "Hint" 108 | ], 109 | "default": "Hint", 110 | "description": "Controls the severity level of a function/const that calls a function that throws in the same file." 111 | }, 112 | "doesItThrow.callToImportedThrowSeverity": { 113 | "scope": "resource", 114 | "type": "string", 115 | "enum": [ 116 | "Error", 117 | "Warning", 118 | "Information", 119 | "Hint" 120 | ], 121 | "default": "Hint", 122 | "description": "Controls the severity level of a function/const that calls a function that throws in a different file." 123 | }, 124 | "doesItThrow.maxNumberOfProblems": { 125 | "scope": "resource", 126 | "type": "number", 127 | "default": 100, 128 | "description": "Controls the maximum number of problems produced by the server." 129 | }, 130 | "doesItThrow.includeTryStatementThrows": { 131 | "scope": "resource", 132 | "type": "boolean", 133 | "default": false, 134 | "description": "Include throw statements inside try statements." 135 | }, 136 | "doesItThrow.ignoreStatements": { 137 | "scope": "resource", 138 | "type": "array", 139 | "items": { 140 | "type": "string" 141 | }, 142 | "default": [ 143 | "@it-throws", 144 | "@does-it-throw-ignore" 145 | ], 146 | "description": "Ignore throw statements with comments above that match these strings." 147 | }, 148 | "doesItThrow.trace.server": { 149 | "scope": "window", 150 | "type": "string", 151 | "enum": [ 152 | "off", 153 | "messages", 154 | "verbose" 155 | ], 156 | "default": "off", 157 | "description": "Enable for debug mode. Traces the communication between VS Code and the language server." 158 | } 159 | } 160 | } 161 | }, 162 | "engines": { 163 | "vscode": "^1.75.0" 164 | }, 165 | "license": "MIT", 166 | "publisher": "michaelangeloio", 167 | "scripts": { 168 | "vscode:prepublish": "npm run compile", 169 | "compile": "bun run build.ts", 170 | "watch": "bun run build.ts --watch", 171 | "build": "bun run build.ts", 172 | "vscode:release": "vsce publish", 173 | "vscode:package": "vsce package", 174 | "format": "biome format .", 175 | "pack:server": "npm pack --workspace server", 176 | "publish:server": "npm publish --workspace server" 177 | }, 178 | "types": "index.d.ts", 179 | "workspaces": [ 180 | "client", 181 | "server" 182 | ] 183 | } 184 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "release-label": "released", 3 | "include-v-in-tag": true, 4 | "tag-separator": "-", 5 | "plugins": [ 6 | "cargo-workspace", 7 | { 8 | "type": "linked-versions", 9 | "groupName": "does-it-throw-rust", 10 | "components": ["crates/does-it-throw", "crates/does-it-throw-wasm"] 11 | } 12 | ], 13 | "packages": { 14 | ".": { 15 | "release-type": "node" 16 | }, 17 | "server": { 18 | "release-type": "node" 19 | }, 20 | "crates/does-it-throw": { 21 | "release-type": "rust" 22 | }, 23 | "crates/does-it-throw-wasm": { 24 | "release-type": "rust" 25 | }, 26 | "jetbrains": { 27 | "release-type": "simple", 28 | "package-name": "does-it-throw-jetbrains", 29 | "extra-files": ["gradle.properties"] 30 | } 31 | }, 32 | "bootstrap-sha": "89c1833c752211c7e712ef926c3d14f85ac1a779", 33 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" 34 | } 35 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | edition = "2021" 3 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | README.md 3 | LICENSE.txt 4 | assets -------------------------------------------------------------------------------- /server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [0.5.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.4.0...does-it-throw-lsp-v0.5.0) (2024-01-25) 4 | 5 | 6 | ### Features 7 | 8 | * **deps-dev:** bump @biomejs/biome from 1.4.1 to 1.5.3 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 9 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.5 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 10 | * **deps-dev:** bump @types/node from 20.10.5 to 20.11.6 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 11 | * **deps-dev:** bump bun-types from 1.0.20 to 1.0.25 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 12 | * **deps-dev:** bump esbuild from 0.19.11 to 0.19.12 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 13 | * **deps:** bump serde from 1.0.193 to 1.0.195 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 14 | * **deps:** bump serde_json from 1.0.109 to 1.0.111 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 15 | * **deps:** bump swc_ecma_ast from 0.110.15 to 0.110.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 16 | * **deps:** bump swc_ecma_ast from 0.110.17 to 0.111.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 17 | * **deps:** bump swc_ecma_parser from 0.141.34 to 0.142.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 18 | * **deps:** bump swc_ecma_visit from 0.96 to 0.97.1 ([2ae9f64](https://github.com/michaelangeloio/does-it-throw/commit/2ae9f64e149a74502547ed60e1b4737518844b4b)) 19 | * **deps:** bump swc_ecma_visit from 0.96.15 to 0.96.17 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 20 | * **deps:** bump vscode-languageserver-textdocument from 1.0.8 to 1.0.11 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 21 | * **deps:** bump wasm-bindgen from 0.2.89 to 0.2.90 ([ea29908](https://github.com/michaelangeloio/does-it-throw/commit/ea29908c94253fc3171738f7fc802a71f9166d75)) 22 | * LSP now supports workspace folders ([#127](https://github.com/michaelangeloio/does-it-throw/issues/127)) ([960b486](https://github.com/michaelangeloio/does-it-throw/commit/960b486e8cfe4fd5165be4dd200457c7e5b90979)) 23 | 24 | 25 | ### Bug Fixes 26 | 27 | * enhance call to throw logic and handle return statements ([#140](https://github.com/michaelangeloio/does-it-throw/issues/140)) ([a1bfaf1](https://github.com/michaelangeloio/does-it-throw/commit/a1bfaf16c768aeb49ecaecb991ca6a2b57e71072)) 28 | 29 | ## [0.4.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.3...does-it-throw-lsp-v0.4.0) (2024-01-06) 30 | 31 | 32 | ### Features 33 | 34 | * **deps-dev:** bump typescript from 5.2.2 to 5.3.3 ([#102](https://github.com/michaelangeloio/does-it-throw/issues/102)) ([5f45eff](https://github.com/michaelangeloio/does-it-throw/commit/5f45eff8493f674470331c252bdfc2f558d96c3f)) 35 | * **deps:** bump serde_json from 1.0.108 to 1.0.109 ([#109](https://github.com/michaelangeloio/does-it-throw/issues/109)) ([5a28b3f](https://github.com/michaelangeloio/does-it-throw/commit/5a28b3f26992c4bca9d7bb276efdd27fa5b9a53a)) 36 | * **deps:** bump swc_ecma_parser from 0.141.33 to 0.141.34 ([#110](https://github.com/michaelangeloio/does-it-throw/issues/110)) ([482be78](https://github.com/michaelangeloio/does-it-throw/commit/482be78a20732f350377d4e534afae1053080e58)) 37 | * user can now discard warnings with ignore statements ([#118](https://github.com/michaelangeloio/does-it-throw/issues/118)) ([3f8957c](https://github.com/michaelangeloio/does-it-throw/commit/3f8957c60fd90f9ab7b6646c04ec22dcecb21556)) 38 | 39 | ## [0.3.3](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.2...does-it-throw-lsp-v0.3.3) (2023-12-24) 40 | 41 | 42 | ### Bug Fixes 43 | 44 | * catch with throw statement not included ([#95](https://github.com/michaelangeloio/does-it-throw/issues/95)) ([fd223db](https://github.com/michaelangeloio/does-it-throw/commit/fd223db4f56e87439999b9b33a393769bd2b7c5b)) 45 | 46 | * **deps-dev:** remove remaining eslint dev dependencies ([#97](https://github.com/michaelangeloio/does-it-throw/issues/97)) ([5f173a6](https://github.com/michaelangeloio/does-it-throw/commit/5f173a69cb86570a526a665d453b86ae776538d0)) 47 | 48 | ## [0.3.2](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.1...does-it-throw-lsp-v0.3.2) (2023-12-17) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * update server package.json keywords ([#87](https://github.com/michaelangeloio/does-it-throw/issues/87)) ([c19717d](https://github.com/michaelangeloio/does-it-throw/commit/c19717d96a09152d959bfd7d5c3a34ac62f5e26d)) 54 | 55 | ## [0.3.1](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.3.0...does-it-throw-lsp-v0.3.1) (2023-12-17) 56 | 57 | 58 | ### Bug Fixes 59 | 60 | * functions and throw statements are underlined even if caught ([#81](https://github.com/michaelangeloio/does-it-throw/issues/81)) ([16adf85](https://github.com/michaelangeloio/does-it-throw/commit/16adf85b05b92542fa6c09ac1611dd56c7603c99)) 61 | 62 | ## [0.3.0](https://github.com/michaelangeloio/does-it-throw/compare/does-it-throw-lsp-v0.2.5...does-it-throw-lsp-v0.3.0) (2023-12-16) 63 | 64 | 65 | ### Features 66 | 67 | * neovim support ([#78](https://github.com/michaelangeloio/does-it-throw/issues/78)) ([6152786](https://github.com/michaelangeloio/does-it-throw/commit/61527869e70f54e99616375f7efd53b24e0fa01a)) 68 | 69 | 70 | ### Bug Fixes 71 | 72 | * add biome for standardization, ensure the builder reports errors correctly ([#72](https://github.com/michaelangeloio/does-it-throw/issues/72)) ([0d18392](https://github.com/michaelangeloio/does-it-throw/commit/0d18392268516abb79d015f90495dd331e7ef998)) 73 | * re-organize primary crate into modules ([#42](https://github.com/michaelangeloio/does-it-throw/issues/42)) ([badb106](https://github.com/michaelangeloio/does-it-throw/commit/badb1061d0dfc679458d55609e43cccfdca01794)) 74 | * results should still show even if file cannot resolve (calls to throws) ([#76](https://github.com/michaelangeloio/does-it-throw/issues/76)) ([f908556](https://github.com/michaelangeloio/does-it-throw/commit/f908556dfda8eca9195c87269fac71bc6d3e8bf9)) 75 | * update details, fix logic in some call expressions, including spread operators ([#40](https://github.com/michaelangeloio/does-it-throw/issues/40)) ([cdfdf47](https://github.com/michaelangeloio/does-it-throw/commit/cdfdf47a2d657364abc1b3b3ce97e89405b842b3)) 76 | -------------------------------------------------------------------------------- /server/bin/does-it-throw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const path = require('path'); 4 | require(path.join(__dirname, '..', 'out', 'server.js')) -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "does-it-throw-lsp", 3 | "description": "Language Server for Does It Throw", 4 | "version": "0.5.0", 5 | "//": "target-version: 0.5.0", 6 | "author": { 7 | "name": "Michael Angelo Rivera", 8 | "email": "email@michaelangelo.io" 9 | }, 10 | "license": "MIT", 11 | "licenses": [ 12 | { 13 | "type": "MIT", 14 | "url": "https://github.com/michaelangeloio/does-it-throw/blob/main/LICENSE" 15 | } 16 | ], 17 | "engines": { 18 | "node": "*" 19 | }, 20 | "repository": "https://github.com/michaelangeloio/does-it-throw", 21 | "categories": [ 22 | "Programming Languages", 23 | "Linters", 24 | "Debuggers" 25 | ], 26 | "keywords": [ 27 | "does it throw", 28 | "throw finder", 29 | "throw", 30 | "javascript", 31 | "typescript", 32 | "lsp", 33 | "language server", 34 | "exceptions", 35 | "extension", 36 | "exception finder" 37 | ], 38 | "qna": "https://github.com/michaelangeloio/does-it-throw/discussions", 39 | "dependencies": { 40 | "vscode-languageserver": "^9.0.1", 41 | "vscode-languageserver-textdocument": "^1.0.11" 42 | }, 43 | "files": [ 44 | "out", 45 | "package.json", 46 | "README.md", 47 | "LICENSE.txt", 48 | "bin" 49 | ], 50 | "main": "out/server.js", 51 | "bin": { 52 | "does-it-throw-lsp": "./bin/does-it-throw" 53 | }, 54 | "scripts": {} 55 | } -------------------------------------------------------------------------------- /server/src/server.ts: -------------------------------------------------------------------------------- 1 | import { 2 | DidChangeConfigurationNotification, 3 | InitializeParams, 4 | InitializeResult, 5 | ProposedFeatures, 6 | TextDocumentSyncKind, 7 | TextDocuments, 8 | createConnection 9 | } from 'vscode-languageserver/node' 10 | 11 | import { access, constants, readFile } from 'fs/promises' 12 | import { TextDocument } from 'vscode-languageserver-textdocument' 13 | import { InputData, ParseResult, parse_js } from './rust/does_it_throw_wasm' 14 | import path = require('path') 15 | import { inspect } from 'util' 16 | 17 | const connection = createConnection(ProposedFeatures.all) 18 | 19 | const documents: TextDocuments = new TextDocuments(TextDocument) 20 | let hasConfigurationCapability = false 21 | let hasWorkspaceFolderCapability = false 22 | 23 | connection.onInitialize((params: InitializeParams) => { 24 | const capabilities = params.capabilities 25 | 26 | // Does the client support the `workspace/configuration` request? 27 | // If not, we fall back using global settings. 28 | hasConfigurationCapability = !!(capabilities.workspace && !!capabilities.workspace.configuration) 29 | hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders) 30 | 31 | const result: InitializeResult = { 32 | capabilities: { 33 | textDocumentSync: TextDocumentSyncKind.Incremental 34 | } 35 | } 36 | if (hasWorkspaceFolderCapability) { 37 | result.capabilities.workspace = { 38 | workspaceFolders: { 39 | supported: true 40 | } 41 | } 42 | } 43 | return result 44 | }) 45 | 46 | connection.onInitialized(() => { 47 | if (hasConfigurationCapability) { 48 | // Register for all configuration changes. 49 | connection.client.register(DidChangeConfigurationNotification.type, undefined) 50 | } 51 | if (hasWorkspaceFolderCapability) { 52 | connection.workspace.onDidChangeWorkspaceFolders((_event) => { 53 | connection.console.log(`Workspace folder change event received. ${JSON.stringify(_event)}`) 54 | }) 55 | } 56 | }) 57 | 58 | type DiagnosticSeverity = 'Error' | 'Warning' | 'Information' | 'Hint' 59 | 60 | // The server settings 61 | interface Settings { 62 | maxNumberOfProblems: number 63 | throwStatementSeverity: DiagnosticSeverity 64 | functionThrowSeverity: DiagnosticSeverity 65 | callToThrowSeverity: DiagnosticSeverity 66 | callToImportedThrowSeverity: DiagnosticSeverity 67 | includeTryStatementThrows: boolean 68 | ignoreStatements: string[] 69 | } 70 | 71 | // The global settings, used when the `workspace/configuration` request is not supported by the client. 72 | // Please note that this is not the case when using this server with the client provided in this example 73 | // but could happen with other clients. 74 | const defaultSettings: Settings = { 75 | maxNumberOfProblems: 1000000, 76 | throwStatementSeverity: 'Hint', 77 | functionThrowSeverity: 'Hint', 78 | callToThrowSeverity: 'Hint', 79 | callToImportedThrowSeverity: 'Hint', 80 | includeTryStatementThrows: false, 81 | ignoreStatements: ['@it-throws', '@does-it-throw-ignore'] 82 | } 83 | // 👆 very unlikely someone will have more than 1 million throw statements, lol 84 | // if you do, might want to rethink your code? 85 | let globalSettings: Settings = defaultSettings 86 | 87 | // Cache the settings of all open documents 88 | const documentSettings: Map> = new Map() 89 | 90 | connection.onDidChangeConfiguration((change) => { 91 | if (hasConfigurationCapability) { 92 | // Reset all cached document settings 93 | documentSettings.clear() 94 | } else { 95 | globalSettings = (change.settings.doesItThrow || defaultSettings) 96 | } 97 | 98 | // Revalidate all open text documents 99 | // biome-ignore lint/complexity/noForEach: original vscode-languageserver code 100 | documents.all().forEach(validateTextDocument) 101 | }) 102 | 103 | function getDocumentSettings(resource: string): Thenable { 104 | if (!hasConfigurationCapability) { 105 | connection.console.info(`does not have config capability, using global settings: ${JSON.stringify(globalSettings)}`) 106 | return Promise.resolve(globalSettings) 107 | } 108 | let result = documentSettings.get(resource) 109 | if (!result) { 110 | result = connection.workspace.getConfiguration({ 111 | scopeUri: resource, 112 | section: 'doesItThrow' 113 | }) 114 | documentSettings.set(resource, result) 115 | } 116 | return result 117 | } 118 | 119 | // Only keep settings for open documents 120 | documents.onDidClose((e) => { 121 | documentSettings.delete(e.document.uri) 122 | }) 123 | 124 | // The content of a text document has changed. This event is emitted 125 | // when the text document first opened or when its content has changed. 126 | documents.onDidChangeContent(async (change) => { 127 | validateTextDocument(change.document) 128 | }) 129 | 130 | documents.onDidSave((change) => { 131 | validateTextDocument(change.document) 132 | }) 133 | 134 | const _checkAccessOnFile = async (file: string) => { 135 | try { 136 | await access(file, constants.R_OK) 137 | return Promise.resolve(file) 138 | } catch (e) { 139 | return Promise.reject(e) 140 | } 141 | } 142 | 143 | const findFirstFileThatExists = async (uri: string, relative_import: string) => { 144 | const isTs = uri.endsWith('.ts') || uri.endsWith('.tsx') 145 | const baseUri = `${path.resolve(path.dirname(uri.replace('file://', '')), relative_import)}` 146 | let files = Array(4) 147 | if (isTs) { 148 | files = [`${baseUri}.ts`, `${baseUri}.tsx`, `${baseUri}.js`, `${baseUri}.jsx`] 149 | } else { 150 | files = [`${baseUri}.js`, `${baseUri}.jsx`, `${baseUri}.ts`, `${baseUri}.tsx`] 151 | } 152 | return Promise.any(files.map(_checkAccessOnFile)) 153 | } 154 | 155 | async function validateTextDocument(textDocument: TextDocument): Promise { 156 | let settings = await getDocumentSettings(textDocument.uri) 157 | if (!settings) { 158 | // this should never happen, but just in case 159 | connection.console.warn(`No settings found for ${textDocument.uri}, using defaults`) 160 | settings = defaultSettings 161 | } 162 | try { 163 | const opts = { 164 | uri: textDocument.uri, 165 | file_content: textDocument.getText(), 166 | ids_to_check: [], 167 | typescript_settings: { 168 | decorators: true 169 | }, 170 | function_throw_severity: settings?.functionThrowSeverity ?? defaultSettings.functionThrowSeverity, 171 | throw_statement_severity: settings?.throwStatementSeverity ?? defaultSettings.throwStatementSeverity, 172 | call_to_imported_throw_severity: 173 | settings?.callToImportedThrowSeverity ?? defaultSettings.callToImportedThrowSeverity, 174 | call_to_throw_severity: settings?.callToThrowSeverity ?? defaultSettings.callToThrowSeverity, 175 | include_try_statement_throws: settings?.includeTryStatementThrows ?? defaultSettings.includeTryStatementThrows, 176 | ignore_statements: settings?.ignoreStatements ?? defaultSettings.ignoreStatements 177 | } satisfies InputData 178 | const analysis = parse_js(opts) as ParseResult 179 | 180 | if (analysis.relative_imports.length > 0) { 181 | const filePromises = analysis.relative_imports.map(async (relative_import) => { 182 | try { 183 | const file = await findFirstFileThatExists(textDocument.uri, relative_import) 184 | return { 185 | fileContent: await readFile(file, 'utf-8'), 186 | fileUri: file 187 | } 188 | } catch (e) { 189 | connection.console.log(`Error reading file ${inspect(e)}`) 190 | return undefined 191 | } 192 | }) 193 | const files = (await Promise.all(filePromises)).filter((file) => !!file) 194 | const analysisArr = files.map((file) => { 195 | if (!file) { 196 | return undefined 197 | } 198 | const opts = { 199 | uri: file.fileUri, 200 | file_content: file.fileContent, 201 | ids_to_check: [], 202 | typescript_settings: { 203 | decorators: true 204 | } 205 | } satisfies InputData 206 | return parse_js(opts) as ParseResult 207 | }) 208 | // TODO - this is a bit of a mess, but it works for now. 209 | // The original analysis is the one that has the throw statements Map() 210 | // We get the get the throw_ids from the imported analysis and then 211 | // check the original analysis for existing throw_ids. 212 | // This allows to to get the diagnostics from the imported analysis (one level deep for now) 213 | for (const import_analysis of analysisArr) { 214 | if (!import_analysis) { 215 | return 216 | } 217 | if (import_analysis.throw_ids.length) { 218 | for (const throw_id of import_analysis.throw_ids) { 219 | const newDiagnostics = analysis.imported_identifiers_diagnostics.get(throw_id) 220 | if (newDiagnostics?.diagnostics?.length) { 221 | analysis.diagnostics.push(...newDiagnostics.diagnostics) 222 | } 223 | } 224 | } 225 | } 226 | } 227 | connection.sendDiagnostics({ 228 | uri: textDocument.uri, 229 | diagnostics: analysis.diagnostics 230 | }) 231 | } catch (e) { 232 | console.log(e) 233 | connection.console.error(`Error parsing file ${textDocument.uri}`) 234 | connection.console.error(`settings are: ${JSON.stringify(settings)}`) 235 | connection.console.error(`Error: ${e instanceof Error ? e.message : JSON.stringify(e)} error`) 236 | connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] }) 237 | } 238 | } 239 | 240 | connection.onDidChangeWatchedFiles((_change) => { 241 | // Monitored files have change in VSCode 242 | connection.console.log(`We received an file change event ${_change}, not implemented yet`) 243 | }) 244 | 245 | // Make the text document manager listen on the connection 246 | // for open, change and close text document events 247 | documents.listen(connection) 248 | 249 | // Listen on the connection 250 | connection.listen() 251 | -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2021", 4 | "lib": [ 5 | "es2021", 6 | "DOM" 7 | ], 8 | "module": "commonjs", 9 | "moduleResolution": "node", 10 | "sourceMap": true, 11 | "strict": true, 12 | "outDir": "out", 13 | "rootDir": "src" 14 | }, 15 | "include": [ 16 | "src" 17 | ], 18 | "exclude": [ 19 | "node_modules", 20 | ".vscode-test" 21 | ] 22 | } -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "types": [ 5 | "bun-types" 6 | ], 7 | "module": "commonjs", 8 | "target": "es2021", 9 | "lib": [ 10 | "es2021" 11 | ], 12 | "outDir": "out", 13 | "rootDir": "src", 14 | "sourceMap": true 15 | }, 16 | "include": [ 17 | "src" 18 | ], 19 | "exclude": [ 20 | "node_modules", 21 | ".vscode-test" 22 | ], 23 | "references": [ 24 | { 25 | "path": "./client" 26 | }, 27 | { 28 | "path": "./server" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "types": ["bun-types"], 5 | "strict": true 6 | }, 7 | "include": ["build.ts"] 8 | } 9 | --------------------------------------------------------------------------------