use crate::languages::definitions;
use crate::languages::spec;
use crate::languages::{all_languages, source_extensions, vendored_dirs};
use std::collections::BTreeSet;
#[test]
fn tsc_stream_is_stdout_go_vet_stream_is_stderr() {
assert_eq!(
definitions::TSC.diagnostics_stream,
spec::DiagnosticsStream::Stdout
);
assert_eq!(
definitions::GO_VET.diagnostics_stream,
spec::DiagnosticsStream::Stderr
);
}
#[test]
fn project_compilers_do_not_take_file_arguments() {
assert!(
!definitions::CLIPPY.accepts_files,
"cargo clippy checks a crate; a path argument is rejected outright"
);
assert!(
!definitions::TSC.accepts_files,
"passing paths makes tsc ignore tsconfig.json"
);
for spec in [
&definitions::RUFF,
&definitions::ESLINT,
&definitions::GOFMT,
&definitions::GO_VET,
] {
assert!(
spec.accepts_files,
"{} is invoked with the files it should check",
spec.name
);
}
}
#[test]
fn whole_project_linters_do_not_take_file_arguments() {
assert!(
!definitions::TFLINT.accepts_files,
"tflint dropped positional file arguments in v0.47"
);
assert!(
!definitions::DOTNET_FORMAT.accepts_files,
"dotnet format checks a project, not a file list"
);
}
#[test]
fn only_clippy_is_serialized_within_a_repository() {
assert!(
definitions::CLIPPY.serial_in_repository,
"parallel cargo processes contend for the same build lock"
);
for spec in [
&definitions::RUFF,
&definitions::ESLINT,
&definitions::TSC,
&definitions::GOFMT,
&definitions::GO_VET,
] {
assert!(
!spec.serial_in_repository,
"{} should remain eligible for bounded parallel execution",
spec.name
);
}
}
#[test]
fn all_languages_returns_every_registered_language() {
let langs = all_languages();
let names: Vec<&str> = langs.iter().map(|l| l.name).collect();
assert_eq!(
names,
vec![
"python",
"javascript",
"typescript",
"vue",
"svelte",
"go",
"rust",
"java",
"kotlin",
"scala",
"groovy",
"shell",
"swift",
"c",
"cpp",
"csharp",
"ruby",
"php",
"terraform",
"elixir",
"sql",
"docker"
]
);
}
#[test]
fn source_extensions_contains_python_and_tsx_but_not_markdown() {
let exts = source_extensions();
assert!(
exts.contains(&".py"),
"`.py` is owned by python, expected in source_extensions, got {exts:?}"
);
assert!(
exts.contains(&".tsx"),
"`.tsx` is owned by typescript, expected in source_extensions, got {exts:?}"
);
assert!(
!exts.contains(&".md"),
"markdown is documentation, not a registered language: {exts:?}"
);
}
#[test]
fn jvm_build_directories_are_vendored() {
let dirs = vendored_dirs();
for expected in ["build", ".gradle", "target"] {
assert!(
dirs.contains(&expected),
"{expected} is a JVM build output and should never be descended into, got {dirs:?}"
);
}
}
#[test]
fn vendored_dirs_collects_unique_entries_across_languages() {
let dirs = vendored_dirs();
for expected in ["node_modules", "venv", "target"] {
assert!(
dirs.contains(&expected),
"{expected} should be in vendored_dirs, got {dirs:?}"
);
}
let count = dirs.iter().filter(|d| **d == "node_modules").count();
assert_eq!(
count, 1,
"JavaScript and TypeScript both declare node_modules; the set must collapse"
);
}
#[test]
fn every_language_declares_a_name_a_display_name_and_conventions() {
for lang in all_languages() {
assert!(!lang.name.is_empty(), "name is empty: {lang:?}");
assert!(
!lang.display_name.is_empty(),
"{} has no display name",
lang.name
);
assert!(
!lang.conventions.is_empty(),
"{} has no conventions for the prompt",
lang.name
);
}
}
#[test]
fn no_two_languages_claim_the_same_extension_or_filename() {
let mut seen: BTreeSet<String> = BTreeSet::new();
for lang in all_languages() {
for claimed in lang.extensions.iter().chain(lang.filenames.iter()) {
assert!(
seen.insert(claimed.to_ascii_lowercase()),
"{claimed} is claimed by {} and an earlier language",
lang.name
);
}
}
}
#[test]
fn no_stem_is_a_prefix_of_another_stem() {
let stems: Vec<String> = all_languages()
.iter()
.flat_map(|lang| lang.filename_prefixes.iter())
.map(|stem| stem.to_ascii_lowercase())
.collect();
for (i, shorter) in stems.iter().enumerate() {
for longer in &stems[i + 1..] {
assert!(
!longer.starts_with(shorter) && !shorter.starts_with(longer),
"stems {shorter:?} and {longer:?} overlap; the first registered would win"
);
}
}
}
#[test]
fn every_registered_entry_is_well_formed() {
for lang in all_languages() {
for ext in lang.extensions {
assert!(
ext.len() > 1 && ext.starts_with('.') && !ext[1..].contains('.'),
"{}: extension {ext:?} must be a single non-empty suffix with one leading dot",
lang.name
);
assert!(
ext.is_ascii(),
"{}: extension {ext:?} must be ASCII to case-fold",
lang.name
);
}
for name in lang.filenames {
assert!(
!name.is_empty() && name.is_ascii(),
"{}: filename {name:?} must be non-empty ASCII",
lang.name
);
}
for stem in lang.filename_prefixes {
assert!(
!stem.is_empty() && stem.is_ascii() && !stem.contains('.'),
"{}: filename stem {stem:?} must be non-empty ASCII without a dot",
lang.name
);
}
for dir in lang.vendored_dirs {
assert!(
!dir.is_empty() && !dir.contains('/') && !dir.contains('\\'),
"{}: vendored dir {dir:?} is compared against single path components",
lang.name
);
}
for tool in lang.tools {
assert!(!tool.name.is_empty(), "{}: tool name is empty", lang.name);
assert!(
!tool.command.is_empty(),
"{}: {} has an empty command",
lang.name,
tool.name
);
assert!(
tool.timeout_secs > 0,
"{}: {} has a zero timeout, which expires every run immediately",
lang.name,
tool.name
);
if tool.config_flag.is_some() {
assert!(
!tool.config_files.is_empty(),
"{}: {} has a config flag but no config files to hand it",
lang.name,
tool.name
);
}
}
}
}
#[test]
fn only_the_line_oriented_parsers_skip_unmatched_input() {
use spec::OutputFormat;
for (format, skips) in [
(OutputFormat::Lines, false),
(OutputFormat::Json, false),
(OutputFormat::Position, true),
(OutputFormat::Tsc, true),
(OutputFormat::Cargo, false),
(OutputFormat::Sarif, false),
(OutputFormat::Ktlint, false),
(OutputFormat::Shellcheck, false),
(OutputFormat::Rubocop, false),
(OutputFormat::Phpcs, false),
(OutputFormat::Credo, false),
(OutputFormat::Sqlfluff, false),
(OutputFormat::Msbuild, true),
] {
assert_eq!(format.skips_unmatched_input(), skips, "{format:?}");
}
}
#[test]
fn cppcheck_findings_make_the_exit_nonzero() {
assert!(
definitions::CPPCHECK
.command
.contains(&"--error-exitcode=2"),
"without --error-exitcode cppcheck exits 0 with findings and the guard never fires"
);
}
#[test]
fn tflint_inspects_nested_modules() {
assert!(
definitions::TFLINT.command.contains(&"--recursive"),
"without --recursive, nested Terraform modules are never linted"
);
}