oxc/tasks/ast_tools/src/util.rs
rzvxa 8e8fcd0584 refactor(ast_tools): rename oxc_ast_codegen to oxc_ast_tools. (#4846)
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.
2024-08-12 14:33:58 +00:00

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())
}