Skip to content

Commit 04cb6c4

Browse files
Natalie Jamesonfacebook-github-bot
authored andcommitted
Allow fetching starlark: uris with new message type
Summary: Add a new JSONRPC message type to allow fetching file contents for Starlark `LspUrl`s from the LSP context. Reviewed By: bobyangyf Differential Revision: D38452373 fbshipit-source-id: c05fe6fc798a1bb823adf47a1aa848bcece74e28
1 parent 7b61237 commit 04cb6c4

File tree

3 files changed

+168
-4
lines changed

3 files changed

+168
-4
lines changed

starlark/bin/eval.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18+
use std::collections::HashMap;
1819
use std::fs;
1920
use std::io;
2021
use std::iter;
@@ -38,6 +39,10 @@ use starlark::lsp::server::ResolveLoadError;
3839
use starlark::lsp::server::StringLiteralResult;
3940
use starlark::syntax::AstModule;
4041
use starlark::syntax::Dialect;
42+
use starlark::values::docs::get_registered_docs;
43+
use starlark::values::docs::render_docs_as_code;
44+
use starlark::values::docs::Doc;
45+
use starlark::values::docs::DocItem;
4146

4247
#[derive(Debug)]
4348
pub(crate) enum ContextMode {
@@ -51,6 +56,7 @@ pub(crate) struct Context {
5156
pub(crate) print_non_none: bool,
5257
pub(crate) prelude: Vec<FrozenModule>,
5358
pub(crate) module: Option<Module>,
59+
pub(crate) builtin_docs: HashMap<LspUrl, String>,
5460
}
5561

5662
/// The outcome of evaluating (checking, parsing or running) given starlark code.
@@ -84,15 +90,36 @@ impl Context {
8490
} else {
8591
None
8692
};
93+
let mut builtins: HashMap<LspUrl, Vec<Doc>> = HashMap::new();
94+
for doc in get_registered_docs() {
95+
let uri = Self::url_for_doc(&doc);
96+
builtins.entry(uri).or_default().push(doc);
97+
}
98+
let builtin_docs = builtins
99+
.into_iter()
100+
.map(|(u, ds)| (u, render_docs_as_code(&ds).join("\n\n")))
101+
.collect();
87102

88103
Ok(Self {
89104
mode,
90105
print_non_none,
91106
prelude,
92107
module,
108+
builtin_docs,
93109
})
94110
}
95111

112+
fn url_for_doc(doc: &Doc) -> LspUrl {
113+
let url = match &doc.item {
114+
DocItem::Module(_) => Url::parse("starlark:/native/builtins.bzl").unwrap(),
115+
DocItem::Object(_) => {
116+
Url::parse(&format!("starlark:/native/builtins/{}.bzl", doc.id.name)).unwrap()
117+
}
118+
DocItem::Function(_) => Url::parse("starlark:/native/builtins.bzl").unwrap(),
119+
};
120+
LspUrl::try_from(url).unwrap()
121+
}
122+
96123
fn new_module(prelude: &[FrozenModule]) -> Module {
97124
let module = Module::new();
98125
for p in prelude {
@@ -271,6 +298,7 @@ impl LspContext for Context {
271298
},
272299
false => Err(LoadContentsError::NotAbsolute(uri.clone()).into()),
273300
},
301+
LspUrl::Starlark(_) => Ok(self.builtin_docs.get(uri).cloned()),
274302
_ => Err(LoadContentsError::WrongScheme("file://".to_owned(), uri.clone()).into()),
275303
}
276304
}

starlark/src/lsp/server.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,32 @@ use serde::Serializer;
6767

6868
use crate::analysis::DefinitionLocation;
6969
use crate::analysis::LspModule;
70+
use crate::lsp::server::LoadContentsError::WrongScheme;
7071
use crate::syntax::AstModule;
7172

