refactor(ast_tools): centralize logic for outputting files (#6900)

Move all logic related to formatting and outputting files into one
place.
This commit is contained in:
overlookmotel 2024-10-25 22:08:58 +01:00 committed by GitHub
parent 26de0de326
commit 5f44d84744
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 209 additions and 213 deletions

View file

@ -1,18 +1,16 @@
use std::{cell::RefCell, path::PathBuf};
use itertools::Itertools;
use proc_macro2::TokenStream;
use rustc_hash::{FxBuildHasher, FxHashMap};
use crate::{
derives::{Derive, DeriveOutput},
fmt::pretty_print,
generators::{Generator, GeneratorOutput},
derives::Derive,
generators::Generator,
log, logln,
output::RawOutput,
passes::Pass,
rust_ast::{self, AstRef},
schema::{lower_ast_types, Schema, TypeDef},
util::write_all_to,
Result, TypeId,
};
@ -20,50 +18,13 @@ use crate::{
pub struct AstCodegen {
files: Vec<PathBuf>,
passes: Vec<Box<dyn Runner<Output = (), Context = EarlyCtx>>>,
generators: Vec<Box<dyn Runner<Output = GeneratorOutput, Context = LateCtx>>>,
derives: Vec<Box<dyn Runner<Output = DeriveOutput, Context = LateCtx>>>,
generators: Vec<Box<dyn Runner<Output = RawOutput, Context = LateCtx>>>,
derives: Vec<Box<dyn Runner<Output = Vec<RawOutput>, Context = LateCtx>>>,
}
pub struct AstCodegenResult {
pub schema: Schema,
pub outputs: Vec<SideEffect>,
}
pub struct SideEffect {
pub path: PathBuf,
pub content: Vec<u8>,
}
impl SideEffect {
/// Apply the side-effect
pub fn apply(self) -> std::io::Result<()> {
let Self { path, content } = self;
let path = path.into_os_string();
let path = path.to_str().unwrap();
write_all_to(&content, path)?;
Ok(())
}
pub fn path(&self) -> String {
let path = self.path.to_string_lossy();
path.replace('\\', "/")
}
}
impl From<(PathBuf, TokenStream)> for SideEffect {
fn from((path, stream): (PathBuf, TokenStream)) -> Self {
let content = pretty_print(&stream);
Self { path, content: content.into() }
}
}
impl From<GeneratorOutput> for SideEffect {
fn from(output: GeneratorOutput) -> Self {
match output {
GeneratorOutput::Rust { path, tokens } => Self::from((path, tokens)),
GeneratorOutput::Javascript { path, code } => Self { path, content: code.into() },
}
}
pub outputs: Vec<RawOutput>,
}
pub trait Runner {
@ -162,7 +123,7 @@ impl AstCodegen {
#[must_use]
pub fn generate<G>(mut self, generator: G) -> Self
where
G: Generator + Runner<Output = GeneratorOutput, Context = LateCtx> + 'static,
G: Generator + Runner<Output = RawOutput, Context = LateCtx> + 'static,
{
self.generators.push(Box::new(generator));
self
@ -171,7 +132,7 @@ impl AstCodegen {
#[must_use]
pub fn derive<D>(mut self, derive: D) -> Self
where
D: Derive + Runner<Output = DeriveOutput, Context = LateCtx> + 'static,
D: Derive + Runner<Output = Vec<RawOutput>, Context = LateCtx> + 'static,
{
self.derives.push(Box::new(derive));
self
@ -213,24 +174,19 @@ impl AstCodegen {
}
result
})
.map_ok(|output| output.0.into_iter().map(SideEffect::from))
.flatten_ok();
let outputs = self
.generators
.into_iter()
.map(|mut runner| {
let name = runner.name();
log!("Generate {name}... ");
let result = runner.run(&ctx);
if result.is_ok() {
logln!("Done!");
} else {
logln!("Fail!");
}
result
})
.map_ok(SideEffect::from);
let outputs = self.generators.into_iter().map(|mut runner| {
let name = runner.name();
log!("Generate {name}... ");
let result = runner.run(&ctx);
if result.is_ok() {
logln!("Done!");
} else {
logln!("Fail!");
}
result
});
let outputs = derives.chain(outputs).collect::<Result<Vec<_>>>()?;
@ -242,27 +198,3 @@ impl AstCodegen {
pub trait CodegenBase {
fn file_path() -> &'static str;
}
/// Creates a generated file warning + required information for a generated file.
pub fn generate_rust_header(file_path: &str) -> TokenStream {
let file_path = file_path.replace('\\', "/");
// TODO: Add generation date, AST source hash, etc here.
let edit_comment = format!("@ To edit this generated file you have to edit `{file_path}`");
quote::quote! {
//!@ Auto-generated code, DO NOT EDIT DIRECTLY!
#![doc = #edit_comment]
//!@@line_break
}
}
/// Creates a generated file warning + required information for a generated file.
pub fn generate_javascript_header(file_path: &str) -> String {
let file_path = file_path.replace('\\', "/");
// TODO: Add generation date, AST source hash, etc here.
format!(
"// Auto-generated code, DO NOT EDIT DIRECTLY!\n\
// To edit this generated file you have to edit `{file_path}`\n\n"
)
}

View file

@ -1,12 +1,11 @@
use std::path::PathBuf;
use convert_case::{Case, Casing};
use itertools::Itertools;
use proc_macro2::TokenStream;
use rustc_hash::{FxHashMap, FxHashSet};
use crate::{
codegen::{generate_rust_header, CodegenBase, LateCtx},
codegen::{CodegenBase, LateCtx},
output::{output_path, Output, RawOutput},
schema::TypeDef,
Result,
};
@ -23,9 +22,6 @@ pub use content_hash::DeriveContentHash;
pub use estree::DeriveESTree;
pub use get_span::{DeriveGetSpan, DeriveGetSpanMut};
#[derive(Debug, Clone)]
pub struct DeriveOutput(pub Vec<(PathBuf, TokenStream)>);
pub trait Derive: CodegenBase {
// Methods defined by implementer
@ -44,7 +40,6 @@ pub trait Derive: CodegenBase {
// Standard methods
fn template(module_paths: Vec<&str>, impls: TokenStream) -> TokenStream {
let header = generate_rust_header(Self::file_path());
let prelude = Self::prelude();
// from `x::y::z` to `crate::y::z::*`
@ -63,8 +58,6 @@ pub trait Derive: CodegenBase {
});
quote::quote! {
#header
#prelude
#(#use_modules)*
@ -74,7 +67,7 @@ pub trait Derive: CodegenBase {
}
}
fn output(&mut self, ctx: &LateCtx) -> Result<DeriveOutput> {
fn output(&mut self, ctx: &LateCtx) -> Result<Vec<RawOutput>> {
let trait_name = Self::trait_name();
let filename = format!("derive_{}.rs", Self::snake_name());
let output = ctx
@ -102,12 +95,12 @@ pub trait Derive: CodegenBase {
let mut modules = Vec::from_iter(modules);
modules.sort_unstable();
acc.push((
crate::output(
let output = Output::Rust {
path: output_path(
format!("crates/{}", path.split("::").next().unwrap()).as_str(),
&filename,
),
Self::template(
tokens: Self::template(
modules,
streams.into_iter().fold(TokenStream::new(), |mut acc, it| {
acc.extend(quote::quote! {
@ -117,10 +110,12 @@ pub trait Derive: CodegenBase {
acc
}),
),
));
};
acc.push(output.output(Self::file_path()));
acc
});
Ok(DeriveOutput(output))
Ok(output)
}
}
@ -129,7 +124,7 @@ macro_rules! define_derive {
const _: () = {
use $crate::{
codegen::{CodegenBase, LateCtx, Runner},
derives::DeriveOutput,
output::RawOutput,
Result,
};
@ -141,13 +136,13 @@ macro_rules! define_derive {
impl $($lifetime)? Runner for $ident $($lifetime)? {
type Context = LateCtx;
type Output = DeriveOutput;
type Output = Vec<RawOutput>;
fn name(&self) -> &'static str {
stringify!($ident)
}
fn run(&mut self, ctx: &LateCtx) -> Result<DeriveOutput> {
fn run(&mut self, ctx: &LateCtx) -> Result<Vec<RawOutput>> {
self.output(ctx)
}
}

View file

@ -5,10 +5,10 @@ use syn::Type;
use super::define_generator;
use crate::{
codegen::LateCtx,
output,
output::{output_path, Output},
schema::{FieldDef, ToType, TypeDef},
util::ToIdent,
Generator, GeneratorOutput,
Generator,
};
pub struct AssertLayouts;
@ -16,7 +16,7 @@ pub struct AssertLayouts;
define_generator!(AssertLayouts);
impl Generator for AssertLayouts {
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput {
fn generate(&mut self, ctx: &LateCtx) -> Output {
let (assertions_64, assertions_32) = ctx
.schema()
.into_iter()
@ -26,8 +26,8 @@ impl Generator for AssertLayouts {
})
.collect::<(Vec<TokenStream>, Vec<TokenStream>)>();
GeneratorOutput::Rust {
path: output(crate::AST_CRATE, "assert_layouts.rs"),
Output::Rust {
path: output_path(crate::AST_CRATE, "assert_layouts.rs"),
tokens: quote! {
use std::mem::{align_of, offset_of, size_of};

View file

@ -11,12 +11,12 @@ use syn::{parse_quote, Ident, Type};
use super::define_generator;
use crate::{
codegen::LateCtx,
output,
output::{output_path, Output},
schema::{
EnumDef, FieldDef, GetIdent, InheritDef, StructDef, ToType, TypeDef, TypeName, VariantDef,
},
util::{TypeAnalysis, TypeWrapper},
Generator, GeneratorOutput,
Generator,
};
pub struct AstBuilderGenerator;
@ -24,7 +24,7 @@ pub struct AstBuilderGenerator;
define_generator!(AstBuilderGenerator);
impl Generator for AstBuilderGenerator {
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput {
fn generate(&mut self, ctx: &LateCtx) -> Output {
let fns = ctx
.schema()
.into_iter()
@ -32,8 +32,8 @@ impl Generator for AstBuilderGenerator {
.map(|it| generate_builder_fn(it, ctx))
.collect_vec();
GeneratorOutput::Rust {
path: output(crate::AST_CRATE, "ast_builder.rs"),
Output::Rust {
path: output_path(crate::AST_CRATE, "ast_builder.rs"),
tokens: quote! {
//! AST node factories

View file

@ -6,9 +6,9 @@ use syn::{parse_quote, Arm, ImplItemFn, Variant};
use super::define_generator;
use crate::{
codegen::LateCtx,
output,
output::{output_path, Output},
schema::{GetIdent, ToType},
Generator, GeneratorOutput,
Generator,
};
pub struct AstKindGenerator;
@ -80,7 +80,7 @@ pub const BLACK_LIST: [&str; 61] = [
];
impl Generator for AstKindGenerator {
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput {
fn generate(&mut self, ctx: &LateCtx) -> Output {
let have_kinds = ctx
.schema()
.into_iter()
@ -125,8 +125,8 @@ impl Generator for AstKindGenerator {
})
.collect_vec();
GeneratorOutput::Rust {
path: output(crate::AST_CRATE, "ast_kind.rs"),
Output::Rust {
path: output_path(crate::AST_CRATE, "ast_kind.rs"),
tokens: quote! {
#![allow(missing_docs)] ///@ FIXME (in ast_tools/src/generators/ast_kind.rs)

View file

@ -1,10 +1,6 @@
use std::path::PathBuf;
use proc_macro2::TokenStream;
use quote::quote;
use crate::{
codegen::{generate_javascript_header, generate_rust_header, CodegenBase, LateCtx},
codegen::{CodegenBase, LateCtx},
output::{Output, RawOutput},
Result,
};
@ -20,37 +16,16 @@ pub use ast_kind::AstKindGenerator;
pub use typescript::TypescriptGenerator;
pub use visit::{VisitGenerator, VisitMutGenerator};
#[derive(Debug, Clone)]
pub enum GeneratorOutput {
Rust { path: PathBuf, tokens: TokenStream },
Javascript { path: PathBuf, code: String },
}
pub trait Generator: CodegenBase {
// Methods defined by implementer
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput;
fn generate(&mut self, ctx: &LateCtx) -> Output;
// Standard methods
fn output(&mut self, ctx: &LateCtx) -> Result<GeneratorOutput> {
let mut output = self.generate(ctx);
match &mut output {
GeneratorOutput::Rust { tokens, .. } => {
let header = generate_rust_header(Self::file_path());
*tokens = quote! {
#header
#tokens
};
}
GeneratorOutput::Javascript { code: content, .. } => {
let header = generate_javascript_header(Self::file_path());
*content = format!("{header}{content}");
}
}
Ok(output)
fn output(&mut self, ctx: &LateCtx) -> Result<RawOutput> {
let output = self.generate(ctx);
Ok(output.output(Self::file_path()))
}
}
@ -59,7 +34,7 @@ macro_rules! define_generator {
const _: () = {
use $crate::{
codegen::{CodegenBase, LateCtx, Runner},
generators::GeneratorOutput,
output::RawOutput,
Result,
};
@ -71,13 +46,13 @@ macro_rules! define_generator {
impl $($lifetime)? Runner for $ident $($lifetime)? {
type Context = LateCtx;
type Output = GeneratorOutput;
type Output = RawOutput;
fn name(&self) -> &'static str {
stringify!($ident)
}
fn run(&mut self, ctx: &LateCtx) -> Result<GeneratorOutput> {
fn run(&mut self, ctx: &LateCtx) -> Result<RawOutput> {
self.output(ctx)
}
}

View file

@ -1,19 +1,15 @@
use convert_case::{Case, Casing};
use itertools::Itertools;
use std::{
io::Write,
process::{Command, Stdio},
};
use super::define_generator;
use crate::{
codegen::LateCtx,
output,
output::{output_path, Output},
schema::{
serialize::{enum_variant_name, get_type_tag},
EnumDef, GetIdent, StructDef, TypeDef, TypeName,
},
Generator, GeneratorOutput,
Generator,
};
const CUSTOM_TYPESCRIPT: &str = include_str!("../../../../crates/oxc_ast/src/ast/types.d.ts");
@ -23,7 +19,7 @@ pub struct TypescriptGenerator;
define_generator!(TypescriptGenerator);
impl Generator for TypescriptGenerator {
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput {
fn generate(&mut self, ctx: &LateCtx) -> Output {
let mut code = format!("{CUSTOM_TYPESCRIPT}\n");
for def in ctx.schema() {
@ -40,10 +36,7 @@ impl Generator for TypescriptGenerator {
code.push_str("\n\n");
}
GeneratorOutput::Javascript {
path: output(crate::TYPESCRIPT_PACKAGE, "types.d.ts"),
code: format_typescript(&code),
}
Output::Javascript { path: output_path(crate::TYPESCRIPT_PACKAGE, "types.d.ts"), code }
}
}
@ -127,19 +120,3 @@ fn type_to_string(ty: &TypeName) -> String {
TypeName::Opt(type_name) => format!("{} | null", type_to_string(type_name)),
}
}
fn format_typescript(source_text: &str) -> String {
let mut dprint = Command::new("dprint")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args(["fmt", "--stdin", "types.d.ts"])
.spawn()
.expect("Failed to run dprint (is it installed?)");
let stdin = dprint.stdin.as_mut().unwrap();
stdin.write_all(source_text.as_bytes()).unwrap();
stdin.flush().unwrap();
let output = dprint.wait_with_output().unwrap();
String::from_utf8(output.stdout).unwrap()
}

View file

@ -12,10 +12,10 @@ use crate::{
codegen::LateCtx,
generators::ast_kind::BLACK_LIST as KIND_BLACK_LIST,
markers::VisitArg,
output,
output::{output_path, Output},
schema::{EnumDef, GetIdent, StructDef, ToType, TypeDef},
util::{StrExt, TokenStreamExt, TypeWrapper},
Generator, GeneratorOutput,
Generator,
};
pub struct VisitGenerator;
@ -23,9 +23,9 @@ pub struct VisitGenerator;
define_generator!(VisitGenerator);
impl Generator for VisitGenerator {
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput {
GeneratorOutput::Rust {
path: output(crate::AST_CRATE, "visit.rs"),
fn generate(&mut self, ctx: &LateCtx) -> Output {
Output::Rust {
path: output_path(crate::AST_CRATE, "visit.rs"),
tokens: generate_visit::<false>(ctx),
}
}
@ -36,9 +36,9 @@ pub struct VisitMutGenerator;
define_generator!(VisitMutGenerator);
impl Generator for VisitMutGenerator {
fn generate(&mut self, ctx: &LateCtx) -> GeneratorOutput {
GeneratorOutput::Rust {
path: output(crate::AST_CRATE, "visit_mut.rs"),
fn generate(&mut self, ctx: &LateCtx) -> Output {
Output::Rust {
path: output_path(crate::AST_CRATE, "visit_mut.rs"),
tokens: generate_visit::<true>(ctx),
}
}

View file

@ -8,10 +8,10 @@ use syn::parse_file;
mod codegen;
mod derives;
mod fmt;
mod generators;
mod layout;
mod markers;
mod output;
mod passes;
mod rust_ast;
mod schema;
@ -22,11 +22,12 @@ use derives::{
DeriveGetSpanMut,
};
use generators::{
AssertLayouts, AstBuilderGenerator, AstKindGenerator, Generator, GeneratorOutput,
TypescriptGenerator, VisitGenerator, VisitMutGenerator,
AssertLayouts, AstBuilderGenerator, AstKindGenerator, Generator, TypescriptGenerator,
VisitGenerator, VisitMutGenerator,
};
use output::write_all_to;
use passes::{CalcLayout, Linker};
use util::{write_all_to, NormalizeError};
use util::NormalizeError;
static SOURCE_PATHS: &[&str] = &[
"crates/oxc_ast/src/ast/literal.rs",
@ -90,7 +91,7 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
.map(|it| {
let path = it.path();
log!("Writing {path}...");
it.apply().unwrap();
it.write_to_file().unwrap();
logln!(" Done!");
path
})
@ -107,10 +108,6 @@ fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
Ok(())
}
fn output(krate: &str, path: &str) -> PathBuf {
std::path::PathBuf::from_iter(vec![krate, "src", "generated", path])
}
fn write_ci_filter(
inputs: &[&str],
side_effects: Vec<String>,

View file

@ -0,0 +1,39 @@
use std::{
io::Write,
process::{Command, Stdio},
};
/// Format Javascript/Typescript code, and add header.
pub fn print_javascript(code: &str, generator_path: &str) -> String {
let header = generate_header(generator_path);
let code = format!("{header}{code}");
format(&code)
}
/// Creates a generated file warning + required information for a generated file.
fn generate_header(generator_path: &str) -> String {
let generator_path = generator_path.replace('\\', "/");
// TODO: Add generation date, AST source hash, etc here.
format!(
"// Auto-generated code, DO NOT EDIT DIRECTLY!\n\
// To edit this generated file you have to edit `{generator_path}`\n\n"
)
}
/// Format JS/TS code with dprint.
fn format(source_text: &str) -> String {
let mut dprint = Command::new("dprint")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.args(["fmt", "--stdin", "dummy.ts"])
.spawn()
.expect("Failed to run dprint (is it installed?)");
let stdin = dprint.stdin.as_mut().unwrap();
stdin.write_all(source_text.as_bytes()).unwrap();
stdin.flush().unwrap();
let output = dprint.wait_with_output().unwrap();
String::from_utf8(output.stdout).unwrap()
}

View file

@ -0,0 +1,72 @@
use std::{fs, io::Write, path::PathBuf};
use proc_macro2::TokenStream;
mod javascript;
mod rust;
use javascript::print_javascript;
use rust::print_rust;
/// An output from codegen.
///
/// Can be either Rust or Javascript.
pub enum Output {
Rust { path: PathBuf, tokens: TokenStream },
Javascript { path: PathBuf, code: String },
}
impl Output {
pub fn output(self, generator_path: &str) -> RawOutput {
let (path, code) = match self {
Self::Rust { path, tokens } => {
let code = print_rust(&tokens, generator_path);
(path, code)
}
Self::Javascript { path, code } => {
let code = print_javascript(&code, generator_path);
(path, code)
}
};
RawOutput { path, content: code.into_bytes() }
}
}
/// A raw output from codegen.
#[derive(Debug)]
pub struct RawOutput {
pub path: PathBuf,
pub content: Vec<u8>,
}
impl RawOutput {
/// Get path of output as a string.
pub fn path(&self) -> String {
let path = self.path.to_string_lossy();
path.replace('\\', "/")
}
/// Write output to file
pub fn write_to_file(self) -> std::io::Result<()> {
let Self { path, content } = self;
let path = path.into_os_string();
let path = path.to_str().unwrap();
write_all_to(&content, path)?;
Ok(())
}
}
/// Get path for an output.
pub fn output_path(krate: &str, path: &str) -> PathBuf {
std::path::PathBuf::from_iter(vec![krate, "src", "generated", path])
}
/// Write data to file.
pub fn write_all_to<S: AsRef<std::path::Path>>(data: &[u8], path: S) -> std::io::Result<()> {
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(())
}

View file

@ -5,17 +5,37 @@ use std::{
use lazy_static::lazy_static;
use proc_macro2::TokenStream;
use quote::quote;
use regex::{Captures, Regex, Replacer};
use syn::parse_file;
/// Pretty print
pub fn pretty_print(input: &TokenStream) -> String {
let result = prettyplease::unparse(&parse_file(input.to_string().as_str()).unwrap());
/// Format Rust code, and add header.
pub fn print_rust(tokens: &TokenStream, generator_path: &str) -> String {
let header = generate_header(generator_path);
let tokens = quote! {
#header
#tokens
};
let result = prettyplease::unparse(&parse_file(tokens.to_string().as_str()).unwrap());
let result = COMMENT_REGEX.replace_all(&result, CommentReplacer).to_string();
rust_fmt(&result)
}
pub fn rust_fmt(source_text: &str) -> String {
/// Creates a generated file warning + required information for a generated file.
fn generate_header(generator_path: &str) -> TokenStream {
let generator_path = generator_path.replace('\\', "/");
// TODO: Add generation date, AST source hash, etc here.
let edit_comment = format!("@ To edit this generated file you have to edit `{generator_path}`");
quote::quote! {
//!@ Auto-generated code, DO NOT EDIT DIRECTLY!
#![doc = #edit_comment]
//!@@line_break
}
}
fn rust_fmt(source_text: &str) -> String {
let mut rustfmt = Command::new("rustfmt")
.stdin(Stdio::piped())
.stdout(Stdio::piped())

View file

@ -309,17 +309,6 @@ where
}
}
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())
}