feat(service): add "import-with-def" check

This commit is contained in:
Pig Fang 2025-12-23 15:43:36 +08:00
parent 9446f048bd
commit 4647b1937f
No known key found for this signature in database
GPG key ID: A8198F548DADA9E2
8 changed files with 359 additions and 1 deletions

View file

@ -0,0 +1,45 @@
use crate::{LanguageService, helpers, uri::InternUri};
use line_index::LineIndex;
use lspt::{Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Location, Union2};
use wat_syntax::{SyntaxKind, SyntaxNode};
const DIAGNOSTIC_CODE: &str = "import-with-def";
pub fn check(
service: &LanguageService,
uri: InternUri,
line_index: &LineIndex,
node: &SyntaxNode,
) -> Option<Diagnostic> {
let import = node.first_child_by_kind(&|kind| kind == SyntaxKind::IMPORT)?;
let first = node.first_child_by_kind(&|kind| {
!matches!(
kind,
SyntaxKind::EXPORT
| SyntaxKind::IMPORT
| SyntaxKind::TYPE_USE
| SyntaxKind::GLOBAL_TYPE
| SyntaxKind::MEMORY_TYPE
| SyntaxKind::TABLE_TYPE
)
})?;
let last = node.last_child()?;
Some(Diagnostic {
range: helpers::rowan_range_to_lsp_range(
line_index,
first.text_range().cover(last.text_range()),
),
severity: Some(DiagnosticSeverity::Error),
source: Some("wat".into()),
code: Some(Union2::B(DIAGNOSTIC_CODE.into())),
message: "imported item can't contain definition".into(),
related_information: Some(vec![DiagnosticRelatedInformation {
location: Location {
uri: uri.raw(service),
range: helpers::rowan_range_to_lsp_range(line_index, import.text_range()),
},
message: "import declared here".into(),
}]),
..Default::default()
})
}

View file

