use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
use gdck_config::FormatConfig;
const FIXED_POINT: &[&str] = &[
"avoid_unnecessary_parentheses__good",
"blank_lines__neutral",
"boolean_operators__bad",
"boolean_operators__good",
"class_declaration__neutral",
"class_declaration__neutral_2",
"classes_and_nodes__neutral",
"classes_and_nodes__neutral_2",
"comment_spacing__bad",
"comment_spacing__bad_2",
"comment_spacing__good",
"comment_spacing__good_2",
"constants_and_enums__good",
"constants_and_enums__neutral",
"constants_and_enums__neutral_2",
"declared_types__neutral",
"declared_types__neutral_2",
"file_names__neutral",
"file_names__neutral_2",
"indentation__good",
"indentation__good_3",
"inferred_types__bad",
"inferred_types__bad_2",
"inferred_types__good",
"inferred_types__good_2",
"inferred_types__good_3",
"intro__neutral",
"methods_and_static_functions__neutral",
"numbers__bad_3",
"numbers__good",
"numbers__good_2",
"numbers__good_3",
"one_statement_per_line__good",
"one_statement_per_line__neutral",
"quotes__neutral",
"signals__neutral",
"signals_and_properties__neutral",
"trailing_comma__good",
"trailing_comma__good_2",
"whitespace__good",
];
const REWRITE_TO_GUIDE: &[(&str, &str)] = &[
(
"avoid_unnecessary_parentheses__bad",
"avoid_unnecessary_parentheses__good",
),
("constants_and_enums__bad", "constants_and_enums__good"),
("indentation__bad_3", "indentation__good_3"),
("numbers__bad", "numbers__good"),
("numbers__bad_2", "numbers__good_2"),
(
"one_statement_per_line__bad",
"one_statement_per_line__good",
),
("trailing_comma__bad", "trailing_comma__good"),
("trailing_comma__bad_2", "trailing_comma__good_2"),
];
const REWRITE_TO_EXPECTED: &[(&str, &str)] = &[
(
"format_multiline_statements_for_readability__bad",
"the ternary wraps; the 83-column `if` is a judgement the linter owns",
),
(
"functions_and_variables__neutral",
"blank lines added around the definition",
),
(
"functions_and_variables__neutral_2",
"blank lines added around the definition",
),
(
"whitespace__bad",
"spacing fixed; the renames belong to the linter",
),
("whitespace__neutral", "vertical alignment padding removed"),
];
const EXCEPTIONS: &[(&str, &str)] = &[
(
"indentation__good_2",
"hand-filled call arguments; gdck puts one argument per line",
),
(
"indentation__bad_2",
"same sample as indentation__good_2, wrongly indented",
),
(
"format_multiline_statements_for_readability__good",
"hand-filled boolean chain; gdck breaks at every operator",
),
];
fn fixture_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("style-guide")
}
fn read(name: &str) -> String {
let path = fixture_dir().join(format!("{name}.gd"));
std::fs::read_to_string(&path)
.unwrap_or_else(|error| panic!("reading {}: {error}", path.display()))
}
fn format(name: &str) -> String {
let source = read(name);
gdck_format::format_source(&source, &FormatConfig::default())
.unwrap_or_else(|error| panic!("formatting {name}: {error}"))
}
#[test]
fn good_samples_are_left_exactly_as_written() {
for name in FIXED_POINT {
let source = read(name);
assert_eq!(
format(name),
source,
"\n{name} is a style-guide sample the formatter must not change"
);
}
}
#[test]
fn bad_samples_become_the_guides_own_good_sample() {
for (bad, good) in REWRITE_TO_GUIDE {
assert_eq!(
format(bad),
read(good),
"\nformatting {bad} must produce {good} exactly"
);
}
}
#[test]
fn reviewed_rewrites_match_their_expected_output() {
for (name, why) in REWRITE_TO_EXPECTED {
let expected = fixture_dir().join(format!("{name}.expected.gd"));
let expected = std::fs::read_to_string(&expected)
.unwrap_or_else(|error| panic!("reading {}: {error}", expected.display()));
assert_eq!(format(name), expected, "\n{name}: {why}");
}
}
#[test]
fn documented_exceptions_still_differ() {
for (name, why) in EXCEPTIONS {
assert_ne!(
format(name),
read(name),
"\n{name} now round-trips; move it to FIXED_POINT and drop the \
exception, which claimed: {why}"
);
}
}
#[test]
fn every_sample_is_idempotent() {
for entry in std::fs::read_dir(fixture_dir()).expect("fixture directory") {
let path = entry.expect("directory entry").path();
let name = path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("");
if std::path::Path::new(name)
.extension()
.is_none_or(|extension| extension != "gd")
{
continue;
}
let source = std::fs::read_to_string(&path).expect("readable fixture");
let config = FormatConfig::default();
let once = gdck_format::format_source(&source, &config)
.unwrap_or_else(|error| panic!("formatting {name}: {error}"));
let twice = gdck_format::format_source(&once, &config)
.unwrap_or_else(|error| panic!("reformatting {name}: {error}"));
assert_eq!(once, twice, "\n{name} is not stable under a second pass");
}
}
#[test]
fn no_sample_is_left_unclassified() {
let mut classified: BTreeSet<&str> = BTreeSet::new();
classified.extend(FIXED_POINT.iter().copied());
classified.extend(REWRITE_TO_GUIDE.iter().map(|(bad, _)| *bad));
classified.extend(REWRITE_TO_GUIDE.iter().map(|(_, good)| *good));
classified.extend(REWRITE_TO_EXPECTED.iter().map(|(name, _)| *name));
classified.extend(EXCEPTIONS.iter().map(|(name, _)| *name));
let mut present: BTreeSet<String> = BTreeSet::new();
for entry in std::fs::read_dir(fixture_dir()).expect("fixture directory") {
let path = entry.expect("directory entry").path();
let Some(name) = path.file_name().and_then(|name| name.to_str()) else {
continue;
};
if !name.ends_with(".expected.gd") {
if let Some(stem) = name.strip_suffix(".gd") {
present.insert(stem.to_string());
}
}
}
let unclassified: Vec<&String> = present
.iter()
.filter(|name| !classified.contains(name.as_str()))
.collect();
assert!(
unclassified.is_empty(),
"these style-guide samples are not listed in any table in this file: {unclassified:?}"
);
let missing: Vec<&&str> = classified
.iter()
.filter(|name| !present.contains(**name))
.collect();
assert!(
missing.is_empty(),
"these are listed but have no fixture; re-run \
tools/extract-style-guide-samples.py: {missing:?}"
);
}