Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for doc comments in icrate generation #435

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions crates/header-translator/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ impl<'a> Cache<'a> {
superclasses: _,
methods,
description,
comment: _,
} = stmt
{
let _span = debug_span!("Stmt::Methods", ?cls).entered();
Expand Down Expand Up @@ -164,6 +165,7 @@ impl<'a> Cache<'a> {
id,
generics,
superclasses,
comment,
..
} => {
let _span = debug_span!("Stmt::ClassDecl", ?id).entered();
Expand Down Expand Up @@ -204,6 +206,7 @@ impl<'a> Cache<'a> {
generics: generics.clone(),
category: cache.category.clone(),
availability: cache.availability.clone(),
comment: comment.clone(),
superclasses: superclasses.clone(),
methods,
description: Some(format!(
Expand Down Expand Up @@ -244,6 +247,7 @@ impl<'a> Cache<'a> {
ty: enum_ty,
kind: _,
variants: _,
comment: _,
}) = iter.peek_mut()
{
if enum_ty.is_typedef_to(&id.name) {
Expand Down
126 changes: 126 additions & 0 deletions crates/header-translator/src/comment.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//! Utilities for manipulating C/C++ comments.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I copied this file straight out of bindgen and then modified it for some of the extra gross objective-c comments.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you leave attribution in the file? I think that should alleviate most licensing issues (they have a different license than us), but IANAL.


/// The type of a comment.
#[derive(Debug, PartialEq, Eq)]
enum Kind {
/// A `///` comment, or something of the like.
/// All lines in a comment should start with the same symbol.
SingleLines,
/// A `/**` comment, where each other line can start with `*` and the
/// entire block ends with `*/`.
MultiLine,
}

/// Preprocesses a C/C++ comment so that it is a valid Rust comment.
pub(crate) fn preprocess(comment: &str) -> String {
match self::kind(comment) {
Some(Kind::SingleLines) => preprocess_single_lines(comment),
Some(Kind::MultiLine) => preprocess_multi_line(comment),
None => comment.to_owned(),
}
}

/// Gets the kind of the doc comment, if it is one.
fn kind(comment: &str) -> Option<Kind> {
if comment.starts_with("/*") {
Some(Kind::MultiLine)
} else if comment.starts_with("//") {
Some(Kind::SingleLines)
} else {
None
}
}

/// Preprocesses multiple single line comments.
///
/// Handles lines starting with both `//` and `///`.
fn preprocess_single_lines(comment: &str) -> String {
debug_assert!(comment.starts_with("//"), "comment is not single line");

let lines: Vec<_> = comment
.lines()
.map(|l| {
l.trim()
.trim_end_matches('/')
.trim_end_matches('*')
.trim_start_matches('/')
})
.collect();
lines.join("\n")
}

fn preprocess_multi_line(comment: &str) -> String {
let comment = comment
.trim_start_matches('/')
.trim_end_matches('/')
.trim_end_matches('*');

// Strip any potential `*` characters preceding each line.
let mut lines: Vec<_> = comment
.lines()
.map(|line| {
line.trim_start_matches('/')
.trim_end_matches('/')
.trim_end_matches('*')
.trim()
.trim_start_matches('*')
.trim_start_matches('!')
})
.skip_while(|line| line.trim().is_empty()) // Skip the first empty lines.
.collect();

// Remove the trailing line corresponding to the `*/`.
if lines.last().map_or(false, |l| l.trim().is_empty()) {
lines.pop();
}

lines.join("\n")
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn picks_up_single_and_multi_line_doc_comments() {
assert_eq!(kind("/// hello"), Some(Kind::SingleLines));
assert_eq!(kind("/** world */"), Some(Kind::MultiLine));
}

#[test]
fn processes_single_lines_correctly() {
assert_eq!(preprocess("///"), "");
assert_eq!(preprocess("/// hello"), " hello");
assert_eq!(preprocess("// hello"), " hello");
assert_eq!(preprocess("// hello"), " hello");
}

#[test]
fn processes_multi_lines_correctly() {
assert_eq!(preprocess("/**/"), "");

assert_eq!(
preprocess("/** hello \n * world \n * foo \n */"),
" hello\n world\n foo"
);

assert_eq!(
preprocess("/**\nhello\n*world\n*foo\n*/"),
"hello\nworld\nfoo"
);
assert_eq!(
preprocess(
r#"/************************ Deprecated ************************/
// Following NSStringDrawing methods are soft deprecated starting with OS X 10.11. It will be officially deprecated in a future release. Use corresponding API with NSStringDrawingContext instead"#
),
r#" Deprecated
Following NSStringDrawing methods are soft deprecated starting with OS X 10.11. It will be officially deprecated in a future release. Use corresponding API with NSStringDrawingContext instead"#
);
assert_eq!(
preprocess(
r#"/// The \c contentLayoutRect will return the area inside the window that is for non-obscured content. Typically, this is the same thing as the `contentView`'s frame. However, for windows with the \c NSFullSizeContentViewWindowMask set, there needs to be a way to determine the portion that is not under the toolbar. The \c contentLayoutRect returns the portion of the layout that is not obscured under the toolbar. \c contentLayoutRect is in window coordinates. It is KVO compliant. */"#
),
r#" The \c contentLayoutRect will return the area inside the window that is for non-obscured content. Typically, this is the same thing as the `contentView`'s frame. However, for windows with the \c NSFullSizeContentViewWindowMask set, there needs to be a way to determine the portion that is not under the toolbar. The \c contentLayoutRect returns the portion of the layout that is not obscured under the toolbar. \c contentLayoutRect is in window coordinates. It is KVO compliant. "#
);
}
}
1 change: 1 addition & 0 deletions crates/header-translator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use tracing::span::EnteredSpan;

mod availability;
mod cache;
mod comment;
mod config;
mod context;
mod data;
Expand Down
4 changes: 2 additions & 2 deletions crates/header-translator/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,8 +313,8 @@ fn get_translation_unit<'i: 'tu, 'tu>(
"-fobjc-arc",
"-fobjc-arc-exceptions",
"-fobjc-abi-version=2", // 3??
// "-fparse-all-comments",
// TODO: "-fretain-comments-from-system-headers"
"-fparse-all-comments",
"-fretain-comments-from-system-headers",
"-fapinotes",
"-isysroot",
sdk.path.to_str().unwrap(),
Expand Down
11 changes: 11 additions & 0 deletions crates/header-translator/src/method.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ pub struct Method {
safe: bool,
mutating: bool,
is_protocol: bool,
comment: Option<String>,
}

impl Method {
Expand Down Expand Up @@ -408,6 +409,7 @@ impl<'tu> PartialMethod<'tu> {
}

let result_type = entity.get_result_type().expect("method return type");
//let comment = entity.get_comment();
let mut result_type = Ty::parse_method_return(result_type, context);

let memory_management = MemoryManagement::new(is_class, &selector, &result_type, modifiers);
Expand Down Expand Up @@ -442,6 +444,7 @@ impl<'tu> PartialMethod<'tu> {
safe: !data.unsafe_,
mutating: data.mutating,
is_protocol,
comment: None,
},
))
}
Expand Down Expand Up @@ -484,6 +487,7 @@ impl PartialProperty<'_> {
}

let availability = Availability::parse(&entity, context);
let comment = entity.get_comment();

let modifiers = MethodModifiers::parse(&entity, context);

Expand Down Expand Up @@ -514,6 +518,7 @@ impl PartialProperty<'_> {
safe: !getter_data.unsafe_,
mutating: getter_data.mutating,
is_protocol,
comment,
})
} else {
None
Expand All @@ -528,6 +533,7 @@ impl PartialProperty<'_> {
let selector = setter_name.clone() + ":";
let memory_management =
MemoryManagement::new(is_class, &selector, &Ty::VOID_RESULT, modifiers);
let comment = entity.get_comment();

Some(Method {
selector,
Expand All @@ -541,6 +547,7 @@ impl PartialProperty<'_> {
safe: !setter_data.unsafe_,
mutating: setter_data.mutating,
is_protocol,
comment,
})
} else {
None
Expand Down Expand Up @@ -575,6 +582,10 @@ impl fmt::Display for Method {
// Attributes
//

if let Some(ref comment) = self.comment {
let comment = crate::comment::preprocess(comment);
writeln!(f, "/**\n {comment} \n*/")?;
}
write!(f, "{}", self.availability)?;

if self.is_optional_protocol {
Expand Down
Loading