feat(lsp): hover and goto definition

This commit is contained in:
rvcas 2023-02-20 02:09:57 -05:00 committed by Lucas
parent 39ea803fe6
commit 815d7d80c6
11 changed files with 478 additions and 108 deletions

19
Cargo.lock generated vendored
View File

@ -60,7 +60,7 @@ dependencies = [
"clap", "clap",
"hex", "hex",
"ignore", "ignore",
"indoc", "indoc 1.0.8",
"miette", "miette",
"owo-colors", "owo-colors",
"pallas-addresses", "pallas-addresses",
@ -81,7 +81,7 @@ dependencies = [
"chumsky", "chumsky",
"hex", "hex",
"indexmap", "indexmap",
"indoc", "indoc 1.0.8",
"itertools", "itertools",
"miette", "miette",
"ordinal", "ordinal",
@ -100,6 +100,8 @@ dependencies = [
"aiken-lang", "aiken-lang",
"aiken-project", "aiken-project",
"crossbeam-channel", "crossbeam-channel",
"indoc 2.0.0",
"itertools",
"lsp-server", "lsp-server",
"lsp-types", "lsp-types",
"miette", "miette",
@ -108,6 +110,7 @@ dependencies = [
"thiserror", "thiserror",
"tracing", "tracing",
"url", "url",
"urlencoding",
] ]
[[package]] [[package]]
@ -1082,6 +1085,12 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780" checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780"
[[package]]
name = "indoc"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fe2b9d82064e8a0226fddb3547f37f28eaa46d0fc210e275d835f08cf3b76a7"
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -2670,6 +2679,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "urlencoding"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.0" version = "0.2.0"

View File

@ -66,6 +66,14 @@ impl UntypedModule {
} }
} }
impl TypedModule {
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
self.definitions
.iter()
.find_map(|definition| definition.find_node(byte_index))
}
}
pub type TypedFunction = Function<Arc<Type>, TypedExpr>; pub type TypedFunction = Function<Arc<Type>, TypedExpr>;
pub type UntypedFunction = Function<(), UntypedExpr>; pub type UntypedFunction = Function<(), UntypedExpr>;
@ -318,6 +326,48 @@ impl<A, B, C> Definition<A, B, C> {
} }
} }
impl TypedDefinition {
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
// Note that the fn span covers the function head, not
// the entire statement.
if let Definition::Fn(Function { body, .. })
| Definition::Validator(Validator {
fun: Function { body, .. },
..
})
| Definition::Test(Function { body, .. }) = self
{
if let Some(expression) = body.find_node(byte_index) {
return Some(Located::Expression(expression));
}
}
if self.location().contains(byte_index) {
Some(Located::Definition(self))
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Located<'a> {
Expression(&'a TypedExpr),
Definition(&'a TypedDefinition),
}
impl<'a> Located<'a> {
pub fn definition_location(&self) -> Option<DefinitionLocation<'_>> {
match self {
Self::Expression(expression) => expression.definition_location(),
Self::Definition(definition) => Some(DefinitionLocation {
module: None,
span: definition.location(),
}),
}
}
}
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct DefinitionLocation<'module> { pub struct DefinitionLocation<'module> {
pub module: Option<&'module str>, pub module: Option<&'module str>,
@ -379,6 +429,12 @@ impl CallArg<UntypedExpr> {
} }
} }
impl TypedCallArg {
pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> {
self.value.find_node(byte_index)
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct RecordConstructor<T> { pub struct RecordConstructor<T> {
pub location: Span, pub location: Span,
@ -816,6 +872,10 @@ impl TypedClause {
end: self.then.location().end, end: self.then.location().end,
} }
} }
pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> {
self.then.find_node(byte_index)
}
} }
pub type UntypedClauseGuard = ClauseGuard<()>; pub type UntypedClauseGuard = ClauseGuard<()>;
@ -946,7 +1006,7 @@ pub struct IfBranch<Expr> {
pub location: Span, pub location: Span,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct TypedRecordUpdateArg { pub struct TypedRecordUpdateArg {
pub label: String, pub label: String,
pub location: Span, pub location: Span,
@ -954,6 +1014,12 @@ pub struct TypedRecordUpdateArg {
pub index: usize, pub index: usize,
} }
impl TypedRecordUpdateArg {
pub fn find_node(&self, byte_index: usize) -> Option<&TypedExpr> {
self.value.find_node(byte_index)
}
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct UntypedRecordUpdateArg { pub struct UntypedRecordUpdateArg {
pub label: String, pub label: String,
@ -1023,6 +1089,10 @@ impl Span {
end: self.end().max(other.end()), end: self.end().max(other.end()),
} }
} }
pub fn contains(&self, byte_index: usize) -> bool {
byte_index >= self.start && byte_index < self.end
}
} }
impl fmt::Debug for Span { impl fmt::Debug for Span {

View File

@ -12,7 +12,7 @@ use crate::{
tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor}, tipo::{ModuleValueConstructor, PatternConstructor, Type, ValueConstructor},
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum TypedExpr { pub enum TypedExpr {
Int { Int {
location: Span, location: Span,
@ -307,6 +307,96 @@ impl TypedExpr {
| Self::RecordUpdate { location, .. } => *location, | Self::RecordUpdate { location, .. } => *location,
} }
} }
// This could be optimised in places to exit early if the first of a series
// of expressions is after the byte index.
pub fn find_node(&self, byte_index: usize) -> Option<&Self> {
if !self.location().contains(byte_index) {
return None;
}
match self {
TypedExpr::ErrorTerm { .. }
| TypedExpr::Var { .. }
| TypedExpr::Int { .. }
| TypedExpr::String { .. }
| TypedExpr::ByteArray { .. }
| TypedExpr::ModuleSelect { .. } => Some(self),
TypedExpr::Trace { text, then, .. } => text
.find_node(byte_index)
.or_else(|| then.find_node(byte_index))
.or(Some(self)),
TypedExpr::Pipeline { expressions, .. } | TypedExpr::Sequence { expressions, .. } => {
expressions.iter().find_map(|e| e.find_node(byte_index))
}
TypedExpr::Fn { body, .. } => body.find_node(byte_index).or(Some(self)),
TypedExpr::Tuple {
elems: elements, ..
}
| TypedExpr::List { elements, .. } => elements
.iter()
.find_map(|e| e.find_node(byte_index))
.or(Some(self)),
TypedExpr::Call { fun, args, .. } => args
.iter()
.find_map(|arg| arg.find_node(byte_index))
.or_else(|| fun.find_node(byte_index))
.or(Some(self)),
TypedExpr::BinOp { left, right, .. } => left
.find_node(byte_index)
.or_else(|| right.find_node(byte_index)),
TypedExpr::Assignment { value, .. } => value.find_node(byte_index),
TypedExpr::When {
subjects, clauses, ..
} => subjects
.iter()
.find_map(|subject| subject.find_node(byte_index))
.or_else(|| {
clauses
.iter()
.find_map(|clause| clause.find_node(byte_index))
})
.or(Some(self)),
TypedExpr::RecordAccess {
record: expression, ..
}
| TypedExpr::TupleIndex {
tuple: expression, ..
} => expression.find_node(byte_index).or(Some(self)),
TypedExpr::RecordUpdate { spread, args, .. } => args
.iter()
.find_map(|arg| arg.find_node(byte_index))
.or_else(|| spread.find_node(byte_index))
.or(Some(self)),
TypedExpr::If {
branches,
final_else,
..
} => branches
.iter()
.find_map(|branch| {
branch
.condition
.find_node(byte_index)
.or_else(|| branch.body.find_node(byte_index))
})
.or_else(|| final_else.find_node(byte_index))
.or(Some(self)),
TypedExpr::UnOp { value, .. } => value.find_node(byte_index).or(Some(self)),
}
}
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View File

@ -715,7 +715,7 @@ pub enum PatternConstructor {
}, },
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub enum ModuleValueConstructor { pub enum ModuleValueConstructor {
Record { Record {
name: String, name: String,

View File

@ -13,6 +13,8 @@ authors = ["Lucas Rosa <x@rvcas.dev>"]
aiken-lang = { path = '../aiken-lang', version = "0.0.28" } aiken-lang = { path = '../aiken-lang', version = "0.0.28" }
aiken-project = { path = '../aiken-project', version = "0.0.28" } aiken-project = { path = '../aiken-project', version = "0.0.28" }
crossbeam-channel = "0.5.6" crossbeam-channel = "0.5.6"
indoc = "2.0.0"
itertools = "0.10.5"
lsp-server = "0.6.0" lsp-server = "0.6.0"
lsp-types = "0.93.2" lsp-types = "0.93.2"
miette = "5.4.1" miette = "5.4.1"
@ -21,3 +23,4 @@ serde_json = "1.0.87"
thiserror = "1.0.37" thiserror = "1.0.37"
tracing = "0.1.37" tracing = "0.1.37"
url = "2.3.1" url = "2.3.1"
urlencoding = "2.1.2"

View File

@ -2,10 +2,6 @@ use std::env;
use aiken_project::{config::Config, paths}; use aiken_project::{config::Config, paths};
use lsp_server::Connection; use lsp_server::Connection;
use lsp_types::{
OneOf, SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind,
TextDocumentSyncOptions, TextDocumentSyncSaveOptions,
};
mod cast; mod cast;
pub mod error; pub mod error;
@ -53,21 +49,32 @@ pub fn start() -> Result<(), Error> {
Ok(()) Ok(())
} }
fn capabilities() -> ServerCapabilities { fn capabilities() -> lsp_types::ServerCapabilities {
ServerCapabilities { lsp_types::ServerCapabilities {
text_document_sync: Some(TextDocumentSyncCapability::Options( completion_provider: Some(lsp_types::CompletionOptions {
TextDocumentSyncOptions { resolve_provider: None,
trigger_characters: Some(vec![".".into(), " ".into()]),
all_commit_characters: None,
work_done_progress_options: lsp_types::WorkDoneProgressOptions {
work_done_progress: None,
},
}),
document_formatting_provider: Some(lsp_types::OneOf::Left(true)),
definition_provider: Some(lsp_types::OneOf::Left(true)),
hover_provider: Some(lsp_types::HoverProviderCapability::Simple(true)),
text_document_sync: Some(lsp_types::TextDocumentSyncCapability::Options(
lsp_types::TextDocumentSyncOptions {
open_close: None, open_close: None,
change: Some(TextDocumentSyncKind::FULL), change: Some(lsp_types::TextDocumentSyncKind::FULL),
will_save: None, will_save: None,
will_save_wait_until: None, will_save_wait_until: None,
save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions { save: Some(lsp_types::TextDocumentSyncSaveOptions::SaveOptions(
include_text: Some(false), lsp_types::SaveOptions {
})), include_text: Some(false),
},
)),
}, },
)), )),
// definition_provider: Some(OneOf::Left(true)),
document_formatting_provider: Some(OneOf::Left(true)),
..Default::default() ..Default::default()
} }
} }

View File

@ -4,18 +4,24 @@ use std::{
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
use aiken_lang::{ast::ModuleKind, parser}; use aiken_lang::{
ast::{Located, ModuleKind, Span},
parser,
tipo::pretty::Printer,
};
use aiken_project::{ use aiken_project::{
config, config,
error::{Error as ProjectError, GetSource}, error::{Error as ProjectError, GetSource},
module::CheckedModule,
}; };
use indoc::formatdoc;
use lsp_server::{Connection, Message}; use lsp_server::{Connection, Message};
use lsp_types::{ use lsp_types::{
notification::{ notification::{
DidChangeTextDocument, DidSaveTextDocument, Notification, Progress, PublishDiagnostics, DidChangeTextDocument, DidSaveTextDocument, Notification, Progress, PublishDiagnostics,
ShowMessage, ShowMessage,
}, },
request::{Formatting, Request, WorkDoneProgressCreate}, request::{Formatting, GotoDefinition, HoverRequest, Request, WorkDoneProgressCreate},
DocumentFormattingParams, InitializeParams, TextEdit, DocumentFormattingParams, InitializeParams, TextEdit,
}; };
use miette::Diagnostic; use miette::Diagnostic;
@ -25,7 +31,8 @@ use crate::{
error::Error as ServerError, error::Error as ServerError,
line_numbers::LineNumbers, line_numbers::LineNumbers,
utils::{ utils::{
path_to_uri, text_edit_replace, COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN, path_to_uri, span_to_lsp_range, text_edit_replace, uri_to_module_name,
COMPILING_PROGRESS_TOKEN, CREATE_COMPILING_PROGRESS_TOKEN,
}, },
}; };
@ -230,12 +237,138 @@ impl Server {
} }
} }
} }
HoverRequest::METHOD => {
let params = cast_request::<HoverRequest>(request)?;
let opt_hover = self.hover(params)?;
Ok(lsp_server::Response {
id,
error: None,
result: Some(serde_json::to_value(opt_hover)?),
})
}
GotoDefinition::METHOD => {
let params = cast_request::<GotoDefinition>(request)?;
let location = self.goto_definition(params)?;
Ok(lsp_server::Response {
id,
error: None,
result: Some(serde_json::to_value(location)?),
})
}
unsupported => Err(ServerError::UnsupportedLspRequest { unsupported => Err(ServerError::UnsupportedLspRequest {
request: unsupported.to_string(), request: unsupported.to_string(),
}), }),
} }
} }
fn goto_definition(
&self,
params: lsp_types::GotoDefinitionParams,
) -> Result<Option<lsp_types::Location>, ServerError> {
let params = params.text_document_position_params;
let (line_numbers, node) = match self.node_at_position(&params) {
Some(location) => location,
None => return Ok(None),
};
let location = match node.definition_location() {
Some(location) => location,
None => return Ok(None),
};
let (uri, line_numbers) = match location.module {
None => (params.text_document.uri, &line_numbers),
Some(name) => {
let module = match self
.compiler
.as_ref()
.and_then(|compiler| compiler.sources.get(name))
{
Some(module) => module,
None => return Ok(None),
};
let url = url::Url::parse(&format!("file:///{}", &module.path))
.expect("goto definition URL parse");
(url, &module.line_numbers)
}
};
let range = span_to_lsp_range(location.span, line_numbers);
Ok(Some(lsp_types::Location { uri, range }))
}
fn node_at_position(
&self,
params: &lsp_types::TextDocumentPositionParams,
) -> Option<(LineNumbers, Located<'_>)> {
let module = self.module_for_uri(&params.text_document.uri);
let module = module?;
let line_numbers = LineNumbers::new(&module.code);
let byte_index = line_numbers.byte_index(
params.position.line as usize,
params.position.character as usize,
);
let node = module.find_node(byte_index);
let node = node?;
Some((line_numbers, node))
}
fn module_for_uri(&self, uri: &url::Url) -> Option<&CheckedModule> {
self.compiler.as_ref().and_then(|compiler| {
let module_name = uri_to_module_name(uri, &self.root).expect("uri to module name");
compiler.modules.get(&module_name)
})
}
fn hover(
&self,
params: lsp_types::HoverParams,
) -> Result<Option<lsp_types::Hover>, ServerError> {
let params = params.text_document_position_params;
let (line_numbers, found) = match self.node_at_position(&params) {
Some(value) => value,
None => return Ok(None),
};
let expression = match found {
Located::Expression(expression) => expression,
Located::Definition(_) => return Ok(None),
};
// Show the type of the hovered node to the user
let type_ = Printer::new().pretty_print(expression.tipo().as_ref(), 0);
let contents = formatdoc! {r#"
```aiken
{type_}
```
"#};
Ok(Some(lsp_types::Hover {
contents: lsp_types::HoverContents::Scalar(lsp_types::MarkedString::String(contents)),
range: Some(span_to_lsp_range(expression.location(), &line_numbers)),
}))
}
pub fn listen(&mut self, connection: Connection) -> Result<(), ServerError> { pub fn listen(&mut self, connection: Connection) -> Result<(), ServerError> {
self.create_compilation_progress_token(&connection)?; self.create_compilation_progress_token(&connection)?;
self.start_watching_aiken_toml(&connection)?; self.start_watching_aiken_toml(&connection)?;
@ -366,21 +499,13 @@ impl Server {
let line_numbers = LineNumbers::new(&src); let line_numbers = LineNumbers::new(&src);
let start = line_numbers.line_and_column_number(labeled_span.inner().offset());
let end = line_numbers.line_and_column_number(
labeled_span.inner().offset() + labeled_span.inner().len(),
);
let lsp_diagnostic = lsp_types::Diagnostic { let lsp_diagnostic = lsp_types::Diagnostic {
range: lsp_types::Range::new( range: span_to_lsp_range(
lsp_types::Position { Span {
line: start.line as u32 - 1, start: labeled_span.inner().offset(),
character: start.column as u32 - 1, end: labeled_span.inner().offset() + labeled_span.inner().len(),
},
lsp_types::Position {
line: end.line as u32 - 1,
character: end.column as u32 - 1,
}, },
&line_numbers,
), ),
severity: Some(severity), severity: Some(severity),
code: error code: error

View File

@ -1,8 +1,11 @@
use std::path::PathBuf; use std::path::{Path, PathBuf};
use aiken_lang::ast::Span;
use itertools::Itertools;
use lsp_types::TextEdit; use lsp_types::TextEdit;
use urlencoding::decode;
use crate::error::Error; use crate::{error::Error, line_numbers::LineNumbers};
pub const COMPILING_PROGRESS_TOKEN: &str = "compiling-gleam"; pub const COMPILING_PROGRESS_TOKEN: &str = "compiling-gleam";
pub const CREATE_COMPILING_PROGRESS_TOKEN: &str = "create-compiling-progress-token"; pub const CREATE_COMPILING_PROGRESS_TOKEN: &str = "create-compiling-progress-token";
@ -32,3 +35,52 @@ pub fn path_to_uri(path: PathBuf) -> Result<lsp_types::Url, Error> {
Ok(uri) Ok(uri)
} }
pub fn span_to_lsp_range(location: Span, line_numbers: &LineNumbers) -> lsp_types::Range {
let start = line_numbers.line_and_column_number(location.start);
let end = line_numbers.line_and_column_number(location.end);
lsp_types::Range {
start: lsp_types::Position {
line: start.line as u32 - 1,
character: start.column as u32 - 1,
},
end: lsp_types::Position {
line: end.line as u32 - 1,
character: end.column as u32 - 1,
},
}
}
pub fn uri_to_module_name(uri: &url::Url, root: &Path) -> Option<String> {
let path = if cfg!(target_os = "windows") {
let mut uri_path = decode(&uri.path().replace('/', "\\"))
.expect("Invalid formatting")
.to_string();
if uri_path.starts_with('\\') {
uri_path = uri_path
.strip_prefix('\\')
.expect("Failed to remove \"\\\" prefix")
.to_string();
}
PathBuf::from(uri_path)
} else {
PathBuf::from(uri.path())
};
let components = path
.strip_prefix(root)
.ok()?
.components()
.skip(1)
.map(|c| c.as_os_str().to_string_lossy());
let module_name = Itertools::intersperse(components, "/".into())
.collect::<String>()
.strip_suffix(".ak")?
.to_string();
Some(module_name)
}

View File

@ -230,7 +230,8 @@ mod test {
let (mut ast, extra) = let (mut ast, extra) =
parser::module(source_code, kind).expect("Failed to parse module"); parser::module(source_code, kind).expect("Failed to parse module");
ast.name = name.clone(); ast.name = name.clone();
let mut module = ParsedModule {
ParsedModule {
kind, kind,
ast, ast,
code: source_code.to_string(), code: source_code.to_string(),
@ -238,9 +239,7 @@ mod test {
path: PathBuf::new(), path: PathBuf::new(),
extra, extra,
package: self.package.to_string(), package: self.package.to_string(),
}; }
module.attach_doc_and_module_comments();
module
} }
fn check(&mut self, module: ParsedModule) -> CheckedModule { fn check(&mut self, module: ParsedModule) -> CheckedModule {
@ -261,7 +260,7 @@ mod test {
self.module_types self.module_types
.insert(module.name.clone(), ast.type_info.clone()); .insert(module.name.clone(), ast.type_info.clone());
CheckedModule { let mut checked_module = CheckedModule {
kind: module.kind, kind: module.kind,
extra: module.extra, extra: module.extra,
name: module.name, name: module.name,
@ -269,7 +268,11 @@ mod test {
package: module.package, package: module.package,
input_path: module.path, input_path: module.path,
ast, ast,
} };
checked_module.attach_doc_and_module_comments();
checked_module
} }
} }

View File

@ -467,7 +467,7 @@ where
// Store the name // Store the name
ast.name = name.clone(); ast.name = name.clone();
let mut module = ParsedModule { let module = ParsedModule {
kind, kind,
ast, ast,
code, code,
@ -489,8 +489,6 @@ where
.into()); .into());
} }
module.attach_doc_and_module_comments();
parsed_modules.insert(module.name.clone(), module); parsed_modules.insert(module.name.clone(), module);
} }
Err(errs) => { Err(errs) => {
@ -561,18 +559,19 @@ where
self.module_types self.module_types
.insert(name.clone(), ast.type_info.clone()); .insert(name.clone(), ast.type_info.clone());
self.checked_modules.insert( let mut checked_module = CheckedModule {
name.clone(), kind,
CheckedModule { extra,
kind, name: name.clone(),
extra, code,
name, ast,
code, package,
ast, input_path: path,
package, };
input_path: path,
}, checked_module.attach_doc_and_module_comments();
);
self.checked_modules.insert(name, checked_module);
} }
} }

