Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.
Merged
Prev Previous commit
Next Next commit
add linter checks to formatter output
  • Loading branch information
addisoncrump committed Jun 14, 2023
commit eaef5dec3e8ad65a4ff33b7807a45578ba4e12fc
2 changes: 1 addition & 1 deletion .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
with:
bins: cargo-fuzz
- name: Run init-fuzzer
run: sh fuzz/init-fuzzer.sh
run: bash fuzz/init-fuzzer.sh

test-node-api:
name: Test node.js API
Expand Down
10 changes: 7 additions & 3 deletions fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ cargo-fuzz = true
[dependencies]
arbitrary = { version = "1.3.0", features = ["derive"] }
libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer", default-features = false }
rome_analyze = { path = "../crates/rome_analyze" }
rome_diagnostics = { path = "../crates/rome_diagnostics" }
rome_formatter = { path = "../crates/rome_formatter" }
rome_js_analyze = { path = "../crates/rome_js_analyze" }
rome_js_formatter = { path = "../crates/rome_js_formatter" }
rome_js_parser = { path = "../crates/rome_js_parser" }
rome_js_syntax = { path = "../crates/rome_js_syntax" }
rome_json_formatter = { path = "../crates/rome_json_formatter" }
rome_json_parser = { path = "../crates/rome_json_parser" }
rome_json_syntax = { path = "../crates/rome_json_syntax" }
rome_service = { path = "../crates/rome_service" }
similar = { version = "2.2.1" }

# Prevent this from interfering with workspaces
Expand Down Expand Up @@ -99,13 +103,13 @@ name = "rome_format_typescript"
path = "fuzz_targets/rome_format_typescript.rs"

[profile.release]
opt-level = 3
opt-level = 2
debug = true

[profile.dev]
opt-level = 3
opt-level = 2
debug = true

[profile.test]
opt-level = 3
opt-level = 2
debug = true
91 changes: 91 additions & 0 deletions fuzz/fuzz_targets/rome_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,20 @@
#![allow(dead_code)]

use libfuzzer_sys::Corpus;
use rome_analyze::{AnalysisFilter, AnalyzerOptions, ControlFlow, RuleFilter};
use rome_diagnostics::Diagnostic;
use rome_formatter::format_node;
use rome_js_analyze::analyze;
use rome_js_formatter::context::JsFormatOptions;
use rome_js_formatter::JsFormatLanguage;
use rome_js_parser::parse;
use rome_js_syntax::JsFileSource;
use rome_json_formatter::context::JsonFormatOptions;
use rome_json_formatter::JsonFormatLanguage;
use rome_json_parser::parse_json;
use rome_service::Rules;
use similar::TextDiff;
use std::fmt::{Display, Formatter};

pub fn fuzz_js_parser_with_source_type(data: &[u8], source: JsFileSource) -> Corpus {
let Ok(code1) = std::str::from_utf8(data) else { return Corpus::Reject; };
Expand All @@ -27,12 +32,69 @@ pub fn fuzz_js_parser_with_source_type(data: &[u8], source: JsFileSource) -> Cor
Corpus::Keep
}

static mut ANALYSIS_RULES: Option<Rules> = None;
static mut ANALYSIS_RULE_FILTERS: Option<Vec<RuleFilter>> = None;
static mut ANALYSIS_OPTIONS: Option<AnalyzerOptions> = None;

struct DiagnosticDescriptionExtractor<'a, D> {
diagnostic: &'a D,
}

impl<'a, D> DiagnosticDescriptionExtractor<'a, D> {
pub fn new(diagnostic: &'a D) -> Self {
Self { diagnostic }
}
}

impl<'a, D> Display for DiagnosticDescriptionExtractor<'a, D>
where
D: Diagnostic,
{
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.diagnostic.description(f)
}
}

pub fn fuzz_js_formatter_with_source_type(data: &[u8], source: JsFileSource) -> Corpus {
let Ok(code1) = std::str::from_utf8(data) else { return Corpus::Reject; };

// TODO: replace with OnceLock when upgrading to 1.70
let rule_filters = if let Some(rules) = unsafe { ANALYSIS_RULE_FILTERS.as_ref() } {
rules
} else {
let rules = unsafe {
ANALYSIS_RULES.get_or_insert_with(|| Rules {
all: Some(true),
..Default::default()
})
};
let rules = rules.as_enabled_rules().into_iter().collect::<Vec<_>>();
unsafe {
ANALYSIS_RULE_FILTERS = Some(rules);
ANALYSIS_RULE_FILTERS.as_ref().unwrap_unchecked()
}
};
let options = unsafe { ANALYSIS_OPTIONS.get_or_insert_with(AnalyzerOptions::default) };

let parse1 = parse(code1, source);
if !parse1.has_errors() {
let language = JsFormatLanguage::new(JsFormatOptions::new(source));
let tree1 = parse1.tree();
let mut linter_errors = Vec::new();
let _ = analyze(
&tree1,
AnalysisFilter::from_enabled_rules(Some(rule_filters)),
options,
source,
|e| -> ControlFlow<()> {
if let Some(diagnostic) = e.diagnostic() {
linter_errors
.push(DiagnosticDescriptionExtractor::new(&diagnostic).to_string());
}

ControlFlow::Continue(())
},
);
let syntax1 = parse1.syntax();
if let Ok(formatted1) = format_node(&syntax1, language.clone()) {
if let Ok(printed1) = formatted1.print() {
Expand All @@ -45,6 +107,35 @@ pub fn fuzz_js_formatter_with_source_type(data: &[u8], source: JsFileSource) ->
.unified_diff()
.header("original code", "formatted")
);
let tree2 = parse2.tree();
let (maybe_diagnostic, _) = analyze(
&tree2,
AnalysisFilter::from_enabled_rules(Some(rule_filters)),
options,
source,
|e| {
if let Some(diagnostic) = e.diagnostic() {
let new_error =
DiagnosticDescriptionExtractor::new(&diagnostic).to_string();
if let Some(idx) = linter_errors.iter().position(|e| *e == new_error) {
linter_errors.remove(idx);
} else {
return ControlFlow::Break(new_error);
}
}

ControlFlow::Continue(())
},
);
if let Some(diagnostic) = maybe_diagnostic {
panic!(
"formatter introduced linter failure: {}\n{}",
diagnostic,
TextDiff::from_lines(code1, code2)
.unified_diff()
.header("original code", "formatted")
);
}
let syntax2 = parse2.syntax();
let formatted2 = format_node(&syntax2, language)
.expect("formatted code could not be reformatted");
Expand Down