Skip to main content

phpdoc_parser/
lib.rs

1//! Structural PHPDoc comment parser.
2//!
3//! Parses `/** ... */` documentation blocks into a structured AST with accurate
4//! spans and support for inline tags. Designed for type checkers, linters, IDEs,
5//! and documentation generators.
6//!
7//! The crate is **agnostic** — it does not interpret tag semantics or parse type
8//! expressions. Tag bodies are exposed as raw [`PhpDocText`], letting tools apply
9//! their own type parsers and validation rules.
10//!
11//! # Quick start
12//!
13//! ```
14//! use phpdoc_parser::parse;
15//!
16//! let text = "/** @param int $x The value */";
17//! let doc = parse(text);
18//! assert_eq!(doc.tags.len(), 1);
19//! assert_eq!(doc.tags[0].name, "param");
20//! ```
21//!
22//! # Common patterns
23//!
24//! ### Read a tag body
25//!
26//! ```
27//! use phpdoc_parser::{parse, find_tags, body_text};
28//!
29//! let doc = parse("/** @param int $x The mapping */");
30//! for param in find_tags(&doc, "param") {
31//!     let body = body_text(&param.body).unwrap_or_default();
32//!     // body is "int $x The mapping" — parse the type yourself
33//! }
34//! ```
35//!
36//! ### Find inline references
37//!
38//! ```
39//! use phpdoc_parser::{parse, inline_tags};
40//!
41//! let doc = parse("/** See {@link User::load()} for details. */");
42//! if let Some(desc) = &doc.description {
43//!     for tag in inline_tags(desc) {
44//!         if tag.name == "link" {
45//!             // Process the reference to User::load()
46//!         }
47//!     }
48//! }
49//! ```
50
51pub(crate) mod ast;
52pub(crate) mod parser;
53pub(crate) mod span;
54
55pub use ast::{InlineTag, PhpDoc, PhpDocTag, PhpDocText, TextSegment};
56pub use parser::parse;
57pub use span::Span;
58
59// =============================================================================
60// Utility functions for common tasks
61// =============================================================================
62
63/// Find the first tag with the given name.
64///
65/// # Example
66/// ```
67/// use phpdoc_parser::{parse, find_tag};
68/// let doc = parse("/** @param int $x @return string */");
69/// assert!(find_tag(&doc, "param").is_some());
70/// assert!(find_tag(&doc, "throws").is_none());
71/// ```
72pub fn find_tag<'d>(doc: &'d PhpDoc, name: &str) -> Option<&'d PhpDocTag> {
73    doc.tags.iter().find(|t| t.name == name)
74}
75
76/// Find all tags with the given name.
77///
78/// # Example
79/// ```
80/// use phpdoc_parser::{parse, find_tags};
81/// let doc = parse("/**\n * @param int $x\n * @param string $y\n */");
82/// assert_eq!(find_tags(&doc, "param").len(), 2);
83/// ```
84pub fn find_tags<'d>(doc: &'d PhpDoc, name: &str) -> Vec<&'d PhpDocTag> {
85    doc.tags.iter().filter(|t| t.name == name).collect()
86}
87
88/// Reconstruct the text content of a `PhpDocText`, including inline tags.
89///
90/// Inline tags are reconstructed as `{@name body}` format.
91///
92/// # Example
93/// ```
94/// use phpdoc_parser::{parse, text_content};
95/// let doc = parse("/** See {@link Foo} for details. */");
96/// let summary = text_content(doc.summary.as_ref().unwrap());
97/// assert!(summary.contains("@link"));
98/// ```
99pub fn text_content(text: &PhpDocText) -> String {
100    text.segments
101        .iter()
102        .map(|seg| match seg {
103            TextSegment::Text(t) => t.clone(),
104            TextSegment::InlineTag(tag) => {
105                format!(
106                    "{{@{}{}}}",
107                    tag.name,
108                    tag.body
109                        .as_ref()
110                        .map(|b| format!(" {}", b))
111                        .unwrap_or_default()
112                )
113            }
114        })
115        .collect()
116}
117
118/// Get the text content of a tag body, if present.
119///
120/// # Example
121/// ```
122/// use phpdoc_parser::{parse, find_tag, body_text};
123/// let doc = parse("/** @param int $x */");
124/// let param = find_tag(&doc, "param").unwrap();
125/// let text = body_text(&param.body).unwrap();
126/// assert!(text.contains("$x"));
127/// ```
128pub fn body_text(body: &Option<PhpDocText>) -> Option<String> {
129    body.as_ref().map(text_content)
130}
131
132/// Extract all inline tags from a text segment.
133///
134/// # Example
135/// ```
136/// use phpdoc_parser::{parse, inline_tags};
137/// let doc = parse("/** See {@link Foo} and {@link Bar}. */");
138/// let tags = inline_tags(doc.summary.as_ref().unwrap());
139/// assert_eq!(tags.len(), 2);
140/// ```
141pub fn inline_tags(text: &PhpDocText) -> Vec<&InlineTag> {
142    text.segments
143        .iter()
144        .filter_map(|seg| match seg {
145            TextSegment::InlineTag(tag) => Some(tag),
146            _ => None,
147        })
148        .collect()
149}