mirror of
https://github.com/danbulant/oxc
synced 2026-05-25 04:42:10 +00:00
This PR renames the `oxc_ast_codegen` crate to `oxc_ast_tools`, It improves the readability and organization of the codebase by giving the crate a name that better reflects its purpose and contents. It also improves the error message in CI.
327 lines
10 KiB
Rust
327 lines
10 KiB
Rust
use itertools::Itertools;
|
|
use proc_macro2::{Group, TokenStream, TokenTree};
|
|
use quote::{format_ident, ToTokens};
|
|
use serde::Serialize;
|
|
use syn::{spanned::Spanned, GenericArgument, Ident, ItemMacro, PathArguments, Type, TypePath};
|
|
|
|
use crate::{codegen::EarlyCtx, TypeId};
|
|
|
|
pub trait NormalizeError<T> {
|
|
fn normalize(self) -> crate::Result<T>;
|
|
fn normalize_with<E>(self, err: E) -> crate::Result<T>
|
|
where
|
|
E: ToString;
|
|
}
|
|
|
|
impl<T, E> NormalizeError<T> for Result<T, E>
|
|
where
|
|
E: ToString,
|
|
{
|
|
fn normalize(self) -> crate::Result<T> {
|
|
self.map_err(|e| e.to_string())
|
|
}
|
|
|
|
fn normalize_with<U>(self, err: U) -> crate::Result<T>
|
|
where
|
|
U: ToString,
|
|
{
|
|
self.map_err(|_| err.to_string())
|
|
}
|
|
}
|
|
|
|
impl<T> NormalizeError<T> for Option<T> {
|
|
fn normalize(self) -> crate::Result<T> {
|
|
self.normalize_with(String::default())
|
|
}
|
|
|
|
fn normalize_with<E>(self, err: E) -> crate::Result<T>
|
|
where
|
|
E: ToString,
|
|
{
|
|
self.map_or_else(|| Err(err.to_string()), |r| Ok(r))
|
|
}
|
|
}
|
|
|
|
pub trait TokenStreamExt {
|
|
fn replace_ident(self, needle: &str, replace: &Ident) -> TokenStream;
|
|
}
|
|
|
|
pub trait TypeExt {
|
|
fn get_ident(&self) -> TypeIdentResult;
|
|
fn analyze(&self, ctx: &EarlyCtx) -> TypeAnalysis;
|
|
}
|
|
|
|
pub trait StrExt: AsRef<str> {
|
|
/// Dead simple, just adds either `s` or `es` based on the last character.
|
|
/// doesn't handle things like `sh`, `x`, `z`, etc. It also creates wrong results when the word
|
|
/// ends with `y` but there is a preceding vowl similar to `toys`,
|
|
/// It WILL output the WRONG result `toies`!
|
|
/// As an edge case would output `children` for the input `child`.
|
|
fn to_plural(self) -> String;
|
|
}
|
|
|
|
pub trait ToIdent {
|
|
fn to_ident(&self) -> Ident;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum TypeIdentResult<'a> {
|
|
Ident(&'a Ident),
|
|
Vec(Box<TypeIdentResult<'a>>),
|
|
Box(Box<TypeIdentResult<'a>>),
|
|
Option(Box<TypeIdentResult<'a>>),
|
|
Reference(Box<TypeIdentResult<'a>>),
|
|
/// We bailed on detecting wrapper
|
|
Complex(Box<TypeIdentResult<'a>>),
|
|
}
|
|
|
|
impl<'a> TypeIdentResult<'a> {
|
|
fn boxed(inner: Self) -> Self {
|
|
Self::Box(Box::new(inner))
|
|
}
|
|
|
|
fn vec(inner: Self) -> Self {
|
|
Self::Vec(Box::new(inner))
|
|
}
|
|
|
|
fn option(inner: Self) -> Self {
|
|
Self::Option(Box::new(inner))
|
|
}
|
|
|
|
fn complex(inner: Self) -> Self {
|
|
Self::Complex(Box::new(inner))
|
|
}
|
|
|
|
fn reference(inner: Self) -> Self {
|
|
Self::Reference(Box::new(inner))
|
|
}
|
|
|
|
pub fn inner_ident(&self) -> &'a Ident {
|
|
match self {
|
|
Self::Ident(it) => it,
|
|
Self::Complex(it)
|
|
| Self::Vec(it)
|
|
| Self::Box(it)
|
|
| Self::Option(it)
|
|
| Self::Reference(it) => it.inner_ident(),
|
|
}
|
|
}
|
|
|
|
pub fn as_ident(&self) -> Option<&'a Ident> {
|
|
if let Self::Ident(it) = self {
|
|
Some(it)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: remove me
|
|
#[allow(dead_code)]
|
|
#[derive(Debug, PartialEq, Clone, Serialize)]
|
|
pub enum TypeWrapper {
|
|
None,
|
|
Box,
|
|
Vec,
|
|
Opt,
|
|
VecBox,
|
|
VecOpt,
|
|
OptBox,
|
|
OptVec,
|
|
Ref,
|
|
/// We bailed on detecting the type wrapper
|
|
Complex,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize)]
|
|
pub struct TypeAnalysis {
|
|
pub type_id: Option<TypeId>,
|
|
pub wrapper: TypeWrapper,
|
|
// pub name: String,
|
|
#[serde(skip)]
|
|
pub typ: Type,
|
|
}
|
|
|
|
impl TypeExt for Type {
|
|
fn get_ident(&self) -> TypeIdentResult {
|
|
match self {
|
|
Type::Path(TypePath { path, .. }) => {
|
|
let seg1 = path.segments.first().unwrap();
|
|
match &seg1.arguments {
|
|
PathArguments::None => TypeIdentResult::Ident(&seg1.ident),
|
|
PathArguments::AngleBracketed(it) => {
|
|
let args = &it.args.iter().collect_vec();
|
|
assert!(args.len() < 3, "Max path arguments here is 2, eg `Box<'a, Adt>`");
|
|
if let Some(second) = args.get(1) {
|
|
let GenericArgument::Type(second) = second else { panic!() };
|
|
let inner = second.get_ident();
|
|
if seg1.ident == "Box" {
|
|
TypeIdentResult::boxed(inner)
|
|
} else if seg1.ident == "Vec" {
|
|
TypeIdentResult::vec(inner)
|
|
} else {
|
|
panic!();
|
|
}
|
|
} else {
|
|
match args.first() {
|
|
Some(GenericArgument::Type(it)) => {
|
|
let inner = it.get_ident();
|
|
if seg1.ident == "Option" {
|
|
TypeIdentResult::option(inner)
|
|
} else {
|
|
TypeIdentResult::complex(inner)
|
|
}
|
|
}
|
|
Some(GenericArgument::Lifetime(_)) => {
|
|
TypeIdentResult::Ident(&seg1.ident)
|
|
}
|
|
_ => panic!("unsupported type!"),
|
|
}
|
|
}
|
|
}
|
|
PathArguments::Parenthesized(_) => {
|
|
panic!("Parenthesized path arguments aren't supported!")
|
|
}
|
|
}
|
|
}
|
|
Type::Reference(typ) => TypeIdentResult::reference(typ.elem.get_ident()),
|
|
_ => panic!("Unsupported type."),
|
|
}
|
|
}
|
|
|
|
fn analyze(&self, ctx: &EarlyCtx) -> TypeAnalysis {
|
|
fn analyze<'a>(res: &'a TypeIdentResult) -> Option<(&'a Ident, TypeWrapper)> {
|
|
let mut wrapper = TypeWrapper::None;
|
|
let ident = match res {
|
|
TypeIdentResult::Ident(inner) => inner,
|
|
TypeIdentResult::Complex(inner) => {
|
|
wrapper = TypeWrapper::Complex;
|
|
let (inner, _) = analyze(inner)?;
|
|
inner
|
|
}
|
|
TypeIdentResult::Box(inner) => {
|
|
wrapper = TypeWrapper::Box;
|
|
let (inner, inner_kind) = analyze(inner)?;
|
|
assert!(inner_kind == TypeWrapper::None,);
|
|
inner
|
|
}
|
|
TypeIdentResult::Vec(inner) => {
|
|
wrapper = TypeWrapper::Vec;
|
|
let (inner, inner_kind) = analyze(inner)?;
|
|
if inner_kind == TypeWrapper::Opt {
|
|
wrapper = TypeWrapper::VecOpt;
|
|
} else if inner_kind != TypeWrapper::None {
|
|
panic!();
|
|
}
|
|
inner
|
|
}
|
|
TypeIdentResult::Option(inner) => {
|
|
wrapper = TypeWrapper::Opt;
|
|
let (inner, inner_kind) = analyze(inner)?;
|
|
if inner_kind == TypeWrapper::Vec {
|
|
wrapper = TypeWrapper::OptVec;
|
|
} else if inner_kind == TypeWrapper::Box {
|
|
wrapper = TypeWrapper::OptBox;
|
|
} else if inner_kind != TypeWrapper::None {
|
|
panic!();
|
|
}
|
|
inner
|
|
}
|
|
TypeIdentResult::Reference(_) => return None,
|
|
};
|
|
Some((ident, wrapper))
|
|
}
|
|
let type_ident = self.get_ident();
|
|
let Some((type_ident, wrapper)) = analyze(&type_ident) else {
|
|
return TypeAnalysis { type_id: None, wrapper: TypeWrapper::Ref, typ: self.clone() };
|
|
};
|
|
|
|
let type_id = ctx.type_id(&type_ident.to_string());
|
|
TypeAnalysis { type_id, wrapper, typ: self.clone() }
|
|
}
|
|
}
|
|
|
|
impl<T: AsRef<str>> StrExt for T {
|
|
fn to_plural(self) -> String {
|
|
let txt = self.as_ref();
|
|
if txt.is_empty() {
|
|
return String::default();
|
|
}
|
|
|
|
let mut txt = txt.to_string();
|
|
if txt.ends_with("child") {
|
|
txt.push_str("ren");
|
|
} else {
|
|
match txt.chars().last() {
|
|
Some('s') => {
|
|
txt.push_str("es");
|
|
}
|
|
Some('y') => {
|
|
txt.pop();
|
|
txt.push_str("ies");
|
|
}
|
|
_ => txt.push('s'),
|
|
}
|
|
}
|
|
txt
|
|
}
|
|
}
|
|
|
|
impl TokenStreamExt for TokenStream {
|
|
fn replace_ident(self, needle: &str, replace: &Ident) -> TokenStream {
|
|
self.into_iter()
|
|
.map(|it| match it {
|
|
TokenTree::Ident(ident) if ident == needle => replace.to_token_stream(),
|
|
TokenTree::Group(group) => {
|
|
Group::new(group.delimiter(), group.stream().replace_ident(needle, replace))
|
|
.to_token_stream()
|
|
}
|
|
_ => it.to_token_stream(),
|
|
})
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
// From https://doc.rust-lang.org/reference/keywords.html
|
|
#[rustfmt::skip]
|
|
static RESERVED_NAMES: &[&str] = &[
|
|
// Strict keywords
|
|
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if",
|
|
"impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self",
|
|
"static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where", "while", "async",
|
|
"await", "dyn",
|
|
// Reserved keywords
|
|
"abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof", "unsized",
|
|
"virtual", "yield", "try",
|
|
// Weak keywords
|
|
"macro_rules", "union", // "dyn" also listed as a weak keyword, but is already on strict list
|
|
];
|
|
|
|
impl<S> ToIdent for S
|
|
where
|
|
S: AsRef<str>,
|
|
{
|
|
fn to_ident(&self) -> Ident {
|
|
let name = self.as_ref();
|
|
if RESERVED_NAMES.contains(&name) {
|
|
format_ident!("r#{name}")
|
|
} else {
|
|
format_ident!("{name}")
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn write_all_to<S: AsRef<std::path::Path>>(data: &[u8], path: S) -> std::io::Result<()> {
|
|
use std::{fs, io::Write};
|
|
let path = path.as_ref();
|
|
if let Some(parent) = path.parent() {
|
|
fs::create_dir_all(parent)?;
|
|
}
|
|
let mut file = fs::File::create(path)?;
|
|
file.write_all(data)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn unexpanded_macro_err(mac: &ItemMacro) -> String {
|
|
format!("Unexpanded macro: {:?}:{:?}", mac.ident, mac.span())
|
|
}
|