use crate::common::{Doc, Ident, Literal, Path};
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use alloc::vec::Vec;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct File {
pub doc: Doc,
pub imports: Vec<Import>,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Import {
pub module: String,
pub type_only: bool,
pub default: Option<Ident>,
pub names: Vec<ImportName>,
pub namespace: Option<Ident>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ImportName {
pub name: Ident,
pub alias: Option<Ident>,
pub type_only: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Decl {
Interface(Interface),
Class(Class),
TypeAlias(TypeAlias),
Enum(Enum),
Variable(Variable),
Function(Function),
Namespace(Namespace),
Statement(Statement),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Interface {
pub doc: Doc,
pub exported: bool,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub extends: Vec<Type>,
pub members: Vec<Signature>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Class {
pub doc: Doc,
pub exported: bool,
pub abstract_: bool,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub extends: Option<Type>,
pub implements: Vec<Type>,
pub members: Vec<ClassMember>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeAlias {
pub doc: Doc,
pub exported: bool,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Enum {
pub doc: Doc,
pub exported: bool,
pub const_: bool,
pub name: Ident,
pub members: Vec<EnumMember>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumMember {
pub doc: Doc,
pub name: PropertyName,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Variable {
pub doc: Doc,
pub exported: bool,
pub declare: bool,
pub kind: VarKind,
pub name: Ident,
pub type_: Option<Type>,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VarKind {
Const,
Let,
Var,
}
impl VarKind {
pub const ALL: [Self; 3] = [Self::Const, Self::Let, Self::Var];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Const => "const",
Self::Let => "let",
Self::Var => "var",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Function {
pub doc: Doc,
pub exported: bool,
pub name: Ident,
pub generics: Vec<TypeParam>,
pub params: Vec<Param>,
pub returns: Option<Type>,
pub body: Option<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Namespace {
pub doc: Doc,
pub exported: bool,
pub declare: bool,
pub name: Ident,
pub decls: Vec<Decl>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Signature {
Property(Property),
Method(MethodSignature),
Index(IndexSignature),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Property {
pub doc: Doc,
pub readonly: bool,
pub name: PropertyName,
pub optional: bool,
pub type_: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MethodSignature {
pub doc: Doc,
pub name: PropertyName,
pub optional: bool,
pub generics: Vec<TypeParam>,
pub params: Vec<Param>,
pub returns: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IndexSignature {
pub doc: Doc,
pub readonly: bool,
pub key: Ident,
pub key_type: Type,
pub value_type: Type,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ClassMember {
Field(Field),
Method(Method),
Constructor(Constructor),
Comment(Doc),
Verbatim(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Field {
pub doc: Doc,
pub accessibility: Option<Accessibility>,
pub static_: bool,
pub readonly: bool,
pub name: PropertyName,
pub optional: bool,
pub definite: bool,
pub type_: Option<Type>,
pub value: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Method {
pub doc: Doc,
pub accessibility: Option<Accessibility>,
pub static_: bool,
pub abstract_: bool,
pub name: PropertyName,
pub generics: Vec<TypeParam>,
pub params: Vec<Param>,
pub returns: Option<Type>,
pub body: Option<Block>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Constructor {
pub doc: Doc,
pub accessibility: Option<Accessibility>,
pub params: Vec<Param>,
pub body: Block,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Accessibility {
Public,
Protected,
Private,
}
impl Accessibility {
pub const ALL: [Self; 3] = [Self::Public, Self::Protected, Self::Private];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Public => "public",
Self::Protected => "protected",
Self::Private => "private",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param {
pub name: Ident,
pub optional: bool,
pub rest: bool,
pub type_: Option<Type>,
pub default: Option<Expr>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeParam {
pub name: Ident,
pub extends: Option<Type>,
pub default: Option<Type>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PropertyName {
Ident(Ident),
Quoted(String),
}
impl PropertyName {
#[must_use]
pub fn ident(name: &str) -> Self {
Self::Ident(Ident::new(name))
}
#[must_use]
pub fn as_str(&self) -> &str {
match self {
Self::Ident(ident) => ident.as_str(),
Self::Quoted(text) => text,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Type {
Keyword(Keyword),
Named {
path: Path,
args: Vec<Type>,
},
Array(Box<Type>),
ReadonlyArray(Box<Type>),
Tuple(Vec<Type>),
Union(Vec<Type>),
Intersection(Vec<Type>),
Object(Vec<Signature>),
Literal(Literal),
Function {
params: Vec<Param>,
returns: Box<Type>,
},
Parenthesised(Box<Type>),
TypeOf(Path),
KeyOf(Box<Type>),
Predicate {
param: Ident,
type_: Box<Type>,
},
Indexed {
object: Box<Type>,
index: Box<Type>,
},
}
impl Type {
#[must_use]
pub fn named(path: Path) -> Self {
Self::Named {
path,
args: Vec::new(),
}
}
#[must_use]
pub fn generic(path: Path, args: Vec<Self>) -> Self {
Self::Named { path, args }
}
#[must_use]
pub const fn number() -> Self {
Self::Keyword(Keyword::Number)
}
#[must_use]
pub const fn string() -> Self {
Self::Keyword(Keyword::String)
}
#[must_use]
pub const fn bigint() -> Self {
Self::Keyword(Keyword::BigInt)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Keyword {
Number,
BigInt,
String,
Boolean,
Unknown,
Any,
Never,
Void,
Undefined,
Null,
Object,
Symbol,
}
impl Keyword {
pub const ALL: [Self; 12] = [
Self::Number,
Self::BigInt,
Self::String,
Self::Boolean,
Self::Unknown,
Self::Any,
Self::Never,
Self::Void,
Self::Undefined,
Self::Null,
Self::Object,
Self::Symbol,
];
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Number => "number",
Self::BigInt => "bigint",
Self::String => "string",
Self::Boolean => "boolean",
Self::Unknown => "unknown",
Self::Any => "any",
Self::Never => "never",
Self::Void => "void",
Self::Undefined => "undefined",
Self::Null => "null",
Self::Object => "object",
Self::Symbol => "symbol",
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Block {
pub statements: Vec<Statement>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Statement {
Declare {
kind: VarKind,
name: Ident,
type_: Option<Type>,
value: Option<Expr>,
},
Expr(Expr),
Return(Option<Expr>),
If {
condition: Expr,
then: Block,
otherwise: Option<Block>,
},
For {
counter: Ident,
bound: Box<Expr>,
body: Block,
},
While {
condition: Box<Expr>,
body: Block,
},
Break,
ForOf {
kind: VarKind,
binding: Ident,
range: Expr,
body: Block,
},
Switch {
value: Expr,
cases: Vec<SwitchCase>,
},
Throw(Expr),
Comment(Doc),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SwitchCase {
pub labels: Vec<Expr>,
pub body: Block,
pub breaks: bool,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Expr {
Literal(Literal),
Path(Path),
Array(Vec<Expr>),
Object(Vec<(PropertyName, Expr)>),
New {
callee: Path,
args: Vec<Expr>,
},
Call {
callee: Box<Expr>,
args: Vec<Expr>,
},
Member {
object: Box<Expr>,
name: Ident,
},
Index {
object: Box<Expr>,
index: Box<Expr>,
},
Paren(Box<Expr>),
Binary {
op: &'static str,
lhs: Box<Expr>,
rhs: Box<Expr>,
},
Unary {
op: &'static str,
value: Box<Expr>,
},
As {
value: Box<Expr>,
type_: Box<Type>,
},
AsConst(Box<Expr>),
Ternary {
condition: Box<Expr>,
then: Box<Expr>,
otherwise: Box<Expr>,
},
Arrow {
params: Vec<Ident>,
body: Box<Expr>,
},
Parenthesised(Box<Expr>),
NonNull(Box<Expr>),
This,
Undefined,
}
#[must_use]
pub fn escape_keyword(name: &str) -> String {
if RESERVED.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
name.to_string()
}
#[must_use]
pub fn escape_member(name: &str) -> String {
if RESERVED_MEMBERS.contains(&name) {
let mut out = name.to_string();
out.push('_');
return out;
}
name.to_string()
}
#[must_use]
pub fn is_reserved(name: &str) -> bool {
RESERVED.contains(&name)
}
#[must_use]
pub fn is_reserved_member(name: &str) -> bool {
RESERVED_MEMBERS.contains(&name)
}
const RESERVED: &[&str] = &[
"any",
"as",
"await",
"bigint",
"boolean",
"break",
"case",
"catch",
"class",
"const",
"continue",
"debugger",
"default",
"delete",
"do",
"else",
"enum",
"export",
"extends",
"false",
"finally",
"for",
"function",
"if",
"implements",
"import",
"in",
"instanceof",
"interface",
"let",
"never",
"new",
"null",
"number",
"object",
"package",
"private",
"protected",
"public",
"readonly",
"return",
"static",
"string",
"super",
"switch",
"symbol",
"this",
"throw",
"true",
"try",
"typeof",
"undefined",
"unknown",
"var",
"void",
"while",
"with",
"yield",
];
const RESERVED_MEMBERS: &[&str] = &["constructor"];
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
use super::*;
#[test]
fn every_type_keyword_writes_its_own_spelling() {
let mut seen = Vec::new();
for keyword in Keyword::ALL {
assert!(!seen.contains(&keyword.as_str()), "{}", keyword.as_str());
seen.push(keyword.as_str());
}
assert_eq!(seen.len(), 12);
}
#[test]
fn a_reserved_binding_escapes_with_a_trailing_underscore() {
assert_eq!(escape_keyword("class"), "class_");
assert_eq!(escape_keyword("package"), "package_");
assert_eq!(escape_keyword("null"), "null_");
assert!(is_reserved("class"));
}
#[test]
fn a_contextual_keyword_is_a_legal_binding_and_keeps_its_spelling() {
for name in ["type", "namespace", "declare", "is", "from", "async"] {
assert_eq!(escape_keyword(name), name);
assert!(!is_reserved(name));
}
}
#[test]
fn a_predefined_type_name_is_reserved_although_javascript_does_not_reserve_it() {
for name in [
"any",
"bigint",
"boolean",
"never",
"number",
"object",
"string",
"symbol",
"undefined",
"unknown",
"readonly",
"as",
] {
assert!(is_reserved(name), "{name}");
assert_eq!(escape_keyword(name), alloc::format!("{name}_"));
}
}
#[test]
fn a_reserved_word_is_still_a_legal_member_name() {
for name in ["new", "class", "delete", "static", "typeof", "function"] {
assert_eq!(escape_member(name), name);
assert!(!is_reserved_member(name));
}
}
#[test]
fn only_constructor_moves_in_the_member_space() {
assert_eq!(escape_member("constructor"), "constructor_");
assert!(is_reserved_member("constructor"));
}
#[test]
fn a_property_name_keeps_its_text_whichever_form_it_takes() {
assert_eq!(PropertyName::ident("value").as_str(), "value");
assert_eq!(PropertyName::Quoted("a-b".to_string()).as_str(), "a-b");
}
#[test]
fn a_bigint_literal_type_carries_its_suffix_in_the_written_form() {
let narrow = Type::Literal(Literal::Int("2".to_string()));
let wide = Type::Literal(Literal::Int("2n".to_string()));
assert_ne!(narrow, wide);
}
#[test]
fn accessibility_is_its_own_enum_and_not_the_common_visibility() {
let mut seen = Vec::new();
for level in Accessibility::ALL {
assert!(!seen.contains(&level.as_str()));
seen.push(level.as_str());
}
assert_eq!(seen, alloc::vec!["public", "protected", "private"]);
}
}