oxc/tasks/prettier_conformance/src/spec.rs

133 lines
5.6 KiB
Rust

#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
use std::{fs, path::Path, str::FromStr};
use oxc_allocator::Allocator;
use oxc_ast::{
ast::{Argument, ArrayExpressionElement, CallExpression, Expression, ObjectPropertyKind},
VisitMut,
};
use oxc_parser::Parser;
use oxc_prettier::{ArrowParens, EndOfLine, PrettierOptions, QuoteProps, TrailingComma};
use oxc_span::{GetSpan, SourceType};
#[derive(Default)]
pub struct SpecParser {
pub calls: Vec<(PrettierOptions, Vec<(String, String)>)>,
source_text: String,
}
impl SpecParser {
pub fn parse(&mut self, spec: &Path) {
let spec_content = fs::read_to_string(spec).unwrap_or_default();
let allocator = Allocator::default();
let source_type = SourceType::from_path(spec).unwrap_or_default();
let mut ret = Parser::new(&allocator, &spec_content, source_type).parse();
self.source_text.clone_from(&spec_content);
self.calls = vec![];
self.visit_program(&mut ret.program);
}
}
impl VisitMut<'_> for SpecParser {
fn visit_call_expression(&mut self, expr: &mut CallExpression<'_>) {
let Some(ident) = expr.callee.get_identifier_reference() else { return };
// The `runFormatTest` function used on prettier's test cases. We need to collect all `run_spec` calls
// And then parse the arguments to get the options and parsers
// Finally we use this information to generate the snapshot tests
if ident.name != "runFormatTest" {
return;
}
let mut parsers = vec![];
let mut snapshot_options: Vec<(String, String)> = vec![];
let mut options = PrettierOptions::default();
if let Some(argument) = expr.arguments.get(1) {
let Some(argument_expr) = argument.as_expression() else {
return;
};
if let Expression::ArrayExpression(arr_expr) = argument_expr {
parsers = arr_expr
.elements
.iter()
.filter_map(|el| {
if let ArrayExpressionElement::StringLiteral(literal) = el {
return Some(literal.value.to_string());
}
None
})
.collect::<Vec<String>>();
}
} else {
return;
}
if let Some(Argument::ObjectExpression(obj_expr)) = expr.arguments.get(2) {
obj_expr.properties.iter().for_each(|item| {
if let ObjectPropertyKind::ObjectProperty(obj_prop) = item {
if let Some(name) = obj_prop.key.static_name() {
match &obj_prop.value {
Expression::BooleanLiteral(literal) => {
if name == "semi" {
options.semi = literal.value;
} else if name == "bracketSpacing" {
options.bracket_spacing = literal.value;
} else if name == "singleQuote" {
options.single_quote = literal.value;
}
}
Expression::NumericLiteral(literal) => match name.as_str() {
"printWidth" => options.print_width = literal.value as usize,
"tabWidth" => options.tab_width = literal.value as usize,
_ => {}
},
Expression::StringLiteral(literal) => match name.as_str() {
"trailingComma" => {
options.trailing_comma =
TrailingComma::from_str(literal.value.as_str()).unwrap();
}
"endOfLine" => {
options.end_of_line =
EndOfLine::from_str(literal.value.as_str()).unwrap();
}
"quoteProps" => {
options.quote_props =
QuoteProps::from_str(literal.value.as_str()).unwrap();
}
"arrowParens" => {
options.arrow_parens =
ArrowParens::from_str(literal.value.as_str()).unwrap();
}
_ => {}
},
_ => {}
};
if name != "errors" {
snapshot_options.push((
name.to_string(),
obj_prop.value.span().source_text(&self.source_text).to_string(),
));
}
};
}
});
}
snapshot_options.push((
"parsers".to_string(),
format!(
"[{}]",
parsers.iter().map(|p| format!("\"{p}\"")).collect::<Vec<_>>().join(", ")
),
));
if !snapshot_options.iter().any(|item| item.0 == "printWidth") {
snapshot_options.push(("printWidth".to_string(), "80".into()));
}
snapshot_options.sort_by(|a, b| a.0.cmp(&b.0));
self.calls.push((options, snapshot_options));
}
}