View File

@ -1,7 +1,7 @@
use crate::error::Error; use crate::error::Error;
use aiken_lang::{ use aiken_lang::{
ast::{ ast::{
DataType, Definition, ModuleKind, TypedDataType, TypedFunction, TypedModule, DataType, Definition, Located, ModuleKind, TypedDataType, TypedFunction, TypedModule,
TypedValidator, UntypedModule, TypedValidator, UntypedModule,
}, },
builder::{DataTypeKey, FunctionAccessKey}, builder::{DataTypeKey, FunctionAccessKey},
@ -41,55 +41,6 @@ impl ParsedModule {
(name, deps) (name, deps)
} }
pub fn attach_doc_and_module_comments(&mut self) {
// Module Comments
self.ast.docs = self
.extra
.module_comments
.iter()
.map(|span| {
Comment::from((span, self.code.as_str()))
.content
.to_string()
})
.collect();
// Order definitions to avoid dissociating doc comments from them
let mut definitions: Vec<_> = self.ast.definitions.iter_mut().collect();
definitions.sort_by(|a, b| a.location().start.cmp(&b.location().start));
// Doc Comments
let mut doc_comments = self.extra.doc_comments.iter().peekable();
for def in &mut definitions {
let docs: Vec<&str> =
comments_before(&mut doc_comments, def.location().start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
def.put_doc(doc);
}
if let Definition::DataType(DataType { constructors, .. }) = def {
for constructor in constructors {
let docs: Vec<&str> =
comments_before(&mut doc_comments, constructor.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
constructor.put_doc(doc);
}
for argument in constructor.arguments.iter_mut() {
let docs: Vec<&str> =
comments_before(&mut doc_comments, argument.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
argument.put_doc(doc);
}
}
}
}
}
}
} }
pub struct ParsedModules(HashMap<String, ParsedModule>); pub struct ParsedModules(HashMap<String, ParsedModule>);
@ -223,6 +174,61 @@ pub struct CheckedModule {
pub extra: ModuleExtra, pub extra: ModuleExtra,
} }
impl CheckedModule {
pub fn find_node(&self, byte_index: usize) -> Option<Located<'_>> {
self.ast.find_node(byte_index)
}
pub fn attach_doc_and_module_comments(&mut self) {
// Module Comments
self.ast.docs = self
.extra
.module_comments
.iter()
.map(|span| {
Comment::from((span, self.code.as_str()))
.content
.to_string()
})
.collect();
// Order definitions to avoid dissociating doc comments from them
let mut definitions: Vec<_> = self.ast.definitions.iter_mut().collect();
definitions.sort_by(|a, b| a.location().start.cmp(&b.location().start));
// Doc Comments
let mut doc_comments = self.extra.doc_comments.iter().peekable();
for def in &mut definitions {
let docs: Vec<&str> =
comments_before(&mut doc_comments, def.location().start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
def.put_doc(doc);
}
if let Definition::DataType(DataType { constructors, .. }) = def {
for constructor in constructors {
let docs: Vec<&str> =
comments_before(&mut doc_comments, constructor.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
constructor.put_doc(doc);
}
for argument in constructor.arguments.iter_mut() {
let docs: Vec<&str> =
comments_before(&mut doc_comments, argument.location.start, &self.code);
if !docs.is_empty() {
let doc = docs.join("\n");
argument.put_doc(doc);
}
}
}
}
}
}
}
#[derive(Default, Debug, Clone)] #[derive(Default, Debug, Clone)]
pub struct CheckedModules(HashMap<String, CheckedModule>); pub struct CheckedModules(HashMap<String, CheckedModule>);