73+
/// The request to get the file contents for a starlark: URI
74+
struct StarlarkFileContentsRequest {}
75+
76+
impl lsp_types::request::Request for StarlarkFileContentsRequest {
77+
type Params = StarlarkFileContentsParams;
78+
type Result = StarlarkFileContentsResponse;
79+
const METHOD: &'static str = "starlark/fileContents";
80+
}
81+
82+
/// Params to get the file contents for a starlark: URI.
83+
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
84+
#[serde(rename_all = "camelCase")] // camelCase to match idioms in LSP spec / typescript land.
85+
struct StarlarkFileContentsParams {
86+
uri: LspUrl,
87+
}
88+
89+
/// The contents of a starlark: URI if available.
90+
#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
91+
#[serde(rename_all = "camelCase")] // camelCase to match idioms in LSP spec / typescript land.
92+
struct StarlarkFileContentsResponse {
93+
contents: Option<String>,
94+
}
95+
7296
/// Errors that can happen when converting LspUrl and Url to/from each other.
7397
#[derive(thiserror::Error, Debug)]
7498
pub enum LspUrlError {
@@ -370,6 +394,18 @@ impl<T: LspContext> Backend<T> {
370394
self.send_response(new_response(id, self.find_definition(params)));
371395
}
372396

397+
/// Get the file contents of a starlark: URI.
398+
fn get_starlark_file_contents(&self, id: RequestId, params: StarlarkFileContentsParams) {
399+
let response: anyhow::Result<_> = match params.uri {
400+
LspUrl::Starlark(_) => self
401+
.context
402+
.get_load_contents(&params.uri)
403+
.map(|contents| StarlarkFileContentsResponse { contents }),
404+
_ => Err(WrongScheme("starlark:".to_owned(), params.uri).into()),
405+
};
406+
self.send_response(new_response(id, response));
407+
}
408+
373409
fn resolve_load_path(&self, path: &str, current_uri: &LspUrl) -> anyhow::Result<LspUrl> {
374410
match current_uri {
375411
LspUrl::File(_) => self.context.resolve_load(path, current_uri),
@@ -528,6 +564,8 @@ impl<T: LspContext> Backend<T> {
528564
// be handled client side.
529565
if let Some(params) = as_request::<GotoDefinition>(&req) {
530566
self.goto_definition(req.id, params);
567+
} else if let Some(params) = as_request::<StarlarkFileContentsRequest>(&req) {
568+
self.get_starlark_file_contents(req.id, params);
531569
} else if self.connection.handle_shutdown(&req)? {
532570
return Ok(());
533571
}
@@ -685,6 +723,10 @@ mod test {
685723
use crate::analysis::FixtureWithRanges;
686724
use crate::codemap::ResolvedSpan;
687725
use crate::lsp::server::LspServerSettings;
726+
use crate::lsp::server::LspUrl;
727+
use crate::lsp::server::StarlarkFileContentsParams;
728+
use crate::lsp::server::StarlarkFileContentsRequest;
729+
use crate::lsp::server::StarlarkFileContentsResponse;
688730
use crate::lsp::test::TestServer;
689731

690732
fn goto_definition_request(
@@ -1488,4 +1530,29 @@ mod test {
14881530
assert!(goto_definition_enabled);
14891531
Ok(())
14901532
}
1533+
1534+
#[test]
1535+
fn returns_starlark_file_contents() -> anyhow::Result<()> {
1536+
let mut server = TestServer::new()?;
1537+
1538+
let uri = LspUrl::try_from(Url::parse("starlark:/native/builtin.bzl")?)?;
1539+
let req = server.new_request::<StarlarkFileContentsRequest>(StarlarkFileContentsParams {
1540+
uri: uri.clone(),
1541+
});
1542+
let request_id = server.send_request(req)?;
1543+
let response = server.get_response::<StarlarkFileContentsResponse>(request_id)?;
1544+
assert_eq!(
1545+
server.docs_as_code(&uri).unwrap(),
1546+
response.contents.unwrap()
1547+
);
1548+
1549+
let req = server.new_request::<StarlarkFileContentsRequest>(StarlarkFileContentsParams {
1550+
uri: LspUrl::try_from(Url::parse("starlark:/native/not_builtin.bzl")?)?,
1551+
});
1552+
let request_id = server.send_request(req)?;
1553+
let response = server.get_response::<StarlarkFileContentsResponse>(request_id)?;
1554+
assert!(response.contents.is_none());
1555+
1556+
Ok(())
1557+
}
14911558
}

starlark/src/lsp/test.rs

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use std::collections::hash_map::Entry;
1919
use std::collections::HashMap;
2020
use std::collections::HashSet;
2121
use std::collections::VecDeque;
22+
use std::path::Path;
2223
use std::path::PathBuf;
2324
use std::sync::Arc;
2425
use std::sync::RwLock;
@@ -68,6 +69,12 @@ use crate::lsp::server::ResolveLoadError;
6869
use crate::lsp::server::StringLiteralResult;
6970
use crate::syntax::AstModule;
7071
use crate::syntax::Dialect;
72+
use crate::values::docs::render_docs_as_code;
73+
use crate::values::docs::Doc;
74+
use crate::values::docs::DocItem;
75+
use crate::values::docs::Function;
76+
use crate::values::docs::Identifier;
77+
use crate::values::docs::Location;
7178

7279
/// Get the path from a URL, trimming off things like the leading slash that gets
7380
/// appended in some windows test environments.
@@ -99,13 +106,14 @@ pub(crate) enum TestServerError {
99106
struct TestServerContext {
100107
file_contents: Arc<RwLock<HashMap<PathBuf, String>>>,
101108
dirs: Arc<RwLock<HashSet<PathBuf>>>,
109+
builtin_docs: Arc<HashMap<LspUrl, String>>,
102110
}
103111

104112
impl LspContext for TestServerContext {
105113
fn parse_file_with_contents(&self, uri: &LspUrl, content: String) -> LspEvalResult {
106114
match uri {
107-
LspUrl::File(uri) => {
108-
match AstModule::parse(&uri.to_string_lossy(), content, &Dialect::Extended) {
115+
LspUrl::File(path) | LspUrl::Starlark(path) => {
116+
match AstModule::parse(&path.to_string_lossy(), content, &Dialect::Extended) {
109117
Ok(ast) => {
110118
let diagnostics = ast.lint(None).into_map(|l| EvalMessage::from(l).into());
111119
LspEvalResult {
@@ -114,7 +122,7 @@ impl LspContext for TestServerContext {
114122
}
115123
}
116124
Err(e) => {
117-
let diagnostics = vec![EvalMessage::from_anyhow(uri, &e).into()];
125+
let diagnostics = vec![EvalMessage::from_anyhow(path, &e).into()];
118126
LspEvalResult {
119127
diagnostics,
120128
ast: None,
@@ -196,6 +204,7 @@ impl LspContext for TestServerContext {
196204
(false, _) => Err(LoadContentsError::NotAbsolute(uri.clone()).into()),
197205
}
198206
}
207+
LspUrl::Starlark(_) => Ok(self.builtin_docs.get(uri).cloned()),
199208
_ => Ok(None),
200209
}
201210
}
@@ -223,6 +232,8 @@ pub struct TestServer {
223232
dirs: Arc<RwLock<HashSet<PathBuf>>>,
224233
/// If it's been received, the response payload for initialization.
225234
initialize_response: Option<InitializeResult>,
235+
/// Documentation for built in symbols.
236+
builtin_docs: Arc<HashMap<LspUrl, String>>,
226237
}
227238

228239
impl Drop for TestServer {
@@ -255,6 +266,49 @@ impl Drop for TestServer {
255266
}
256267

257268
impl TestServer {
269+
pub(crate) fn docs_as_code(&self, uri: &LspUrl) -> Option<String> {
270+
self.builtin_docs.get(uri).cloned()
271+
}
272+
273+
/// A static set of "builtins" to use for testing
274+
fn testing_builtins(root: &Path) -> anyhow::Result<HashMap<LspUrl, Vec<Doc>>> {
275+
let prelude_path = root.join("dir/prelude.bzl");
276+
let ret = hashmap! {
277+
LspUrl::try_from(Url::parse("starlark:/native/builtin.bzl")?)? => vec![
278+
Doc {
279+
id: Identifier {
280+
name: "native_function1".to_owned(),
281+
location: None,
282+
},
283+
item: DocItem::Function(Function::default()),
284+
custom_attrs: Default::default(),
285+
},
286+
Doc {
287+
id: Identifier {
288+
name: "native_function2".to_owned(),
289+
location: None,
290+
},
291+
item: DocItem::Function(Function::default()),
292+
custom_attrs: Default::default(),
293+
},
294+
],
295+
LspUrl::try_from(Url::from_file_path(prelude_path).unwrap())? => vec![
296+
Doc {
297+
id: Identifier {
298+
name: "prelude_function".to_owned(),
299+
location: Some(Location {
300+
path: "//dir/prelude.bzl".to_owned(),
301+
position: None,
302+
}),
303+
},
304+
item: DocItem::Function(Function::default()),
305+
custom_attrs: Default::default(),
306+
},
307+
]
308+
};
309+
Ok(ret)
310+
}
311+
258312
/// Generate a new request ID
259313
fn next_request_id(&mut self) -> RequestId {
260314
self.req_counter += 1;
@@ -281,11 +335,25 @@ impl TestServer {
281335
pub(crate) fn new_with_settings(settings: Option<LspServerSettings>) -> anyhow::Result<Self> {
282336
let (server_connection, client_connection) = Connection::memory();
283337

284-
let file_contents = Arc::new(RwLock::new(HashMap::new()));
338+
let builtin_docs = Arc::new(
339+
Self::testing_builtins(&std::env::current_dir()?)?
340+
.into_iter()
341+
.map(|(u, ds)| (u, render_docs_as_code(&ds).join("\n\n")))
342+
.collect::<HashMap<_, _>>(),
343+
);
344+
let prelude_file_contents = builtin_docs
345+
.iter()
346+
.filter_map(|(u, d)| match u {
347+
LspUrl::File(p) => Some((p.clone(), d.clone())),
348+
_ => None,
349+
})
350+
.collect();
351+
let file_contents = Arc::new(RwLock::new(prelude_file_contents));
285352
let dirs = Arc::new(RwLock::new(HashSet::new()));
286353
let ctx = TestServerContext {
287354
file_contents: file_contents.dupe(),
288355
dirs: dirs.dupe(),
356+
builtin_docs: builtin_docs.dupe(),
289357
};
290358

291359
let server_thread = std::thread::spawn(|| {
@@ -305,6 +373,7 @@ impl TestServer {
305373
file_contents,
306374
dirs,
307375
initialize_response: None,
376+
builtin_docs,
308377
};
309378
ret.initialize(settings)
310379
}

0 commit comments

Comments
 (0)