@ -12,6 +12,7 @@ mod elem_type;
mod immediates;
mod implicit_module;
mod import_occur;
mod import_with_def;
mod mem_type;
mod multi_modules;
mod mutated_immutable;
@ -105,6 +106,9 @@ pub fn check(service: &LanguageService, document: Document) -> Vec<Diagnostic> {
symbol_table,
&node,
);
if let Some(diagnostic) = import_with_def::check(service, uri, line_index, &node) {
diagnostics.push(diagnostic);
}
}
SyntaxKind::MODULE_FIELD_GLOBAL => {
typeck::check_global(
@ -128,6 +132,9 @@ pub fn check(service: &LanguageService, document: Document) -> Vec<Diagnostic> {
if let Some(diagnostic) = const_expr::check(line_index, &node) {
diagnostics.push(diagnostic);
}
if let Some(diagnostic) = import_with_def::check(service, uri, line_index, &node) {
diagnostics.push(diagnostic);
}
}
SyntaxKind::MODULE_FIELD_IMPORT => {
if let Some(diagnostic) = import_occur::check(line_index, &node) {
@ -194,6 +201,9 @@ pub fn check(service: &LanguageService, document: Document) -> Vec<Diagnostic> {
if let Some(diagnostic) = const_expr::check(line_index, &node) {
diagnostics.push(diagnostic);
}
if let Some(diagnostic) = import_with_def::check(service, uri, line_index, &node) {
diagnostics.push(diagnostic);
}
}
SyntaxKind::MODULE_FIELD_ELEM => {
if let Some(diagnostic) = elem_type::check(
@ -208,6 +218,11 @@ pub fn check(service: &LanguageService, document: Document) -> Vec<Diagnostic> {
diagnostics.push(diagnostic);
}
}
SyntaxKind::MODULE_FIELD_MEMORY => {
if let Some(diagnostic) = import_with_def::check(service, uri, line_index, &node) {
diagnostics.push(diagnostic);
}
}
SyntaxKind::MEMORY_TYPE => {
mem_type::check(&mut diagnostics, line_index, &node);
}
@ -241,7 +256,20 @@ pub fn check(service: &LanguageService, document: Document) -> Vec<Diagnostic> {
diagnostics.push(diagnostic);
}
}
SyntaxKind::MODULE_FIELD_TAG | SyntaxKind::EXTERN_TYPE_TAG => {
SyntaxKind::MODULE_FIELD_TAG => {
tag_type::check(
&mut diagnostics,
service,
document,
line_index,
symbol_table,
&node,
);
if let Some(diagnostic) = import_with_def::check(service, uri, line_index, &node) {
diagnostics.push(diagnostic);
}
}
SyntaxKind::EXTERN_TYPE_TAG => {
tag_type::check(
&mut diagnostics,
service,

View file

@ -0,0 +1,80 @@
use super::*;
use insta::assert_json_snapshot;
use wat_service::LanguageService;
#[test]
fn func() {
let uri = "untitled:test".to_string();
let source = r#"
(module
(func (import "" "a") (param i32) (result i32))
(func (import "" "b") (param i32) (result i32) (local i32) (local)))
"#;
let mut service = LanguageService::default();
service.commit(&uri, source.into());
calm(&mut service, &uri);
let response = service.pull_diagnostics(create_params(uri));
assert_json_snapshot!(response);
}
#[test]
fn global() {
let uri = "untitled:test".to_string();
let source = r#"
(module
(global (import "" "a") i32)
(global (import "" "b") i32
i32.const 0))
"#;
let mut service = LanguageService::default();
service.commit(&uri, source.into());
calm(&mut service, &uri);
let response = service.pull_diagnostics(create_params(uri));
assert_json_snapshot!(response);
}
#[test]
fn memory() {
let uri = "untitled:test".to_string();
let source = r#"
(module
(memory (import "" "a") 1)
(memory (import "" "b") (data)))
"#;
let mut service = LanguageService::default();
service.commit(&uri, source.into());
calm(&mut service, &uri);
let response = service.pull_diagnostics(create_params(uri));
assert_json_snapshot!(response);
}
#[test]
fn table() {
let uri = "untitled:test".to_string();
let source = r#"
(module
(table (import "" "a") 0 funcref)
(table (import "" "b") 0 funcref
ref.func 0)
(func))
"#;
let mut service = LanguageService::default();
service.commit(&uri, source.into());
calm(&mut service, &uri);
let response = service.pull_diagnostics(create_params(uri));
assert_json_snapshot!(response);
}
#[test]
fn tag() {
let uri = "untitled:test".to_string();
let source = r#"
(module
(tag (import "" "") (param i32)))
"#;
let mut service = LanguageService::default();
service.commit(&uri, source.into());
calm(&mut service, &uri);
let response = service.pull_diagnostics(create_params(uri));
assert!(response.items.is_empty());
}

View file

@ -14,6 +14,7 @@ mod elem_type;
mod immediates;
mod implicit_module;
mod import_occur;
mod import_with_def;
mod mem_type;
mod multi_modules;
mod mutated_immutable;

View file

@ -0,0 +1,43 @@
---
source: crates/service/tests/diagnostics/import_with_def.rs
expression: response
---
{
"kind": "full",
"items": [
{
"range": {
"start": {
"line": 3,
"character": 49
},
"end": {
"line": 3,
"character": 68
}
},
"severity": 1,
"code": "import-with-def",
"source": "wat",
"message": "imported item can't contain definition",
"relatedInformation": [
{
"location": {
"uri": "untitled:test",
"range": {
"start": {
"line": 3,
"character": 8
},
"end": {
"line": 3,
"character": 23
}
}
},
"message": "import declared here"
}
]
}
]
}

View file

@ -0,0 +1,59 @@
---
source: crates/service/tests/diagnostics/import_with_def.rs
expression: response
---
{
"kind": "full",
"items": [
{
"range": {
"start": {
"line": 4,
"character": 15
},
"end": {
"line": 4,
"character": 16
}
},
"severity": 1,
"code": "type-check",
"source": "wat",
"message": "expected types [i32], found [i32, i32] at the end"
},
{
"range": {
"start": {
"line": 4,
"character": 4
},
"end": {
"line": 4,
"character": 15
}
},
"severity": 1,
"code": "import-with-def",
"source": "wat",
"message": "imported item can't contain definition",
"relatedInformation": [
{
"location": {
"uri": "untitled:test",
"range": {
"start": {
"line": 3,
"character": 10
},
"end": {
"line": 3,
"character": 25
}
}
},
"message": "import declared here"
}
]
}
]
}

View file

@ -0,0 +1,43 @@
---
source: crates/service/tests/diagnostics/import_with_def.rs
expression: response
---
{
"kind": "full",
"items": [
{
"range": {
"start": {
"line": 3,
"character": 26
},
"end": {
"line": 3,
"character": 32
}
},
"severity": 1,
"code": "import-with-def",
"source": "wat",
"message": "imported item can't contain definition",
"relatedInformation": [
{
"location": {
"uri": "untitled:test",
"range": {
"start": {
"line": 3,
"character": 10
},
"end": {
"line": 3,
"character": 25
}
}
},
"message": "import declared here"
}
]
}
]
}

View file

@ -0,0 +1,59 @@
---
source: crates/service/tests/diagnostics/import_with_def.rs
expression: response
---
{
"kind": "full",
"items": [
{
"range": {
"start": {
"line": 4,
"character": 14
},
"end": {
"line": 4,
"character": 15
}
},
"severity": 1,
"code": "type-check",
"source": "wat",
"message": "expected types [(ref null func)], found [(ref null func), (func 0)] at the end"
},
{
"range": {
"start": {
"line": 4,
"character": 4
},
"end": {
"line": 4,
"character": 14
}
},
"severity": 1,
"code": "import-with-def",
"source": "wat",
"message": "imported item can't contain definition",
"relatedInformation": [
{
"location": {
"uri": "untitled:test",
"range": {
"start": {
"line": 3,
"character": 9
},
"end": {
"line": 3,
"character": 24
}
}
},
"message": "import declared here"
}
]
}
]
}