Skip to content

Commit c94a6da

Browse files
authored
fix: tree-sitter edit_from_change crash (#273)
* add repro * fix: ts edit from change * fix: lockfile
1 parent 4cbeb5d commit c94a6da

File tree

7 files changed

+276
-36
lines changed

7 files changed

+276
-36
lines changed

Cargo.lock

Lines changed: 44 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ strum = { version = "0.27.1", features = ["derive"] }
4444
sqlx = { version = "0.8.2", features = ["runtime-tokio", "runtime-async-std", "postgres", "json"] }
4545
syn = "1.0.109"
4646
termcolor = "1.4.1"
47+
test-log = "0.2.17"
4748
tokio = { version = "1.40.0", features = ["full"] }
4849
tower-lsp = "0.20.0"
4950
tracing = { version = "0.1.40", default-features = false, features = ["std"] }

crates/pgt_analyse/Cargo.toml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ version = "0.0.0"
1313

1414

1515
[dependencies]
16-
pgt_console.workspace = true
17-
pgt_diagnostics.workspace = true
18-
pgt_query_ext.workspace = true
19-
pgt_schema_cache.workspace = true
20-
rustc-hash = { workspace = true }
16+
pgt_console.workspace = true
17+
pgt_diagnostics.workspace = true
18+
pgt_query_ext.workspace = true
19+
rustc-hash = { workspace = true }
2120

2221
biome_deserialize = { workspace = true, optional = true }
2322
biome_deserialize_macros = { workspace = true, optional = true }

crates/pgt_lsp/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ tracing = { workspace = true, features = ["attributes"] }
3535
[dev-dependencies]
3636
pgt_test_utils = { workspace = true }
3737
sqlx = { workspace = true }
38+
test-log = { workspace = true }
3839
tokio = { workspace = true, features = ["macros"] }
3940
tower = { version = "0.4.13", features = ["timeout"] }
4041

crates/pgt_lsp/tests/server.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ use sqlx::Executor;
2323
use std::any::type_name;
2424
use std::fmt::Display;
2525
use std::time::Duration;
26+
use test_log::test;
2627
use tower::timeout::Timeout;
2728
use tower::{Service, ServiceExt};
2829
use tower_lsp::LspService;
@@ -558,6 +559,208 @@ async fn test_completions() -> Result<()> {
558559
Ok(())
559560
}
560561

562+
#[tokio::test]
563+
async fn test_issue_271() -> Result<()> {
564+
let factory = ServerFactory::default();
565+
let mut fs = MemoryFileSystem::default();
566+
let test_db = get_new_test_db().await;
567+
568+
let setup = r#"
569+
create table public.users (
570+
id serial primary key,
571+
name varchar(255) not null
572+
);
573+
"#;
574+
575+
test_db
576+
.execute(setup)
577+
.await
578+
.expect("Failed to setup test database");
579+
580+
let mut conf = PartialConfiguration::init();
581+
conf.merge_with(PartialConfiguration {
582+
db: Some(PartialDatabaseConfiguration {
583+
database: Some(
584+
test_db
585+
.connect_options()
586+
.get_database()
587+
.unwrap()
588+
.to_string(),
589+
),
590+
..Default::default()
591+
}),
592+
..Default::default()
593+
});
594+
fs.insert(
595+
url!("postgrestools.jsonc").to_file_path().unwrap(),
596+
serde_json::to_string_pretty(&conf).unwrap(),
597+
);
598+
599+
let (service, client) = factory
600+
.create_with_fs(None, DynRef::Owned(Box::new(fs)))
601+
.into_inner();
602+
603+
let (stream, sink) = client.split();
604+
let mut server = Server::new(service);
605+
606+
let (sender, _) = channel(CHANNEL_BUFFER_SIZE);
607+
let reader = tokio::spawn(client_handler(stream, sink, sender));
608+
609+
server.initialize().await?;
610+
server.initialized().await?;
611+
612+
server.load_configuration().await?;
613+
614+
server
615+
.open_document("CREATE COLLATION ignore_accent_case (provider = icu, deterministic = false, locale = 'und-u-ks-level1');\n\n-- CREATE OR REPLACE FUNCTION\n-- add_one(integer)\n-- RETURNS\n-- integer\n-- AS\n-- 'add_one.so', 'add_one'\n-- LANGUAGE\n-- C \n-- STRICT;\n\n\nSELECT pwhash, FROM users;")
616+
.await?;
617+
618+
server
619+
.change_document(
620+
3,
621+
vec![TextDocumentContentChangeEvent {
622+
range: Some(Range {
623+
start: Position {
624+
line: 13,
625+
character: 13,
626+
},
627+
end: Position {
628+
line: 13,
629+
character: 14,
630+
},
631+
}),
632+
range_length: Some(0),
633+
text: "".to_string(),
634+
}],
635+
)
636+
.await?;
637+
638+
server
639+
.change_document(
640+
1,
641+
vec![TextDocumentContentChangeEvent {
642+
range: Some(Range {
643+
start: Position {
644+
line: 13,
645+
character: 13,
646+
},
647+
end: Position {
648+
line: 13,
649+
character: 13,
650+
},
651+
}),
652+
range_length: Some(0),
653+
text: ",".to_string(),
654+
}],
655+
)
656+
.await?;
657+
658+
server
659+
.change_document(
660+
2,
661+
vec![TextDocumentContentChangeEvent {
662+
range: Some(Range {
663+
start: Position {
664+
line: 13,
665+
character: 14,
666+
},
667+
end: Position {
668+
line: 13,
669+
character: 14,
670+
},
671+
}),
672+
range_length: Some(0),
673+
text: " ".to_string(),
674+
}],
675+
)
676+
.await?;
677+
678+
server
679+
.change_document(
680+
3,
681+
vec![TextDocumentContentChangeEvent {
682+
range: Some(Range {
683+
start: Position {
684+
line: 13,
685+
character: 15,
686+
},
687+
end: Position {
688+
line: 13,
689+
character: 15,
690+
},
691+
}),
692+
range_length: Some(0),
693+
text: "county_name".to_string(),
694+
}],
695+
)
696+
.await?;
697+
698+
server
699+
.change_document(
700+
4,
701+
vec![TextDocumentContentChangeEvent {
702+
range: Some(Range {
703+
start: Position {
704+
line: 13,
705+
character: 13,
706+
},
707+
end: Position {
708+
line: 13,
709+
character: 26,
710+
},
711+
}),
712+
range_length: Some(13),
713+
text: "".to_string(),
714+
}],
715+
)
716+
.await?;
717+
718+
server
719+
.change_document(
720+
5,
721+
vec![TextDocumentContentChangeEvent {
722+
range: Some(Range {
723+
start: Position {
724+
line: 13,
725+
character: 13,
726+
},
727+
end: Position {
728+
line: 13,
729+
character: 13,
730+
},
731+
}),
732+
range_length: Some(0),
733+
text: ",".to_string(),
734+
}],
735+
)
736+
.await?;
737+
738+
// crashes with range end index 37 out of range for slice of length 26
739+
let res = server
740+
.get_completion(CompletionParams {
741+
work_done_progress_params: WorkDoneProgressParams::default(),
742+
partial_result_params: PartialResultParams::default(),
743+
context: None,
744+
text_document_position: TextDocumentPositionParams {
745+
text_document: TextDocumentIdentifier {
746+
uri: url!("document.sql"),
747+
},
748+
position: Position {
749+
line: 13,
750+
character: 14,
751+
},
752+
},
753+
})
754+
.await?;
755+
756+
assert!(res.is_some());
757+
758+
server.shutdown().await?;
759+
reader.abort();
760+
761+
Ok(())
762+
}
763+
561764
#[tokio::test]
562765
async fn test_execute_statement() -> Result<()> {
563766
let factory = ServerFactory::default();

crates/pgt_treesitter_queries/src/queries/relations.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use super::QueryTryFrom;
77
static TS_QUERY: LazyLock<tree_sitter::Query> = LazyLock::new(|| {
88
static QUERY_STR: &str = r#"
99
(relation
10-
(object_reference
10+
(object_reference
1111
.
1212
(identifier) @schema_or_table
1313
"."?
@@ -38,7 +38,7 @@ impl RelationMatch<'_> {
3838
pub fn get_table(&self, sql: &str) -> String {
3939
self.table
4040
.utf8_text(sql.as_bytes())
41-
.expect("Failed to get schema from RelationMatch")
41+
.expect("Failed to get table from RelationMatch")
4242
.to_string()
4343
}
4444
}

0 commit comments

Comments
 (0)