use crate::entities::*; use clang::{Clang, Index}; use codemap::CodeMap; use std::collections::HashMap; use std::path::{Path, PathBuf}; pub(crate) fn parse_file(path: T, mut extra_args: Vec<&str>) -> EntitiesManager where T: Into, T: AsRef, T: ToString, { let mut codemap = CodeMap::new(); let file_map = codemap.add_file(path.to_string(), std::fs::read_to_string(&path).unwrap()); let file_span = file_map.span; let clang = Clang::new().unwrap(); let index = Index::new(&clang, true, false); let mut parser = index.parser(path); parser.skip_function_bodies(true); extra_args.push("-Werror=documentation"); parser.arguments(&extra_args); if log_enabled!(log::Level::Debug) { for extra_arg in extra_args { debug!("Extra LibClang argument: {:?}", extra_arg); } } let trans_unit = parser.parse().unwrap(); let mut manager = EntitiesManager::new(); trans_unit.get_entity().visit_children(|entity, _parent| { if entity.is_in_system_header() { trace!( "Entity is in system header, skipping: USR = {:?}", entity.get_display_name() ); return clang::EntityVisitResult::Continue; } // TODO: wrap this callback in another function so that we can use the // "?" operator instead of all these `match`es let usr = match entity.get_usr() { Some(usr) => usr, None => return clang::EntityVisitResult::Continue, }; trace!("Entity with USR = {:?}", usr); let name = match entity.get_name() { Some(name) => name, None => return clang::EntityVisitResult::Continue, }; debug!("Parsing toplevel entity with name = {:?}", name); let entity = entity.into_poseidoc_entity(&mut manager); manager.insert_toplevel(usr.into(), entity); clang::EntityVisitResult::Continue }); use codemap_diagnostic::{ColorConfig, Emitter}; let mut emitter = Emitter::stderr(ColorConfig::Auto, Some(&codemap)); for diagnostic in trans_unit.get_diagnostics().iter() { let main_diag = match clang_diag_to_codemap_diag(&diagnostic, &file_span) { Some(diag) => diag, None => continue, }; let sub_diags = diagnostic .get_children() .into_iter() .filter_map(|diagnostic| clang_diag_to_codemap_diag(&diagnostic, &file_span)); let fix_it_diags = diagnostic .get_fix_its() .into_iter() .map(|fix_it| clang_fix_it_to_codemap_diag(&fix_it, &file_span)); emitter.emit( &std::iter::once(main_diag) .chain(sub_diags) .chain(fix_it_diags) .collect::>(), ); } manager } fn clang_diag_to_codemap_diag( diagnostic: &clang::diagnostic::Diagnostic, file_span: &codemap::Span, ) -> Option { use codemap_diagnostic::{Diagnostic, Level, SpanLabel, SpanStyle}; let ranges = diagnostic.get_ranges(); let (begin, end) = if ranges.is_empty() { let location = diagnostic.get_location(); ( location.get_file_location().offset as u64, location.get_file_location().offset as u64, ) } else { let range = diagnostic.get_ranges()[0]; if !range.is_in_main_file() { warn!("Skipping diagnostic: {:?}", diagnostic); return None; } ( range.get_start().get_file_location().offset as u64, range.get_end().get_file_location().offset as u64, ) }; let label = SpanLabel { span: file_span.subspan(begin, end), label: None, style: SpanStyle::Primary, }; Some(Diagnostic { level: match diagnostic.get_severity() { clang::diagnostic::Severity::Ignored => return None, clang::diagnostic::Severity::Note => Level::Note, clang::diagnostic::Severity::Warning => Level::Warning, clang::diagnostic::Severity::Error => Level::Error, clang::diagnostic::Severity::Fatal => Level::Error, }, message: diagnostic.get_text(), //code: Some("-Werror=documentation".to_string()), code: None, spans: vec![label], }) } fn clang_fix_it_to_codemap_diag( fix_it: &clang::diagnostic::FixIt, file_span: &codemap::Span, ) -> codemap_diagnostic::Diagnostic { use clang::diagnostic::FixIt; use codemap_diagnostic::{Diagnostic, Level, SpanLabel, SpanStyle}; let label = match fix_it { FixIt::Deletion(range) => { let begin = range.get_start().get_file_location().offset as u64; let end = range.get_end().get_file_location().offset as u64; SpanLabel { span: file_span.subspan(begin, end), label: Some(String::from("Try deleting this")), style: SpanStyle::Primary, } } FixIt::Insertion(range, text) => { let location = range.get_file_location().offset as u64; SpanLabel { span: file_span.subspan(location, location), label: Some(format!("Try insterting {:?}", text)), style: SpanStyle::Primary, } } FixIt::Replacement(range, text) => { let begin = range.get_start().get_file_location().offset as u64; let end = range.get_end().get_file_location().offset as u64; SpanLabel { span: file_span.subspan(begin, end), label: Some(format!("Try replacing this with {:?}", text)), style: SpanStyle::Primary, } } }; Diagnostic { level: Level::Help, message: String::from("Possible fix"), //code: Some("-Werror=documentation".to_string()), code: None, spans: vec![label], } } pub(crate) struct Comment(String); impl Comment { pub fn from_raw(raw: String) -> Self { #[derive(Debug)] enum CommentStyle { // Comments of type `/**` or `/*!` Starred, // Comments of type `///` SingleLine, } let mut chars = raw.chars(); let style = match &chars.as_str()[..3] { "/*!" | "/**" => CommentStyle::Starred, "///" => CommentStyle::SingleLine, _ => panic!("Comment is empty or doesn't start with either `///`, `/**`, or `/*!`"), }; chars.nth(2); let mut result = String::new(); 'parse_loop: loop { let maybe_space = chars.next(); let mut empty_line = false; match maybe_space { // TODO: Warn on empty comments None => break, Some(' ') => {} Some('\n') => { empty_line = true; result.push('\n'); } Some(ch) => result.push(ch), } if !empty_line { let rest = chars.as_str(); match rest.find('\n') { None => { result.push_str(rest); break; } Some(position) => { result.push_str(&rest[..position + 1]); chars.nth(position); } } } // Beginning of the line let first_non_ws_ch = 'ws_loop: loop { let maybe_whitespace = chars.next(); match maybe_whitespace { None => break 'parse_loop, Some(ch) if ch.is_whitespace() => continue, Some(ch) => break 'ws_loop ch, } }; match style { CommentStyle::Starred if first_non_ws_ch == '*' => { if &chars.as_str()[..1] == "/" { break; } } CommentStyle::Starred => result.push(first_non_ws_ch), CommentStyle::SingleLine => { assert!(first_non_ws_ch == '/'); let rest = chars.as_str(); if &rest[..2] == "//" { chars.nth(1); } else if &rest[..1] == "/" { chars.nth(0); } else { panic!("Could not parse comment"); } } } } Self(result) } } impl From for Usr { fn from(usr: clang::Usr) -> Self { Self(usr.0) } } trait FromClangEntity { /// Is responsible for inserting its documentation into the entities manager fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self; } trait IntoPoseidocEntity { /// Useful for the `clang_entity.into_poseidoc_entity(&mut manager)` syntax. Implement /// `FromClangEntity` instead. fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T; } impl IntoPoseidocEntity for clang::Entity<'_> where T: FromClangEntity, { fn into_poseidoc_entity(self, manager: &mut EntitiesManager) -> T { T::from_clang_entity(self, manager) } } fn get_description(entity: &clang::Entity) -> Description { let name = entity.get_display_name().unwrap(); if let (Some(brief), Some(comment)) = (entity.get_comment_brief(), entity.get_comment()) { Description { name, brief, detailed: Comment::from_raw(comment).0, } } else { Description::with_name(name) } } impl FromClangEntity for Entity { fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { use clang::EntityKind; match entity.get_kind() { EntityKind::Namespace => Self::NameSpace(entity.into_poseidoc_entity(manager)), EntityKind::FieldDecl | EntityKind::VarDecl => { Self::Variable(entity.into_poseidoc_entity(manager)) } EntityKind::FunctionDecl | EntityKind::Method | EntityKind::FunctionTemplate => { Self::Function(entity.into_poseidoc_entity(manager)) } EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::ClassTemplate => { Self::Class(entity.into_poseidoc_entity(manager)) } _ => panic!("Unsupported entity: {:?}", entity), } } } impl FromClangEntity for NameSpace { fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { match entity.get_kind() { clang::EntityKind::Namespace => {} _ => panic!("Trying to parse a non-variable into a variable"), } debug!("Parsing NameSpace"); let members = entity .get_children() .into_iter() .map(|child| { let usr = child.get_usr().unwrap().into(); trace!("Namespace has member: {:?}", usr); (usr, child.into_poseidoc_entity(manager)) }) .collect(); manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); NameSpace { members } } } impl FromClangEntity for Variable { fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { match entity.get_kind() { clang::EntityKind::VarDecl | clang::EntityKind::FieldDecl => {} _ => panic!("Trying to parse a non-variable into a variable"), } debug!("Parsing Variable"); let r#type = entity.get_type().unwrap().get_display_name(); trace!("Variable has type: {:?}", r#type); manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); Variable { r#type } } } impl FromClangEntity for Function { fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { match entity.get_kind() { clang::EntityKind::Method | clang::EntityKind::FunctionDecl | clang::EntityKind::FunctionTemplate => {} _ => panic!("Trying to parse a non-function into a function"), } debug!("Parsing Function"); let return_type = entity.get_result_type().unwrap().get_display_name(); trace!("Function has return type: {:?}", return_type); let arguments = entity .get_arguments() .unwrap() .into_iter() .map(|arg| { let name = arg.get_display_name().unwrap(); let r#type = arg.get_type().unwrap().get_display_name(); trace!("Function has argument {:?} of type {:?}", name, r#type); FunctionArgument { name, r#type } }) .collect(); manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); Function { arguments, return_type, } } } impl FromClangEntity for Class { fn from_clang_entity(entity: clang::Entity, manager: &mut EntitiesManager) -> Self { match entity.get_kind() { clang::EntityKind::ClassDecl | clang::EntityKind::StructDecl | clang::EntityKind::ClassTemplate => {} _ => panic!("Trying to parse a non-class into a class"), } debug!("Parsing Class"); let mut member_types = Vec::new(); let mut member_functions = HashMap::new(); let mut member_variables = HashMap::new(); for child in entity.get_children() { trace!("Class has child: {:?}", child); let child_usr = child.get_usr().unwrap().into(); use clang::EntityKind; match child.get_kind() { EntityKind::ClassDecl | EntityKind::StructDecl | EntityKind::TypeAliasDecl => { member_types.push(child_usr); } EntityKind::Method => { member_functions.insert(child_usr, child.into_poseidoc_entity(manager)); } EntityKind::FieldDecl => { member_variables.insert(child_usr, child.into_poseidoc_entity(manager)); } _ => trace!("Skipping child"), } } manager.insert(entity.get_usr().unwrap().into(), get_description(&entity)); Class { member_types, member_functions, member_variables, } } }