use core::fmt;
#[repr(u8)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Kind {
Sentinel = 0x00,
JsdocBlock = 0x01,
JsdocDescriptionLine = 0x02,
JsdocTag = 0x03,
JsdocTagName = 0x04,
JsdocTagNameValue = 0x05,
JsdocTypeSource = 0x06,
JsdocTypeLine = 0x07,
JsdocInlineTag = 0x08,
JsdocGenericTagBody = 0x09,
JsdocBorrowsTagBody = 0x0A,
JsdocRawTagBody = 0x0B,
JsdocParameterName = 0x0C,
JsdocNamepathSource = 0x0D,
JsdocIdentifier = 0x0E,
JsdocText = 0x0F,
NodeList = 0x7F,
TypeName = 0x80,
TypeNumber = 0x81,
TypeStringValue = 0x82,
TypeNull = 0x83,
TypeUndefined = 0x84,
TypeAny = 0x85,
TypeUnknown = 0x86,
TypeUnion = 0x87,
TypeIntersection = 0x88,
TypeGeneric = 0x89,
TypeFunction = 0x8A,
TypeObject = 0x8B,
TypeTuple = 0x8C,
TypeParenthesis = 0x8D,
TypeNamePath = 0x8E,
TypeSpecialNamePath = 0x8F,
TypeNullable = 0x90,
TypeNotNullable = 0x91,
TypeOptional = 0x92,
TypeVariadic = 0x93,
TypeConditional = 0x94,
TypeInfer = 0x95,
TypeKeyOf = 0x96,
TypeTypeOf = 0x97,
TypeImport = 0x98,
TypePredicate = 0x99,
TypeAsserts = 0x9A,
TypeAssertsPlain = 0x9B,
TypeReadonlyArray = 0x9C,
TypeTemplateLiteral = 0x9D,
TypeUniqueSymbol = 0x9E,
TypeSymbol = 0x9F,
TypeObjectField = 0xA0,
TypeJsdocObjectField = 0xA1,
TypeKeyValue = 0xA2,
TypeProperty = 0xA3,
TypeIndexSignature = 0xA4,
TypeMappedType = 0xA5,
TypeTypeParameter = 0xA6,
TypeCallSignature = 0xA7,
TypeConstructorSignature = 0xA8,
TypeMethodSignature = 0xA9,
TypeIndexedAccessIndex = 0xAA,
TypeParameterList = 0xAB,
TypeReadonlyProperty = 0xAC,
}
pub const FIRST_RESERVED_KIND: u8 = 0x40;
pub const LAST_RESERVED_KIND: u8 = 0x7E;
pub const FIRST_TYPE_NODE_KIND: u8 = 0x80;
pub const LAST_TYPE_NODE_KIND_IN_USE: u8 = 0xAC;
pub const FIRST_COMMENT_AST_KIND: u8 = 0x01;
pub const LAST_COMMENT_AST_KIND_IN_USE: u8 = 0x0F;
pub const SENTINEL_KIND: u8 = 0x00;
pub const NODE_LIST_KIND: u8 = 0x7F;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnknownKind(pub u8);
impl fmt::Display for UnknownKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unknown Kind discriminant 0x{:02X}", self.0)
}
}
impl Kind {
#[inline]
pub const fn from_u8(value: u8) -> Result<Self, UnknownKind> {
match value {
0x00 => Ok(Kind::Sentinel),
0x01 => Ok(Kind::JsdocBlock),
0x02 => Ok(Kind::JsdocDescriptionLine),
0x03 => Ok(Kind::JsdocTag),
0x04 => Ok(Kind::JsdocTagName),
0x05 => Ok(Kind::JsdocTagNameValue),
0x06 => Ok(Kind::JsdocTypeSource),
0x07 => Ok(Kind::JsdocTypeLine),
0x08 => Ok(Kind::JsdocInlineTag),
0x09 => Ok(Kind::JsdocGenericTagBody),
0x0A => Ok(Kind::JsdocBorrowsTagBody),
0x0B => Ok(Kind::JsdocRawTagBody),
0x0C => Ok(Kind::JsdocParameterName),
0x0D => Ok(Kind::JsdocNamepathSource),
0x0E => Ok(Kind::JsdocIdentifier),
0x0F => Ok(Kind::JsdocText),
0x7F => Ok(Kind::NodeList),
0x80 => Ok(Kind::TypeName),
0x81 => Ok(Kind::TypeNumber),
0x82 => Ok(Kind::TypeStringValue),
0x83 => Ok(Kind::TypeNull),
0x84 => Ok(Kind::TypeUndefined),
0x85 => Ok(Kind::TypeAny),
0x86 => Ok(Kind::TypeUnknown),
0x87 => Ok(Kind::TypeUnion),
0x88 => Ok(Kind::TypeIntersection),
0x89 => Ok(Kind::TypeGeneric),
0x8A => Ok(Kind::TypeFunction),
0x8B => Ok(Kind::TypeObject),
0x8C => Ok(Kind::TypeTuple),
0x8D => Ok(Kind::TypeParenthesis),
0x8E => Ok(Kind::TypeNamePath),
0x8F => Ok(Kind::TypeSpecialNamePath),
0x90 => Ok(Kind::TypeNullable),
0x91 => Ok(Kind::TypeNotNullable),
0x92 => Ok(Kind::TypeOptional),
0x93 => Ok(Kind::TypeVariadic),
0x94 => Ok(Kind::TypeConditional),
0x95 => Ok(Kind::TypeInfer),
0x96 => Ok(Kind::TypeKeyOf),
0x97 => Ok(Kind::TypeTypeOf),
0x98 => Ok(Kind::TypeImport),
0x99 => Ok(Kind::TypePredicate),
0x9A => Ok(Kind::TypeAsserts),
0x9B => Ok(Kind::TypeAssertsPlain),
0x9C => Ok(Kind::TypeReadonlyArray),
0x9D => Ok(Kind::TypeTemplateLiteral),
0x9E => Ok(Kind::TypeUniqueSymbol),
0x9F => Ok(Kind::TypeSymbol),
0xA0 => Ok(Kind::TypeObjectField),
0xA1 => Ok(Kind::TypeJsdocObjectField),
0xA2 => Ok(Kind::TypeKeyValue),
0xA3 => Ok(Kind::TypeProperty),
0xA4 => Ok(Kind::TypeIndexSignature),
0xA5 => Ok(Kind::TypeMappedType),
0xA6 => Ok(Kind::TypeTypeParameter),
0xA7 => Ok(Kind::TypeCallSignature),
0xA8 => Ok(Kind::TypeConstructorSignature),
0xA9 => Ok(Kind::TypeMethodSignature),
0xAA => Ok(Kind::TypeIndexedAccessIndex),
0xAB => Ok(Kind::TypeParameterList),
0xAC => Ok(Kind::TypeReadonlyProperty),
other => Err(UnknownKind(other)),
}
}
#[inline]
#[must_use]
pub const fn as_u8(self) -> u8 {
self as u8
}
}
#[inline]
#[must_use]
pub const fn is_type_node(kind: u8) -> bool {
(kind & 0x80) != 0
}
#[inline]
#[must_use]
pub const fn is_node_list(kind: u8) -> bool {
kind == NODE_LIST_KIND
}
#[inline]
#[must_use]
pub const fn is_sentinel(kind: u8) -> bool {
kind == SENTINEL_KIND
}
#[inline]
#[must_use]
pub const fn is_comment_ast(kind: u8) -> bool {
(kind & 0xC0) == 0x00 && kind != SENTINEL_KIND
}
#[inline]
#[must_use]
pub const fn is_reserved(kind: u8) -> bool {
(kind & 0xC0) == 0x40 && kind != NODE_LIST_KIND
}
#[inline]
#[must_use]
pub const fn is_known(kind: u8) -> bool {
Kind::from_u8(kind).is_ok()
}
#[cfg(test)]
mod tests {
use super::*;
const EXPECTED_KNOWN_KINDS: usize = 62;
#[test]
fn from_u8_recognizes_all_62_known_discriminants() {
let mut count = 0usize;
for byte in 0u8..=u8::MAX {
if Kind::from_u8(byte).is_ok() {
count += 1;
}
}
assert_eq!(count, EXPECTED_KNOWN_KINDS);
}
#[test]
fn from_u8_round_trips_for_all_known_kinds() {
for byte in 0u8..=u8::MAX {
if let Ok(kind) = Kind::from_u8(byte) {
assert_eq!(kind.as_u8(), byte);
}
}
}
#[test]
fn category_partition_covers_every_byte() {
for byte in 0u8..=u8::MAX {
let mut hits = 0;
if is_sentinel(byte) {
hits += 1;
}
if is_comment_ast(byte) {
hits += 1;
}
if is_reserved(byte) {
hits += 1;
}
if is_node_list(byte) {
hits += 1;
}
if is_type_node(byte) {
hits += 1;
}
assert_eq!(hits, 1, "byte 0x{byte:02X} matched {hits} categories");
}
}
#[test]
fn type_node_range_count_matches_msb_bytes() {
let count = (0u8..=u8::MAX).filter(|&b| is_type_node(b)).count();
assert_eq!(count, 128);
}
#[test]
fn comment_ast_range_count_is_63() {
let count = (0u8..=u8::MAX).filter(|&b| is_comment_ast(b)).count();
assert_eq!(count, 63);
}
#[test]
fn reserved_range_count_is_63() {
let count = (0u8..=u8::MAX).filter(|&b| is_reserved(b)).count();
assert_eq!(count, 63);
}
#[test]
fn known_kinds_are_a_subset_of_categorized_bytes() {
for byte in 0u8..=u8::MAX {
if Kind::from_u8(byte).is_ok() {
assert!(
is_sentinel(byte)
|| is_node_list(byte)
|| is_comment_ast(byte)
|| is_type_node(byte),
"known Kind 0x{byte:02X} fell into reserved range"
);
assert!(!is_reserved(byte));
}
}
}
#[test]
fn unknown_kind_error_carries_the_byte() {
let err = Kind::from_u8(0x40).unwrap_err();
assert_eq!(err.0, 0x40);
}
#[test]
fn first_and_last_constants_match_enum() {
assert_eq!(Kind::Sentinel.as_u8(), SENTINEL_KIND);
assert_eq!(Kind::JsdocBlock.as_u8(), FIRST_COMMENT_AST_KIND);
assert_eq!(Kind::JsdocText.as_u8(), LAST_COMMENT_AST_KIND_IN_USE);
assert_eq!(Kind::NodeList.as_u8(), NODE_LIST_KIND);
assert_eq!(Kind::TypeName.as_u8(), FIRST_TYPE_NODE_KIND);
assert_eq!(
Kind::TypeReadonlyProperty.as_u8(),
LAST_TYPE_NODE_KIND_IN_USE
);
}
}