use std::borrow::Cow;
use crate::format::kind::Kind;
use crate::format::node_record::{KIND_OFFSET, NODE_RECORD_SIZE};
use crate::format::string_field::STRING_FIELD_SIZE;
use crate::writer::nodes::comment_ast::{
JSDOC_BLOCK_BASIC_SIZE, JSDOC_BLOCK_COMPAT_SIZE, JSDOC_BLOCK_DESC_LINES_SLOT,
JSDOC_BLOCK_HAS_DESCRIPTION_RAW_SPAN_BIT, JSDOC_BLOCK_INLINE_TAGS_SLOT, JSDOC_BLOCK_TAGS_SLOT,
JSDOC_TAG_BASIC_SIZE, JSDOC_TAG_COMPAT_SIZE, JSDOC_TAG_DESC_LINES_SLOT,
JSDOC_TAG_HAS_DESCRIPTION_RAW_SPAN_BIT, JSDOC_TAG_INLINE_TAGS_SLOT, JSDOC_TAG_TYPE_LINES_SLOT,
};
use super::super::helpers::{
child_at_visitor_index, ext_offset, read_list_metadata, read_string_field, string_payload,
};
use super::super::source_file::LazySourceFile;
use super::super::text::parsed_preserving_whitespace;
use super::type_node::LazyTypeNode;
use super::{LazyNode, NodeListIter};
macro_rules! define_lazy_comment_node {
($name:ident, $kind:expr, $doc:expr) => {
#[doc = $doc]
#[derive(Debug, Clone, Copy)]
pub struct $name<'a> {
source_file: &'a LazySourceFile<'a>,
node_index: u32,
root_index: u32,
}
impl<'a> LazyNode<'a> for $name<'a> {
const KIND: Kind = $kind;
#[inline]
fn from_index(
source_file: &'a LazySourceFile<'a>,
node_index: u32,
root_index: u32,
) -> Self {
$name {
source_file,
node_index,
root_index,
}
}
#[inline]
fn source_file(&self) -> &'a LazySourceFile<'a> {
self.source_file
}
#[inline]
fn node_index(&self) -> u32 {
self.node_index
}
#[inline]
fn root_index(&self) -> u32 {
self.root_index
}
}
};
}
#[inline]
fn resolve_string_field_slot<'a>(
sf: &LazySourceFile<'a>,
ext_byte: usize,
field_offset: usize,
) -> Option<&'a str> {
let field = read_string_field(sf.bytes(), ext_byte + field_offset);
sf.get_string_by_field(field)
}
#[inline]
fn ext_string<'a>(sf: &LazySourceFile<'a>, node_index: u32, field_offset: usize) -> &'a str {
let ext = ext_offset(sf, node_index) as usize;
let field = read_string_field(sf.bytes(), ext + field_offset);
sf.get_string_by_field(field).unwrap_or("")
}
define_lazy_comment_node!(
LazyJsdocBlock,
Kind::JsdocBlock,
"Lazy view of a `JsdocBlock` (Kind 0x01, root node)."
);
impl<'a> LazyJsdocBlock<'a> {
#[inline]
#[allow(dead_code)]
fn children_bitmask(&self) -> u8 {
let ext = ext_offset(self.source_file, self.node_index) as usize;
self.source_file.bytes()[ext]
}
pub fn description(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 2)
}
pub fn delimiter(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 8)
}
pub fn post_delimiter(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 14)
}
pub fn terminal(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 20)
}
pub fn line_end(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 26)
}
pub fn initial(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 32)
}
pub fn delimiter_line_break(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 38)
}
pub fn preterminal_line_break(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 44)
}
pub fn description_lines(&self) -> NodeListIter<'a, LazyJsdocDescriptionLine<'a>> {
self.list_at(JSDOC_BLOCK_DESC_LINES_SLOT)
}
pub fn tags(&self) -> NodeListIter<'a, LazyJsdocTag<'a>> {
self.list_at(JSDOC_BLOCK_TAGS_SLOT)
}
pub fn inline_tags(&self) -> NodeListIter<'a, LazyJsdocInlineTag<'a>> {
self.list_at(JSDOC_BLOCK_INLINE_TAGS_SLOT)
}
pub fn end_line(&self) -> Option<u32> {
if !self.source_file.compat_mode {
return None;
}
let ext = ext_offset(self.source_file, self.node_index) as usize;
Some(super::super::helpers::read_u32(
self.source_file.bytes(),
ext + 70,
))
}
pub fn description_raw(&self) -> Option<&'a str> {
if (self.common_data() & JSDOC_BLOCK_HAS_DESCRIPTION_RAW_SPAN_BIT) == 0 {
return None;
}
let span_off = if self.source_file.compat_mode {
JSDOC_BLOCK_COMPAT_SIZE
} else {
JSDOC_BLOCK_BASIC_SIZE
};
let ext = ext_offset(self.source_file, self.node_index) as usize;
let bytes = self.source_file.bytes();
let start = super::super::helpers::read_u32(bytes, ext + span_off);
let end = super::super::helpers::read_u32(bytes, ext + span_off + 4);
self.source_file
.slice_source_text(self.root_index, start, end)
}
pub fn description_text(&self, preserve_whitespace: bool) -> Option<Cow<'a, str>> {
if preserve_whitespace {
self.description_raw()
.map(|raw| Cow::Owned(parsed_preserving_whitespace(raw)))
} else {
self.description().map(Cow::Borrowed)
}
}
#[inline]
fn list_at<T: LazyNode<'a>>(&self, slot_offset: usize) -> NodeListIter<'a, T> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
let (head, count) = read_list_metadata(self.source_file, ext, slot_offset);
NodeListIter::new(self.source_file, head, count, self.root_index)
}
}
define_lazy_comment_node!(
LazyJsdocDescriptionLine,
Kind::JsdocDescriptionLine,
"Lazy view of a `JsdocDescriptionLine` (Kind 0x02)."
);
impl<'a> LazyJsdocDescriptionLine<'a> {
pub fn description(&self) -> &'a str {
if self.source_file.compat_mode {
ext_string(self.source_file, self.node_index, 0)
} else {
string_payload(self.source_file, self.node_index).unwrap_or("")
}
}
}
define_lazy_comment_node!(
LazyJsdocTag,
Kind::JsdocTag,
"Lazy view of a `JsdocTag` (Kind 0x03)."
);
impl<'a> LazyJsdocTag<'a> {
#[inline]
pub fn optional(&self) -> bool {
(self.common_data() & 0b0000_0001) != 0
}
pub fn default_value(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 2)
}
pub fn description(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 8)
}
pub fn description_raw(&self) -> Option<&'a str> {
if (self.common_data() & JSDOC_TAG_HAS_DESCRIPTION_RAW_SPAN_BIT) == 0 {
return None;
}
let span_off = if self.source_file.compat_mode {
JSDOC_TAG_COMPAT_SIZE
} else {
JSDOC_TAG_BASIC_SIZE
};
let ext = ext_offset(self.source_file, self.node_index) as usize;
let bytes = self.source_file.bytes();
let start = super::super::helpers::read_u32(bytes, ext + span_off);
let end = super::super::helpers::read_u32(bytes, ext + span_off + 4);
self.source_file
.slice_source_text(self.root_index, start, end)
}
pub fn description_text(&self, preserve_whitespace: bool) -> Option<Cow<'a, str>> {
if preserve_whitespace {
self.description_raw()
.map(|raw| Cow::Owned(parsed_preserving_whitespace(raw)))
} else {
self.description().map(Cow::Borrowed)
}
}
pub fn raw_body(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 14)
}
#[inline]
fn children_bitmask(&self) -> u8 {
let ext = ext_offset(self.source_file, self.node_index) as usize;
self.source_file.bytes()[ext]
}
pub fn tag(&self) -> LazyJsdocTagName<'a> {
let bitmask = self.children_bitmask();
let idx = child_at_visitor_index(self.source_file, self.node_index, bitmask, 0)
.expect("JsdocTag.tag is required");
LazyJsdocTagName::from_index(self.source_file, idx, self.root_index)
}
pub fn raw_type(&self) -> Option<LazyJsdocTypeSource<'a>> {
let bitmask = self.children_bitmask();
child_at_visitor_index(self.source_file, self.node_index, bitmask, 1)
.map(|idx| LazyJsdocTypeSource::from_index(self.source_file, idx, self.root_index))
}
pub fn name(&self) -> Option<LazyJsdocTagNameValue<'a>> {
let bitmask = self.children_bitmask();
child_at_visitor_index(self.source_file, self.node_index, bitmask, 2)
.map(|idx| LazyJsdocTagNameValue::from_index(self.source_file, idx, self.root_index))
}
pub fn parsed_type(&self) -> Option<LazyTypeNode<'a>> {
let bitmask = self.children_bitmask();
let idx = child_at_visitor_index(self.source_file, self.node_index, bitmask, 3)?;
LazyTypeNode::from_index(self.source_file, idx, self.root_index)
}
pub fn body(&self) -> Option<LazyJsdocTagBody<'a>> {
let bitmask = self.children_bitmask();
let idx = child_at_visitor_index(self.source_file, self.node_index, bitmask, 4)?;
let kind_byte = self.source_file.bytes()[self.source_file.nodes_offset as usize
+ idx as usize * NODE_RECORD_SIZE
+ KIND_OFFSET];
let kind = Kind::from_u8(kind_byte).ok()?;
match kind {
Kind::JsdocGenericTagBody => Some(LazyJsdocTagBody::Generic(
LazyJsdocGenericTagBody::from_index(self.source_file, idx, self.root_index),
)),
Kind::JsdocBorrowsTagBody => Some(LazyJsdocTagBody::Borrows(
LazyJsdocBorrowsTagBody::from_index(self.source_file, idx, self.root_index),
)),
Kind::JsdocRawTagBody => Some(LazyJsdocTagBody::Raw(LazyJsdocRawTagBody::from_index(
self.source_file,
idx,
self.root_index,
))),
_ => None,
}
}
pub fn description_lines(&self) -> NodeListIter<'a, LazyJsdocDescriptionLine<'a>> {
self.list_at(JSDOC_TAG_DESC_LINES_SLOT)
}
pub fn type_lines(&self) -> NodeListIter<'a, LazyJsdocTypeLine<'a>> {
self.list_at(JSDOC_TAG_TYPE_LINES_SLOT)
}
pub fn inline_tags(&self) -> NodeListIter<'a, LazyJsdocInlineTag<'a>> {
self.list_at(JSDOC_TAG_INLINE_TAGS_SLOT)
}
#[inline]
fn list_at<T: LazyNode<'a>>(&self, slot_offset: usize) -> NodeListIter<'a, T> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
let (head, count) = read_list_metadata(self.source_file, ext, slot_offset);
NodeListIter::new(self.source_file, head, count, self.root_index)
}
}
macro_rules! define_string_leaf {
($name:ident, $kind:expr, $accessor:ident, $doc:expr) => {
define_lazy_comment_node!($name, $kind, $doc);
impl<'a> $name<'a> {
#[doc = concat!("Resolve the underlying string value of this `", stringify!($name), "`.")]
pub fn $accessor(&self) -> &'a str {
string_payload(self.source_file, self.node_index).unwrap_or("")
}
}
};
}
define_string_leaf!(
LazyJsdocTagName,
Kind::JsdocTagName,
value,
"Lazy view of a `JsdocTagName` leaf (Kind 0x04)."
);
define_string_leaf!(
LazyJsdocTagNameValue,
Kind::JsdocTagNameValue,
raw,
"Lazy view of a `JsdocTagNameValue` leaf (Kind 0x05)."
);
define_string_leaf!(
LazyJsdocTypeSource,
Kind::JsdocTypeSource,
raw,
"Lazy view of a `JsdocTypeSource` leaf (Kind 0x06)."
);
define_string_leaf!(
LazyJsdocRawTagBody,
Kind::JsdocRawTagBody,
raw,
"Lazy view of a `JsdocRawTagBody` leaf (Kind 0x0B)."
);
define_string_leaf!(
LazyJsdocNamepathSource,
Kind::JsdocNamepathSource,
raw,
"Lazy view of a `JsdocNamepathSource` leaf (Kind 0x0D)."
);
define_string_leaf!(
LazyJsdocIdentifier,
Kind::JsdocIdentifier,
name,
"Lazy view of a `JsdocIdentifier` leaf (Kind 0x0E)."
);
define_string_leaf!(
LazyJsdocText,
Kind::JsdocText,
value,
"Lazy view of a `JsdocText` leaf (Kind 0x0F)."
);
define_lazy_comment_node!(
LazyJsdocTypeLine,
Kind::JsdocTypeLine,
"Lazy view of a `JsdocTypeLine` (Kind 0x07)."
);
impl<'a> LazyJsdocTypeLine<'a> {
pub fn raw_type(&self) -> &'a str {
if self.source_file.compat_mode {
ext_string(self.source_file, self.node_index, 0)
} else {
string_payload(self.source_file, self.node_index).unwrap_or("")
}
}
}
define_lazy_comment_node!(
LazyJsdocInlineTag,
Kind::JsdocInlineTag,
"Lazy view of a `JsdocInlineTag` (Kind 0x08)."
);
impl<'a> LazyJsdocInlineTag<'a> {
#[inline]
pub fn format(&self) -> u8 {
self.common_data() & 0b0000_0111
}
pub fn namepath_or_url(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 0)
}
pub fn text(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, STRING_FIELD_SIZE)
}
pub fn raw_body(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 2 * STRING_FIELD_SIZE)
}
}
define_lazy_comment_node!(
LazyJsdocGenericTagBody,
Kind::JsdocGenericTagBody,
"Lazy view of a `JsdocGenericTagBody` (Kind 0x09)."
);
impl<'a> LazyJsdocGenericTagBody<'a> {
#[inline]
pub fn has_dash_separator(&self) -> bool {
(self.common_data() & 0b0000_0001) != 0
}
pub fn description(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, 2)
}
}
define_lazy_comment_node!(
LazyJsdocBorrowsTagBody,
Kind::JsdocBorrowsTagBody,
"Lazy view of a `JsdocBorrowsTagBody` (Kind 0x0A)."
);
define_lazy_comment_node!(
LazyJsdocParameterName,
Kind::JsdocParameterName,
"Lazy view of a `JsdocParameterName` (Kind 0x0C)."
);
impl<'a> LazyJsdocParameterName<'a> {
#[inline]
pub fn optional(&self) -> bool {
(self.common_data() & 0b0000_0001) != 0
}
pub fn path(&self) -> &'a str {
ext_string(self.source_file, self.node_index, 0)
}
pub fn default_value(&self) -> Option<&'a str> {
let ext = ext_offset(self.source_file, self.node_index) as usize;
resolve_string_field_slot(self.source_file, ext, STRING_FIELD_SIZE)
}
}
#[derive(Debug, Clone, Copy)]
pub enum LazyJsdocTagBody<'a> {
Generic(LazyJsdocGenericTagBody<'a>),
Borrows(LazyJsdocBorrowsTagBody<'a>),
Raw(LazyJsdocRawTagBody<'a>),
}
#[derive(Debug, Clone, Copy)]
pub enum LazyJsdocTagValue<'a> {
Parameter(LazyJsdocParameterName<'a>),
Namepath(LazyJsdocNamepathSource<'a>),
Identifier(LazyJsdocIdentifier<'a>),
Raw(LazyJsdocText<'a>),
}
#[cfg(test)]
mod tests {
use super::*;
use core::mem::size_of;
#[test]
fn lazy_comment_structs_are_compact() {
macro_rules! assert_size {
($t:ty) => {
assert!(
size_of::<$t>() <= 16,
concat!(stringify!($t), " exceeds 16 bytes")
);
};
}
assert_size!(LazyJsdocBlock<'static>);
assert_size!(LazyJsdocDescriptionLine<'static>);
assert_size!(LazyJsdocTag<'static>);
assert_size!(LazyJsdocTagName<'static>);
assert_size!(LazyJsdocText<'static>);
assert_size!(LazyJsdocInlineTag<'static>);
assert_size!(LazyJsdocParameterName<'static>);
}
#[test]
fn variant_wrappers_fit_in_24_bytes() {
assert!(size_of::<LazyJsdocTagBody<'static>>() <= 24);
assert!(size_of::<LazyJsdocTagValue<'static>>() <= 24);
}
}