├── .cargo
└── config
├── .editorconfig
├── .gitattributes
├── .github
├── codecov.yaml
├── dependabot.yaml
└── workflows
│ ├── main.yaml
│ └── release.yaml
├── .gitignore
├── .gitmodules
├── CODE_OF_CONDUCT.md
├── Cargo.lock
├── Cargo.toml
├── LICENSE
├── README.md
├── clippy.toml
├── crates
├── browser
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── cli
│ ├── Cargo.toml
│ └── src
│ │ └── bin
│ │ └── main.rs
├── languages
│ ├── Cargo.toml
│ ├── build.rs
│ └── src
│ │ ├── error.rs
│ │ ├── language.rs
│ │ └── lib.rs
├── macros
│ ├── Cargo.toml
│ └── src
│ │ └── lib.rs
├── server
│ ├── Cargo.toml
│ ├── build.rs
│ ├── src
│ │ ├── core.rs
│ │ ├── core
│ │ │ ├── document.rs
│ │ │ ├── error.rs
│ │ │ ├── lock.rs
│ │ │ ├── map.rs
│ │ │ ├── reference.rs
│ │ │ ├── session.rs
│ │ │ └── text.rs
│ │ ├── handler.rs
│ │ ├── handler
│ │ │ └── text_document.rs
│ │ ├── lib.rs
│ │ ├── metadata.rs
│ │ ├── provider.rs
│ │ └── provider
│ │ │ ├── text_document.rs
│ │ │ └── text_document
│ │ │ ├── document_symbol.rs
│ │ │ ├── document_symbol
│ │ │ ├── wast.rs
│ │ │ └── wat.rs
│ │ │ ├── publish_diagnostics.rs
│ │ │ ├── publish_diagnostics
│ │ │ ├── wast.rs
│ │ │ └── wat.rs
│ │ │ ├── semantic_tokens.rs
│ │ │ └── semantic_tokens
│ │ │ ├── builder.rs
│ │ │ ├── wast.rs
│ │ │ └── wat.rs
│ └── tests
│ │ └── all
│ │ ├── lsp.rs
│ │ └── main.rs
├── syntax
│ ├── Cargo.toml
│ └── src
│ │ ├── language.rs
│ │ ├── language
│ │ ├── wast.rs
│ │ └── wat.rs
│ │ ├── lib.rs
│ │ ├── node.rs
│ │ ├── node
│ │ └── walker.rs
│ │ └── range.rs
└── testing
│ ├── Cargo.toml
│ └── src
│ ├── jsonrpc.rs
│ ├── lib.rs
│ ├── lsp.rs
│ └── service.rs
├── fuzz
├── Cargo.toml
├── README.md
└── fuzz_targets
│ └── lsp
│ └── text_document
│ └── did_open.rs
├── rust-toolchain
├── rustfmt.toml
└── xtask
├── Cargo.toml
└── src
└── main.rs
/.cargo/config:
--------------------------------------------------------------------------------
1 | [alias]
2 | xtask = "run --package xtask --"
3 |
4 | [build]
5 | rustflags = "--cfg=web_sys_unstable_apis"
6 |
7 | [target.wasm32-unknown-unknown]
8 | runner = "wasm-bindgen-test-runner"
9 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [Makefile]
12 | indent_style = tab
13 |
14 | [*.rs]
15 | indent_size = 4
16 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto eol=lf
2 | *.png filter=lfs diff=lfs merge=lfs -text
3 |
--------------------------------------------------------------------------------
/.github/codecov.yaml:
--------------------------------------------------------------------------------
1 | coverage:
2 | status:
3 | project:
4 | default:
5 | informational: true
6 | patch:
7 | default:
8 | informational: true
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yaml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "cargo"
4 | directory: "/"
5 | schedule:
6 | interval: "daily"
7 |
--------------------------------------------------------------------------------
/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | name: main
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | # skip ci if the last commit contains the appropriate tag
13 | skip-commit:
14 | name: Conditionally skip ci
15 | runs-on: ubuntu-latest
16 | steps:
17 | - if: "contains(github.event.head_commit.message, '[skip-ci]')
18 | || contains(github.event.head_commit.message, '[skip ci]')
19 | || contains(github.event.head_commit.message, '[ci-skip]')
20 | || contains(github.event.head_commit.message, '[ci skip]')"
21 | run: exit 78
22 |
23 | # verify that Cargo.lock passes audit
24 | cargo-audit:
25 | name: Run cargo audit
26 | needs: [skip-commit]
27 | runs-on: ubuntu-latest
28 | steps:
29 | - uses: actions/checkout@v2
30 | - name: Fetch latest release version of cargo-audit
31 | run: |
32 | mkdir -p .github/caching
33 | cargo search cargo-audit | grep '^cargo-audit' | awk '{gsub(/"/,"",$3); print $3}' > .github/caching/cargo-audit.lock
34 | - name: Cache cargo-audit/bin
35 | id: cache-cargo-audit
36 | uses: actions/cache@v1
37 | with:
38 | path: ${{ runner.tool_cache }}/cargo-audit/bin
39 | key: cargo-audit-bin-${{ hashFiles('.github/caching/cargo-audit.lock') }}
40 | - name: Install cargo-audit
41 | if: "steps.cache-cargo-audit.outputs.cache-hit != 'true'"
42 | uses: actions-rs/cargo@v1
43 | with:
44 | command: install
45 | args: --root ${{ runner.tool_cache }}/cargo-audit --force cargo-audit
46 | - run: echo "${{ runner.tool_cache }}/cargo-audit/bin" >> $GITHUB_PATH
47 | - run: cargo audit
48 |
49 | # verify that project passes clippy lints
50 | cargo-clippy:
51 | name: Run cargo clippy
52 | needs: [skip-commit]
53 | runs-on: ubuntu-latest
54 | steps:
55 | - uses: actions/checkout@v2
56 | - name: Install Rust toolchain
57 | uses: actions-rs/toolchain@v1
58 | with:
59 | profile: minimal
60 | components: clippy
61 | - name: Run cargo xtask init
62 | uses: actions-rs/cargo@v1
63 | with:
64 | command: xtask
65 | args: init --with-corpus
66 | - name: Run cargo clippy
67 | uses: actions-rs/clippy-check@v1
68 | with:
69 | token: ${{ secrets.GITHUB_TOKEN }}
70 | args: --all-targets --examples --bins --lib --tests -- -D warnings
71 |
72 | # build the documentation
73 | cargo-docs:
74 | name: Run cargo docs
75 | needs: [skip-commit]
76 | runs-on: ubuntu-latest
77 | env:
78 | RUST_TOOLCHAIN: nightly
79 | steps:
80 | - uses: actions/checkout@v2
81 | - name: Install Rust toolchain
82 | uses: actions-rs/toolchain@v1
83 | with:
84 | profile: minimal
85 | toolchain: ${{ env.RUST_TOOLCHAIN }}
86 | override: true
87 | - name: Run cargo xtask init
88 | uses: actions-rs/cargo@v1
89 | with:
90 | command: xtask
91 | args: init
92 | - name: Run cargo xtask doc
93 | uses: actions-rs/cargo@v1
94 | with:
95 | command: xtask
96 | args: doc -- --no-deps --package wasm-lsp-server
97 | - uses: peaceiris/actions-gh-pages@v3
98 | with:
99 | github_token: ${{ secrets.GITHUB_TOKEN }}
100 | publish_dir: ./target/doc
101 | if: github.event_name == 'push' && github.ref == 'refs/heads/main'
102 |
103 | # verify that code is formatted
104 | cargo-fmt:
105 | name: Run cargo fmt
106 | needs: [skip-commit]
107 | runs-on: ubuntu-latest
108 | env:
109 | RUST_TOOLCHAIN: nightly
110 | steps:
111 | - uses: actions/checkout@v2
112 | - name: Install Rust toolchain
113 | uses: actions-rs/toolchain@v1
114 | with:
115 | profile: minimal
116 | toolchain: ${{ env.RUST_TOOLCHAIN }}
117 | override: true
118 | components: rustfmt
119 | - name: Run cargo fmt
120 | uses: actions-rs/cargo@v1
121 | with:
122 | toolchain: ${{ env.RUST_TOOLCHAIN }}
123 | command: fmt
124 | args: --all -- --check
125 |
126 | # verify that tests pass and calculate coverage with tarpaulin
127 | cargo-test-coverage:
128 | name: Run cargo tarpaulin
129 | needs: [skip-commit]
130 | runs-on: ubuntu-latest
131 | env:
132 | RUST_TOOLCHAIN: nightly
133 | steps:
134 | - uses: actions/checkout@v2
135 | - name: Install Rust toolchain
136 | uses: actions-rs/toolchain@v1
137 | with:
138 | profile: minimal
139 | toolchain: ${{ env.RUST_TOOLCHAIN }}
140 | override: true
141 | - name: Run cargo xtask init
142 | uses: actions-rs/cargo@v1
143 | with:
144 | command: xtask
145 | args: init --with-corpus
146 | - name: Fetch latest release version of cargo-tarpaulin
147 | run: |
148 | mkdir -p .github/caching
149 | curl -sL https://api.github.com/repos/xd009642/tarpaulin/releases/latest | jq -r '.name' > .github/caching/cargo-tarpaulin.lock
150 | - name: Cache cargo-tarpaulin/bin
151 | id: cache-cargo-tarpaulin
152 | uses: actions/cache@v1
153 | with:
154 | path: ${{ runner.tool_cache }}/cargo-tarpaulin/bin
155 | key: cargo-tarpaulin-bin-${{ hashFiles('.github/caching/cargo-tarpaulin.lock') }}
156 | - name: Install cargo-tarpaulin
157 | if: "steps.cache-cargo-tarpaulin.outputs.cache-hit != 'true'"
158 | uses: actions-rs/cargo@v1
159 | with:
160 | command: install
161 | args: --root ${{ runner.tool_cache }}/cargo-tarpaulin --force cargo-tarpaulin
162 | - run: echo "${{ runner.tool_cache }}/cargo-tarpaulin/bin" >> $GITHUB_PATH
163 | - name: Run cargo xtask tarpaulin
164 | uses: actions-rs/cargo@v1
165 | with:
166 | command: xtask
167 | args: tarpaulin
168 | - name: Upload to codecov.io
169 | uses: codecov/codecov-action@v1
170 | with:
171 | token: ${{ secrets.CODECOV_TOKEN }}
172 | fail_ci_if_error: true
173 |
174 | # verify that tests pass
175 | cargo-test:
176 | name: Run cargo test
177 | needs: [skip-commit]
178 | strategy:
179 | matrix:
180 | os: [macos-latest, ubuntu-latest, windows-latest]
181 | runs-on: ${{ matrix.os }}
182 | steps:
183 | - uses: actions/checkout@v2
184 | - name: Install Rust toolchain
185 | uses: actions-rs/toolchain@v1
186 | with:
187 | toolchain: nightly
188 | profile: minimal
189 | - name: Run cargo xtask init
190 | uses: actions-rs/cargo@v1
191 | with:
192 | command: xtask
193 | args: init --with-corpus
194 | - name: Run cargo xtask test
195 | uses: actions-rs/cargo@v1
196 | with:
197 | command: xtask
198 | args: test
199 | - name: Run cargo xtask test-cli
200 | uses: actions-rs/cargo@v1
201 | with:
202 | command: xtask
203 | args: test-cli
204 |
205 | # verify that tests pass for wasm target
206 | cargo-test-wasm:
207 | name: Run cargo test (wasm)
208 | needs: [skip-commit]
209 | runs-on: ubuntu-latest
210 | env:
211 | WASM_BINDGEN_CLI_VERSION: "0.2.81"
212 | steps:
213 | - uses: actions/checkout@v2
214 | - name: Install Rust toolchain (wasm32-unknown-unknown)
215 | uses: actions-rs/toolchain@v1
216 | with:
217 | target: wasm32-unknown-unknown
218 | toolchain: stable
219 | profile: minimal
220 | override: true
221 | - name: Run cargo xtask init
222 | uses: actions-rs/cargo@v1
223 | with:
224 | command: xtask
225 | args: init --with-corpus
226 | - name: Cache wasm-bindgen-cli/bin
227 | id: cache-wasm-bindgen-cli
228 | uses: actions/cache@v1
229 | with:
230 | path: ${{ runner.tool_cache }}/wasm-bindgen-cli/bin
231 | key: wasm-bindgen-cli-bin-${{ hashFiles('.github/caching/wasm-bindgen-cli.lock') }}
232 | - name: Install Rust toolchain (native)
233 | if: "steps.cache-wasm-bindgen-cli.outputs.cache-hit != 'true'"
234 | uses: actions-rs/toolchain@v1
235 | with:
236 | toolchain: stable
237 | profile: minimal
238 | - name: Install wasm-bindgen-cli
239 | if: "steps.cache-wasm-bindgen-cli.outputs.cache-hit != 'true'"
240 | uses: actions-rs/cargo@v1
241 | with:
242 | command: install
243 | args: --root ${{ runner.tool_cache }}/wasm-bindgen-cli --force wasm-bindgen-cli --version ${{ env.WASM_BINDGEN_CLI_VERSION }}
244 | - name: Configure $PATH for wasm-bindgen-cli
245 | run: echo "${{ runner.tool_cache }}/wasm-bindgen-cli/bin" >> $GITHUB_PATH
246 | - name: Run cargo xtask test
247 | uses: actions-rs/cargo@v1
248 | with:
249 | command: xtask
250 | args: test -- --target wasm32-unknown-unknown
251 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | tags:
8 | - 'v*'
9 |
10 | jobs:
11 | create_release:
12 | name: Create release entry
13 | if: startsWith(github.ref, 'refs/tags')
14 | runs-on: ubuntu-latest
15 | outputs:
16 | upload_url: ${{ steps.create_release.outputs.upload_url }}
17 | steps:
18 | - name: Create GitHub release entry
19 | id: create_release
20 | uses: actions/create-release@v1
21 | env:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 | with:
24 | tag_name: ${{ github.ref }}
25 | release_name: Release ${{ github.ref }}
26 | draft: false
27 | prerelease: false
28 |
29 | deploy_release:
30 | name: Deploy release asset for ${{ matrix.os }}
31 | needs: create_release
32 | if: startsWith(github.ref, 'refs/tags')
33 | runs-on: ${{ matrix.os }}
34 | strategy:
35 | matrix:
36 | name: [linux, windows, macos]
37 | include:
38 | - name: linux
39 | os: ubuntu-latest
40 | artifact_name: wasm-lsp
41 | asset_name: wasm-lsp-linux
42 | - name: windows
43 | os: windows-latest
44 | artifact_name: wasm-lsp.exe
45 | asset_name: wasm-lsp-windows
46 | - name: macos
47 | os: macos-latest
48 | artifact_name: wasm-lsp
49 | asset_name: wasm-lsp-macos
50 | steps:
51 | - uses: actions/checkout@v2
52 | - name: Install Rust toolchain
53 | uses: actions-rs/toolchain@v1
54 | with:
55 | profile: minimal
56 | - name: Run cargo xtask init
57 | uses: actions-rs/cargo@v1
58 | with:
59 | command: xtask
60 | args: init --with-corpus
61 | - name: Run cargo xtask build
62 | uses: actions-rs/cargo@v1
63 | with:
64 | command: xtask
65 | args: build -- --release
66 | - name: Create release asset
67 | run: Compress-Archive -DestinationPath ${{ matrix.asset_name }}.zip -Path target/release/${{ matrix.artifact_name }}
68 | shell: pwsh
69 | - name: Deploy release asset to GitHub
70 | id: upload-release-asset
71 | uses: actions/upload-release-asset@v1
72 | env:
73 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
74 | with:
75 | upload_url: ${{ needs.create_release.outputs.upload_url }}
76 | asset_path: ./${{ matrix.asset_name }}.zip
77 | asset_name: ${{ matrix.asset_name }}.zip
78 | asset_content_type: application/zip
79 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "vendor/tree-sitter-wasm"]
2 | path = vendor/tree-sitter-wasm
3 | url = https://github.com/wasm-lsp/tree-sitter-wasm
4 | [submodule "vendor/corpus"]
5 | path = vendor/corpus
6 | url = https://github.com/wasm-lsp/corpus
7 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | This project is governed by the Contributor Covenant version 2.0 (https://www.contributor-covenant.org/version/2/0/code_of_conduct).
2 |
3 | All contributors and participants agree to abide by its terms.
4 |
5 | To report violations, send an email to darinmorrison+conduct@gmail.com.
6 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [profile.release]
2 | codegen-units = 1
3 | lto = "fat"
4 | opt-level = "z"
5 |
6 | [workspace]
7 | members = [
8 | "crates/browser",
9 | "crates/cli",
10 | "crates/languages",
11 | "crates/macros",
12 | "crates/server",
13 | "crates/syntax",
14 | "crates/testing",
15 | "fuzz",
16 | "xtask",
17 | ]
18 | default-members = ["crates/cli"]
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
wasm-lsp-server
3 |
4 | A language server implementation for WebAssembly
5 |
6 |
7 |
10 |
13 |
16 |
17 |
18 |
19 | ## Status
20 |
21 | The server is still in an early state. It is usable but many advanced features have not yet been implemented.
22 |
23 | ## Usage
24 |
25 | The server has not yet had a stable release. You can build and install it locally if you would like to experiment with it in the meantime.
26 |
27 | ### Installing the Server
28 |
29 | #### Prebuilt Binaries
30 |
31 | The easiest way to install the server is to grab one of the prebuilt binaries under [releases](https://github.com/wasm-lsp/wasm-lsp-server/releases).
32 |
33 | #### Building from Source
34 |
35 | First ensure you have the [rust toolchain](https://rustup.rs/) installed, then proceed as follows:
36 |
37 | ```bash
38 | git clone https://github.com/wasm-lsp/wasm-lsp-server
39 | cd wasm-lsp-server
40 | cargo xtask init
41 | cargo xtask install
42 | ```
43 |
44 | ##### Selecting the Async Runtime
45 |
46 | The server is runtime agnostic and can be configured to run on [`async-std`](https://github.com/async-rs/async-std), [`futures`](https://github.com/rust-lang/futures-rs), [`smol`](https://github.com/smol-rs/smol), or [`tokio`](https://github.com/tokio-rs/tokio).
47 |
48 | The table below describes how to select a runtime. The `tokio` runtime is selected by default.
49 |
50 | | runtime | command |
51 | | ----------- | ----------------------------------------- |
52 | | `async-std` | `cargo xtask install --runtime=async-std` |
53 | | `futures` | `cargo xtask install --runtime=futures` |
54 | | `smol` | `cargo xtask install --runtime=smol` |
55 | | `tokio` | `cargo xtask install --runtime=tokio` |
56 |
57 | ### Installing the Client Extension
58 |
59 | Once the server is installed you can install the Visual Studio Code [client extension](https://github.com/wasm-lsp/vscode-wasm).
60 |
61 | ## Supported Document Types
62 |
63 | | extension | supported | kind |
64 | | :-------: | --------- | ---------------------------------------------------------------------------------------------------------------- |
65 | | `.wat` | ☑ | [WebAssembly module definition](https://github.com/WebAssembly/spec/tree/master/interpreter#s-expression-syntax) |
66 | | `.wast` | ☑ | [WebAssembly script](https://github.com/WebAssembly/spec/tree/master/interpreter#scripts) |
67 |
68 | ## Supported WebAssembly Proposals
69 |
70 | The server also supports parsing WebAssembly modules that use the following features:
71 |
72 | #### Phase 4 (Standardization)
73 |
74 | - ☑ [bulk-memory-operations](https://github.com/WebAssembly/bulk-memory-operations)
75 | - ☑ [reference-types](https://github.com/WebAssembly/reference-types)
76 |
77 | #### Phase 3 (Implementation)
78 |
79 | - ☑ [annotations](https://github.com/WebAssembly/annotations)
80 | - ☑ [multi-memory](https://github.com/WebAssembly/multi-memory)
81 | - ☑ [simd](https://github.com/WebAssembly/simd)
82 |
83 | #### Phase 2 (Specification)
84 |
85 | - ☑ [exception-handling](https://github.com/WebAssembly/exception-handling)
86 | - ☑ [function-references](https://github.com/WebAssembly/function-references)
87 | - ☑ [threads](https://github.com/WebAssembly/threads)
88 |
89 | #### Phase 1 (Proposal)
90 |
91 | Nothing planned.
92 |
93 | #### Phase 0 (Pre-Proposal)
94 |
95 | Nothing planned.
96 |
97 | ## Language Server Feature Support
98 |
99 | - ☑ document parsing via [wasm tree-sitter grammars](https://github.com/wasm-lsp/tree-sitter-wasm)
100 | - ☑ document symbol provider
101 | - ☑ syntax error diagnostics provider
102 | - ☑ incremental document synchronization
103 |
104 | ## Language Server Feature Roadmap
105 |
106 | - ☐ code action provider
107 | - ☐ code lens provider
108 | - ☐ completion provider
109 | - ☐ definition provider
110 | - ☐ document formatting (full and ranged) provider
111 | - ☐ document highlight provider
112 | - ☐ hover provider
113 | - ☐ references provider
114 | - ☐ workspace symbol provider
115 | - ☐ semantic tokens provider
116 | - ☐ signature help provider
117 | - ☐ document validation
118 | - ☐ integration with existing wasm toolchains
119 | - ☐ implementation of the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
120 |
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wasm-lsp/wasm-lsp-server/35004c3b7f18704cf413d0e0a3c835d885f025c4/clippy.toml
--------------------------------------------------------------------------------
/crates/browser/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "wasm-lsp-browser"
4 | version = "0.0.0"
5 | authors = ["silvanshade "]
6 | license = "Apache-2.0 WITH LLVM-exception"
7 | readme = "README.md"
8 | repository = "https://github.com/wasm-lsp/wasm-lsp-server"
9 | keywords = []
10 | description = """
11 | A web browser interface for the WebAssembly language server.
12 | """
13 |
14 | [badges]
15 | maintenance = { status = "experimental" }
16 |
17 | [features]
18 | default = []
19 |
20 | [lib]
21 | crate-type = ["cdylib", "rlib"]
22 |
23 | [dependencies]
24 | console_error_panic_hook = "0.1.7"
25 | wasm-lsp-languages = { version = "0.0", path = "../languages" }
26 | wasm-lsp-server = { version = "0.0", path = "../server", default-features = false }
27 | futures = "0.3"
28 | js-sys = "0.3"
29 | tower-lsp = { version = "0.17", default-features = false, features = ["runtime-agnostic"] }
30 | tree-sitter = { version = "*", package = "tree-sitter-facade" }
31 | wasm-bindgen = "=0.2.81"
32 | wasm-bindgen-futures = { version = "0.4", features = ["futures-core-03-stream"] }
33 | wasm-streams = "0.2"
34 | web-tree-sitter-sys = "0.6"
35 |
36 | [dependencies.web-sys]
37 | version = "0.3"
38 | features = [
39 | "ReadableStream",
40 | "WritableStream",
41 | ]
42 |
--------------------------------------------------------------------------------
/crates/browser/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![deny(clippy::all)]
2 | #![deny(unsafe_code)]
3 |
4 | use futures::stream::TryStreamExt;
5 | use tower_lsp::{LspService, Server};
6 | use wasm_bindgen::{prelude::*, JsCast};
7 | use wasm_bindgen_futures::{stream::JsStream, JsFuture};
8 |
9 | #[wasm_bindgen]
10 | pub struct ServerConfig {
11 | into_server: js_sys::AsyncIterator,
12 | from_server: web_sys::WritableStream,
13 | }
14 |
15 | #[wasm_bindgen]
16 | impl ServerConfig {
17 | #[wasm_bindgen(constructor)]
18 | pub fn new(into_server: js_sys::AsyncIterator, from_server: web_sys::WritableStream) -> Self {
19 | Self {
20 | into_server,
21 | from_server,
22 | }
23 | }
24 | }
25 |
26 | // NOTE: we don't use web_sys::ReadableStream for input here because on the
27 | // browser side we need to use a ReadableByteStreamController to construct it
28 | // and so far only Chromium-based browsers support that functionality.
29 |
30 | // NOTE: input needs to be an AsyncIterator specifically
31 | #[wasm_bindgen]
32 | pub async fn serve(config: ServerConfig) -> Result<(), JsValue> {
33 | console_error_panic_hook::set_once();
34 |
35 | let ServerConfig {
36 | into_server,
37 | from_server,
38 | } = config;
39 |
40 | JsFuture::from(web_tree_sitter_sys::Parser::init())
41 | .await
42 | .expect("failed to initialize tree-sitter");
43 |
44 | #[rustfmt::skip]
45 | #[cfg(target_arch = "wasm32")]
46 | let languages = wasm_lsp_server::core::SessionLanguages {
47 | wast: wasm_lsp_languages::language::wast().await.unwrap(),
48 | wat : wasm_lsp_languages::language::wat ().await.unwrap(),
49 | };
50 |
51 | #[rustfmt::skip]
52 | #[cfg(not(target_arch = "wasm32"))]
53 | let languages = wasm_lsp_server::core::SessionLanguages {
54 | wast: wasm_lsp_languages::language::wast(),
55 | wat : wasm_lsp_languages::language::wat (),
56 | };
57 |
58 | let stdin = JsStream::from(into_server);
59 | let stdin = stdin
60 | .map_ok(|value| {
61 | value
62 | .dyn_into::()
63 | .expect("could not cast stream item to Uint8Array")
64 | .to_vec()
65 | })
66 | .map_err(|_err| std::io::Error::from(std::io::ErrorKind::Other))
67 | .into_async_read();
68 |
69 | let stdout = JsCast::unchecked_into::(from_server);
70 | let stdout = wasm_streams::WritableStream::from_raw(stdout);
71 | let stdout = stdout.try_into_async_write().map_err(|err| err.0)?;
72 |
73 | let (service, socket) = LspService::new(|client| wasm_lsp_server::Server::new(languages, client).unwrap());
74 | Server::new(stdin, stdout, socket).serve(service).await;
75 |
76 | Ok(())
77 | }
78 |
--------------------------------------------------------------------------------
/crates/cli/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "wasm-lsp-cli"
4 | version = "0.0.0"
5 | authors = ["silvanshade "]
6 | license = "Apache-2.0 WITH LLVM-exception"
7 | readme = "README.md"
8 | repository = "https://github.com/wasm-lsp/wasm-lsp-server"
9 | keywords = []
10 | description = """
11 | A command-line interface for the WebAssembly language server.
12 | """
13 |
14 | [badges]
15 | maintenance = { status = "experimental" }
16 |
17 | [[bin]]
18 | name = "wasm-lsp"
19 | path = "src/bin/main.rs"
20 |
21 | [features]
22 | default = []
23 |
24 | [build-dependencies]
25 | anyhow = "1.0"
26 | built = { version = "0.5", features = ["git2"] }
27 |
28 | [dependencies]
29 | anyhow = "1.0"
30 | blocking = "1.0"
31 | clap = "3.1"
32 | env_logger = "0.9"
33 | futures = "0.3"
34 | tower-lsp = { version = "0.17", default-features = false, features = ["runtime-agnostic"] }
35 | wasm-lsp-languages = { version = "0.0", path = "../languages" }
36 | wasm-lsp-server = { version = "0.0", path = "../server", default-features = false }
37 |
38 | [dev-dependencies]
39 | predicates = "2.1"
40 |
41 | [target.'cfg(any(target_os = "wasi", not(target_arch = "wasm32")))'.dev-dependencies]
42 | assert_cmd = "2.0"
43 |
--------------------------------------------------------------------------------
/crates/cli/src/bin/main.rs:
--------------------------------------------------------------------------------
1 | //! Command-line interface for the WebAssembly Language Server
2 |
3 | #![deny(clippy::all)]
4 | #![deny(missing_docs)]
5 | #![deny(unsafe_code)]
6 |
7 | use clap::Command;
8 | use tower_lsp::{LspService, Server};
9 |
10 | fn cli() {
11 | use wasm_lsp_server::metadata;
12 | Command::new(metadata::PKG_NAME)
13 | .author(metadata::PKG_AUTHORS)
14 | .version(metadata::PKG_VERSION)
15 | .about(metadata::PKG_DESCRIPTION)
16 | .get_matches();
17 | }
18 |
19 | fn main() -> anyhow::Result<()> {
20 | run()?;
21 | Ok(())
22 | }
23 |
24 | /// Run the server with the futures runtime.
25 | fn run() -> anyhow::Result<()> {
26 | env_logger::try_init()?;
27 | cli();
28 | futures::executor::block_on(async {
29 | #[rustfmt::skip]
30 | #[cfg(target_arch = "wasm32")]
31 | let languages = wasm_lsp_server::core::SessionLanguages {
32 | wast: wasm_lsp_languages::language::wast().await?,
33 | wat : wasm_lsp_languages::language::wat ().await?,
34 | };
35 | #[rustfmt::skip]
36 | #[cfg(not(target_arch = "wasm32"))]
37 | let languages = wasm_lsp_server::core::SessionLanguages {
38 | wast: wasm_lsp_languages::language::wast(),
39 | wat : wasm_lsp_languages::language::wat (),
40 | };
41 | let (service, socket) = LspService::new(|client| wasm_lsp_server::Server::new(languages, client).unwrap());
42 | let stdin = blocking::Unblock::new(std::io::stdin());
43 | let stdout = blocking::Unblock::new(std::io::stdout());
44 | Server::new(stdin, stdout, socket).serve(service).await;
45 | Ok(())
46 | })
47 | }
48 |
--------------------------------------------------------------------------------
/crates/languages/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "wasm-lsp-languages"
4 | version = "0.0.0"
5 | authors = ["silvanshade "]
6 | license = "Apache-2.0 WITH LLVM-exception"
7 | readme = "README.md"
8 | repository = "https://github.com/wasm-lsp/wasm-lsp-server"
9 | keywords = []
10 | description = """
11 | Tree-sitter languages for the WebAssembly language server.
12 | """
13 |
14 | [badges]
15 | maintenance = { status = "experimental" }
16 |
17 | [build-dependencies]
18 | anyhow = "1.0"
19 | cc = "1.0"
20 |
21 | [dependencies]
22 | anyhow = "1.0"
23 | thiserror = "1.0"
24 |
25 | [dependencies.tree-sitter]
26 | package = "tree-sitter-facade"
27 | version = "0.4"
28 |
29 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies.tree-sitter-sys]
30 | package = "tree-sitter"
31 | version = "0.20"
32 |
33 | [target.'cfg(target_arch = "wasm32")'.dependencies]
34 | futures = "0.3"
35 | wasm-bindgen = { version = "=0.2.81", features = ["strict-macro"] }
36 | wasm-bindgen-futures = "0.4"
37 | web-tree-sitter-sys = "0.6"
38 |
--------------------------------------------------------------------------------
/crates/languages/build.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | fn compile_tree_sitter_grammars() {
4 | let dir = Path::new("../../vendor/tree-sitter-wasm");
5 |
6 | println!("cargo:rerun-if-changed={:?}", dir.join("wast/src/parser.c"));
7 | let mut cc = cc::Build::new();
8 | cc.include(dir.join("wast/src"));
9 | cc.file(dir.join("wast/src/parser.c"));
10 | cc.compile("tree-sitter-wast");
11 |
12 | println!("cargo:rerun-if-changed={:?}", dir.join("wat/src/parser.c"));
13 | let mut cc = cc::Build::new();
14 | cc.include(dir.join("wat/src"));
15 | cc.file(dir.join("wat/src/parser.c"));
16 | cc.compile("tree-sitter-wat");
17 | }
18 |
19 | fn main() -> anyhow::Result<()> {
20 | if std::env::var("CARGO_CFG_TARGET_ARCH")? != "wasm32" {
21 | compile_tree_sitter_grammars();
22 | }
23 | Ok(())
24 | }
25 |
--------------------------------------------------------------------------------
/crates/languages/src/error.rs:
--------------------------------------------------------------------------------
1 | //! Functionality related to runtime errors.
2 |
3 | use std::path::PathBuf;
4 | use thiserror::Error;
5 |
6 | /// Runtime errors for the WebAssembly parsers.
7 | #[allow(clippy::enum_variant_names)]
8 | #[derive(Debug, Error)]
9 | pub enum Error {
10 | /// Error that occurs when parsing an invalid language-id string.
11 | #[error("InvalidLanguageId: {0}")]
12 | InvalidLanguageId(String),
13 | /// Error that occurs when [`std::ffi::OsStr::to_str`] returns `None`.
14 | #[error("OsStrToStrFailed")]
15 | OsStrToStrFailed,
16 | /// Error that occurs when [`std::path::Path::extension`] returns `None`.
17 | #[error("PathExtensionFailed: {0}")]
18 | PathExtensionFailed(PathBuf),
19 | }
20 |
--------------------------------------------------------------------------------
/crates/languages/src/language.rs:
--------------------------------------------------------------------------------
1 | use std::path::Path;
2 |
3 | /// Tree-sitter language for the `.wast` grammar.
4 | #[cfg(not(target_arch = "wasm32"))]
5 | pub fn wast() -> tree_sitter::Language {
6 | #[allow(unsafe_code)]
7 | let inner = unsafe { crate::tree_sitter_wast() };
8 | inner.into()
9 | }
10 |
11 | /// Tree-sitter language for the `.wast` grammar.
12 | #[cfg(target_arch = "wasm32")]
13 | pub async fn wast() -> anyhow::Result {
14 | use anyhow::anyhow;
15 | use wasm_bindgen::JsCast;
16 | use wasm_bindgen_futures::JsFuture;
17 | let bytes: &[u8] = include_bytes!("../../../vendor/tree-sitter-wasm/wast/tree-sitter-wast.wasm");
18 | let promise = web_tree_sitter_sys::Language::load_bytes(&bytes.into());
19 | let future = JsFuture::from(promise);
20 | let value = future
21 | .await
22 | .map_err(|_| anyhow!("failed to load tree-sitter-wast.wasm"))?;
23 | let inner = value.unchecked_into::();
24 | let result = inner.into();
25 | Ok(result)
26 | }
27 |
28 | /// Tree-sitter language for the `.wat` grammar.
29 | #[cfg(not(target_arch = "wasm32"))]
30 | pub fn wat() -> tree_sitter::Language {
31 | #[allow(unsafe_code)]
32 | let inner = unsafe { crate::tree_sitter_wat() };
33 | inner.into()
34 | }
35 |
36 | /// Tree-sitter language for the `.wat` grammar.
37 | #[cfg(target_arch = "wasm32")]
38 | pub async fn wat() -> anyhow::Result {
39 | use anyhow::anyhow;
40 | use wasm_bindgen::JsCast;
41 | use wasm_bindgen_futures::JsFuture;
42 | let bytes: &[u8] = include_bytes!("../../../vendor/tree-sitter-wasm/wat/tree-sitter-wat.wasm");
43 | let promise = web_tree_sitter_sys::Language::load_bytes(&bytes.into());
44 | let future = JsFuture::from(promise);
45 | let value = future
46 | .await
47 | .map_err(|_| anyhow!("failed to load tree-sitter-wat.wasm"))?;
48 | let inner = value.unchecked_into::();
49 | let result = inner.into();
50 | Ok(result)
51 | }
52 |
53 | /// Languages supported by the server.
54 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
55 | pub enum Language {
56 | /// The `.wast` language.
57 | Wast,
58 | /// The `.wat` language.
59 | Wat,
60 | }
61 |
62 | impl Language {
63 | /// Compute the language id string for the given language.
64 | pub fn id(&self) -> &str {
65 | match self {
66 | Language::Wast => "wasm.wast",
67 | Language::Wat => "wasm.wat",
68 | }
69 | }
70 | }
71 |
72 | impl TryFrom<&str> for Language {
73 | type Error = anyhow::Error;
74 |
75 | fn try_from(language_id: &str) -> anyhow::Result {
76 | use crate::error::Error;
77 | match language_id {
78 | "wasm.wast" => Ok(Language::Wast),
79 | "wasm.wat" => Ok(Language::Wat),
80 | _ => Err(Error::InvalidLanguageId(language_id.into()).into()),
81 | }
82 | }
83 | }
84 |
85 | impl TryFrom<&Path> for Language {
86 | type Error = anyhow::Error;
87 |
88 | fn try_from(path: &Path) -> anyhow::Result {
89 | use crate::error::Error;
90 | let file_ext = path
91 | .extension()
92 | .ok_or_else(|| Error::PathExtensionFailed(path.into()))?;
93 | let file_ext = file_ext.to_str().ok_or(Error::OsStrToStrFailed)?;
94 | let language_id = format!("wasm.{}", file_ext);
95 | Language::try_from(language_id.as_str())
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/crates/languages/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Tree-sitter languages for the WebAssembly language server.
2 |
3 | #![deny(clippy::all)]
4 | #![deny(missing_docs)]
5 | #![deny(unsafe_code)]
6 |
7 | #[cfg(not(target_arch = "wasm32"))]
8 | extern {
9 | #[allow(dead_code)]
10 | #[doc(hidden)]
11 | fn tree_sitter_wast() -> tree_sitter_sys::Language;
12 |
13 | #[allow(dead_code)]
14 | #[doc(hidden)]
15 | fn tree_sitter_wat() -> tree_sitter_sys::Language;
16 | }
17 |
18 | mod error;
19 |
20 | /// Functions for creating [`tree-sitter::Language`].
21 | pub mod language;
22 |
--------------------------------------------------------------------------------
/crates/macros/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "wasm-lsp-macros"
4 | version = "0.0.0"
5 | authors = ["silvanshade "]
6 | license = "Apache-2.0 WITH LLVM-exception"
7 | readme = "README.md"
8 | repository = "https://github.com/wasm-lsp/wasm-lsp-server"
9 | keywords = []
10 | description = """
11 | Internal macros for the WebAssembly language server implementation.
12 | """
13 |
14 | [badges]
15 | maintenance = { status = "experimental" }
16 |
17 | [lib]
18 | proc-macro = true
19 |
20 | [dependencies]
21 | anyhow = "1.0"
22 | glob = "0.3"
23 | heck = "0.4"
24 | proc-macro2 = "1.0"
25 | quote = "1.0"
26 | syn = "1.0"
27 | thiserror = "1.0"
28 | wasm-lsp-languages = { version = "0.0", path = "../languages" }
29 |
30 | [dependencies.tree-sitter-sys]
31 | package = "tree-sitter"
32 | version = "0.20"
33 |
--------------------------------------------------------------------------------
/crates/macros/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Macros for the WASM language server.
2 |
3 | #![deny(clippy::all)]
4 | #![deny(missing_docs)]
5 | #![deny(unsafe_code)]
6 |
7 | use glob::glob;
8 | use proc_macro::TokenStream;
9 | use quote::quote;
10 |
11 | mod corpus_tests {
12 | use syn::parse::{Parse, ParseStream};
13 |
14 | mod keyword {
15 | syn::custom_keyword!(corpus);
16 | syn::custom_keyword!(include);
17 | syn::custom_keyword!(exclude);
18 | syn::custom_keyword!(handler);
19 | }
20 |
21 | pub(crate) struct MacroInput {
22 | pub(crate) corpus: syn::Ident,
23 | pub(crate) include: String,
24 | pub(crate) exclude: Vec,
25 | pub(crate) handler: syn::Expr,
26 | }
27 |
28 | impl Parse for MacroInput {
29 | fn parse(input: ParseStream) -> syn::parse::Result {
30 | input.parse::()?;
31 | input.parse::()?;
32 | let corpus = input.parse()?;
33 | input.parse::()?;
34 |
35 | input.parse::()?;
36 | input.parse::()?;
37 | let include = input.parse::()?.value();
38 | input.parse::()?;
39 |
40 | let mut exclude = vec![];
41 | if input.peek(keyword::exclude) {
42 | input.parse::()?;
43 | input.parse::()?;
44 | let paths = {
45 | let content;
46 | syn::bracketed!(content in input);
47 | content.parse_terminated::(|b| b.parse())?
48 | };
49 | exclude = paths.into_iter().map(|s| s.value()).collect();
50 | input.parse::()?;
51 | }
52 |
53 | input.parse::()?;
54 | input.parse::()?;
55 | let handler = input.parse()?;
56 | input.parse::().ok();
57 |
58 | Ok(MacroInput {
59 | corpus,
60 | include,
61 | exclude,
62 | handler,
63 | })
64 | }
65 | }
66 | }
67 |
68 | /// Generate tests from a corpus of wasm modules on the filesystem.
69 | ///
70 | /// # Arguments
71 | ///
72 | /// * `corpus` - name of the generated submodule containing the individual tests
73 | /// * `include` - glob pattern of files to include for testing
74 | /// * `exclude` - array of file names to exclude from testing
75 | ///
76 | /// # Examples
77 | ///
78 | /// ```
79 | /// corpus_tests! {
80 | /// corpus: annotations,
81 | /// include: "vendor/corpus/vendor/WebAssembly/annotations/test/core/*.wast",
82 | /// exclude: ["annotations.wast"],
83 | /// }
84 | /// ```
85 | #[proc_macro]
86 | pub fn corpus_tests(input: TokenStream) -> TokenStream {
87 | let corpus_tests::MacroInput {
88 | corpus,
89 | include,
90 | exclude,
91 | handler,
92 | } = syn::parse_macro_input!(input as corpus_tests::MacroInput);
93 | // Compute a string representation for the corpus name.
94 | let corpus_name = corpus.to_string();
95 | let corpus_name = corpus_name.as_str();
96 |
97 | // Compute the paths from the glob pattern.
98 | let paths = glob(&include).unwrap();
99 |
100 | // Prepare the vector of syntax items; these items are the individual test
101 | // functions that will be enclosed in the generated test submodule.
102 | let mut content = vec![];
103 |
104 | for path in paths {
105 | // Ensure the path is canonicalized and absolute
106 | let path = path.unwrap().canonicalize().unwrap();
107 | let path_name = path.to_str().unwrap();
108 | let file_name = path.file_name().unwrap().to_str().unwrap();
109 |
110 | // Skip the file if contained in the exclude list; otherwise continue.
111 | if !exclude.contains(&String::from(file_name)) {
112 | let file_stem = path.file_stem().unwrap().to_str().unwrap();
113 | let test_name = heck::ToSnakeCase::to_snake_case(file_stem);
114 | let test_name = format!("r#{}", test_name);
115 |
116 | // Compute the test identifier.
117 | let test = syn::parse_str::(&test_name).unwrap();
118 |
119 | // Generate the individual test function for the given file.
120 | let item = quote! {
121 | #[test]
122 | fn #test() {
123 | #handler(#corpus_name, #path_name);
124 | }
125 | };
126 | content.push(item);
127 | }
128 | }
129 |
130 | // Generate the enclosing test submodule for the given corpus.
131 | let module = quote! {
132 | mod #corpus {
133 | // Include the test functions generated from the corpus files.
134 | #(#content)*
135 | }
136 | };
137 |
138 | module.into()
139 | }
140 |
141 | mod language {
142 | use syn::parse::{Parse, ParseStream};
143 | use wasm_lsp_languages::language;
144 |
145 | pub(crate) struct Language(pub(crate) language::Language);
146 |
147 | impl Parse for Language {
148 | fn parse(input: ParseStream) -> syn::parse::Result {
149 | let language = input.parse::()?.value();
150 | let language = language::Language::try_from(language.as_str());
151 | let language = language.map_err(|_| input.error("invalid language identifier"))?;
152 | Ok(Language(language))
153 | }
154 | }
155 | }
156 |
157 | mod field_ids {
158 | use syn::parse::{Parse, ParseStream};
159 |
160 | mod keyword {
161 | syn::custom_keyword!(language);
162 | syn::custom_keyword!(fields);
163 | }
164 |
165 | pub(crate) struct Field {
166 | pub(crate) ident: syn::Ident,
167 | pub(crate) name: String,
168 | }
169 |
170 | impl Parse for Field {
171 | fn parse(input: ParseStream) -> syn::parse::Result {
172 | let content;
173 | syn::parenthesized!(content in input);
174 | let ident = content.parse()?;
175 | content.parse::()?;
176 | let name = content.parse::()?.value();
177 | Ok(Field { ident, name })
178 | }
179 | }
180 |
181 | pub(crate) struct MacroInput {
182 | pub(crate) language: super::language::Language,
183 | pub(crate) fields: Vec,
184 | }
185 |
186 | impl Parse for MacroInput {
187 | fn parse(input: ParseStream) -> syn::parse::Result {
188 | input.parse::()?;
189 | input.parse::()?;
190 | let language = input.parse()?;
191 | input.parse::()?;
192 |
193 | input.parse::()?;
194 | input.parse::()?;
195 | let fields = {
196 | let content;
197 | syn::bracketed!(content in input);
198 | content
199 | .parse_terminated::(|b| b.parse())?
200 | .into_iter()
201 | .collect()
202 | };
203 | input.parse::().ok();
204 |
205 | Ok(MacroInput { language, fields })
206 | }
207 | }
208 | }
209 |
210 | #[allow(missing_docs)]
211 | #[proc_macro]
212 | pub fn field_ids(input: TokenStream) -> TokenStream {
213 | use wasm_lsp_languages::language;
214 |
215 | let macro_input = syn::parse_macro_input!(input as field_ids::MacroInput);
216 |
217 | #[allow(unsafe_code)]
218 | let language = match macro_input.language.0 {
219 | language::Language::Wast => language::wast(),
220 | language::Language::Wat => language::wat(),
221 | };
222 |
223 | let mut content = vec![];
224 |
225 | for field in macro_input.fields {
226 | let ident = field.ident;
227 | let name = field.name.as_str();
228 | let value = language.field_id_for_name(name).unwrap();
229 | let item = quote! {
230 | pub const #ident: u16 = #value;
231 | };
232 | content.push(item);
233 | }
234 |
235 | let result = quote! {
236 | #(#content)*
237 | };
238 |
239 | result.into()
240 | }
241 |
242 | mod node_kind_ids {
243 | use syn::parse::{Parse, ParseStream};
244 |
245 | mod keyword {
246 | syn::custom_keyword!(language);
247 | syn::custom_keyword!(node_kinds);
248 | }
249 |
250 | pub(crate) struct NodeKind {
251 | pub(crate) ident: syn::Ident,
252 | pub(crate) kind: String,
253 | pub(crate) named: bool,
254 | }
255 |
256 | impl Parse for NodeKind {
257 | fn parse(input: ParseStream) -> syn::parse::Result {
258 | let content;
259 | syn::parenthesized!(content in input);
260 | let ident = content.parse()?;
261 | content.parse::()?;
262 | let kind = content.parse::()?.value();
263 | content.parse::()?;
264 | let named = content.parse::()?.value();
265 | Ok(NodeKind { ident, kind, named })
266 | }
267 | }
268 |
269 | pub(crate) struct MacroInput {
270 | pub(crate) language: super::language::Language,
271 | pub(crate) node_kinds: Vec,
272 | }
273 |
274 | impl Parse for MacroInput {
275 | fn parse(input: ParseStream) -> syn::parse::Result {
276 | input.parse::()?;
277 | input.parse::()?;
278 | let language = input.parse()?;
279 | input.parse::()?;
280 |
281 | input.parse::()?;
282 | input.parse::()?;
283 | let node_kinds = {
284 | let content;
285 | syn::bracketed!(content in input);
286 | content
287 | .parse_terminated::(|b| b.parse())?
288 | .into_iter()
289 | .collect()
290 | };
291 | input.parse::().ok();
292 |
293 | Ok(MacroInput { language, node_kinds })
294 | }
295 | }
296 | }
297 |
298 | #[allow(missing_docs)]
299 | #[proc_macro]
300 | pub fn node_kind_ids(input: TokenStream) -> TokenStream {
301 | use wasm_lsp_languages::language;
302 |
303 | let macro_input = syn::parse_macro_input!(input as node_kind_ids::MacroInput);
304 |
305 | #[allow(unsafe_code)]
306 | let language = match macro_input.language.0 {
307 | language::Language::Wast => language::wast(),
308 | language::Language::Wat => language::wat(),
309 | };
310 |
311 | let mut content = vec![];
312 |
313 | for node_kind in macro_input.node_kinds {
314 | let ident = node_kind.ident;
315 | let kind = node_kind.kind.as_str();
316 | let value = language.id_for_node_kind(kind, node_kind.named);
317 | let item = quote! {
318 | pub const #ident: u16 = #value;
319 | };
320 | content.push(item);
321 | }
322 |
323 | let result = quote! {
324 | #(#content)*
325 | };
326 |
327 | result.into()
328 | }
329 |
--------------------------------------------------------------------------------
/crates/server/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | edition = "2021"
3 | name = "wasm-lsp-server"
4 | version = "0.0.0"
5 | authors = ["silvanshade "]
6 | license = "Apache-2.0 WITH LLVM-exception"
7 | readme = "README.md"
8 | repository = "https://github.com/wasm-lsp/wasm-lsp-server"
9 | keywords = []
10 | description = """
11 | A language server implementation for WebAssembly.
12 | """
13 |
14 | [badges]
15 | maintenance = { status = "experimental" }
16 |
17 | [features]
18 | default = []
19 | goldenfiles = []
20 |
21 | [build-dependencies]
22 | anyhow = "1.0"
23 | built = { version = "0.5", features = ["git2"] }
24 |
25 | [dependencies]
26 | anyhow = "1.0"
27 | async-lock = "2.3"
28 | async-trait = "0.1"
29 | bytes = "1.0"
30 | env_logger = "0.9"
31 | futures = "0.3"
32 | log = "0.4"
33 | lsp = { version = "0.93", package = "lsp-types" }
34 | lsp-text = { version = "0.5", features = ["tree-sitter"] }
35 | ropey = "1.2"
36 | serde_json = "1.0"
37 | thiserror = "1.0"
38 | tower-lsp = { version = "0.17", default-features = false, features = ["runtime-agnostic"] }
39 | wasm-lsp-languages = { version = "0.0", path = "../languages" }
40 | wasm-lsp-syntax = { version = "0.0", path = "../syntax" }
41 |
42 | [dependencies.tree-sitter]
43 | package = "tree-sitter-facade"
44 | version = "0.4"
45 |
46 | [dev-dependencies]
47 | criterion = "0.3"
48 | futures-test = "0.3"
49 | glob = "0.3"
50 | indoc = "1.0"
51 | testing = { package = "wasm-lsp-testing", version = "0.0", path = "../testing" }
52 | wasm-lsp-macros = { version = "0.0", path = "../macros" }
53 |
54 | [target.'cfg(any(target_os = "wasi", not(target_arch = "wasm32")))'.dev-dependencies]
55 |
56 | goldenfile = "1.0"
57 |
58 | [package.metadata.cargo-udeps.ignore]
59 | normal = ["env_logger"]
60 |
--------------------------------------------------------------------------------
/crates/server/build.rs:
--------------------------------------------------------------------------------
1 | fn collect_metadata() -> anyhow::Result<()> {
2 | built::write_built_file()?;
3 | Ok(())
4 | }
5 |
6 | fn main() -> anyhow::Result<()> {
7 | collect_metadata()?;
8 | Ok(())
9 | }
10 |
--------------------------------------------------------------------------------
/crates/server/src/core.rs:
--------------------------------------------------------------------------------
1 | //! Core definitions for server functionality.
2 |
3 | /// Definitions related to LSP documents.
4 | pub mod document;
5 |
6 | /// Definitions related to runtime errors.
7 | pub mod error;
8 |
9 | /// Definitions related to session hashmaps.
10 | pub mod map;
11 |
12 | /// Definitions related to lock structures for session hashmaps.
13 | pub mod lock;
14 |
15 | /// Definitions related to references for session hashmaps.
16 | pub mod reference;
17 |
18 | /// Definitions related to the LSP session.
19 | pub mod session;
20 |
21 | /// Definitions related to working with textual content.
22 | pub mod text;
23 |
24 | pub use document::*;
25 | pub use error::*;
26 | pub use lock::*;
27 | pub use map::*;
28 | pub use reference::*;
29 | pub use session::*;
30 | pub use text::*;
31 | pub use wasm_lsp_languages::language::Language;
32 | pub use wasm_lsp_syntax::{language, node, range};
33 |
--------------------------------------------------------------------------------
/crates/server/src/core/document.rs:
--------------------------------------------------------------------------------
1 | //! Definitions related to LSP documents.
2 |
3 | use crate::core::{self};
4 | use async_lock::Mutex;
5 | use lsp_text::{RopeExt, TextEdit};
6 | use std::sync::Arc;
7 |
8 | /// Documents for the LSP session.
9 | pub struct Document {
10 | /// The language-id of the document.
11 | pub language: core::Language,
12 | /// The textual content of the document.
13 | pub content: ropey::Rope,
14 | /// The active parser for the document.
15 | pub parser: tree_sitter::Parser,
16 | /// The current syntax tree for the document.
17 | pub tree: tree_sitter::Tree,
18 | }
19 |
20 | impl Document {
21 | /// Create a new [`Document`] given [`lsp::DidOpenTextDocumentParams`].
22 | pub fn open(
23 | session: Arc,
24 | params: lsp::DidOpenTextDocumentParams,
25 | ) -> anyhow::Result