structure-check: gate counts code lines, docs and comments in English

Gate ignores blank and full-line comment lines so the 250-line limit
measures code bulk, not documentation weight; docs/README.md synced.
Translate code comments to English in classfile, core config, namegen,
rename assign, constants forms. Expand Plausible word bank with
client-ecosystem terms (Module, Setting, Mixin, ...). cargo fmt clean,
check.sh green.
This commit is contained in:
Loki 2026-10-09 10:31:02 +02:00
parent ec28ec82e3
commit 39178420e4
Signed by: boba
GPG key ID: 253067914055423B
42 changed files with 532 additions and 437 deletions

View file

@ -1,7 +1,8 @@
//! Compile-time gate for the project structure rules.
//!
//! Every crate calls [`check`] from its `build.rs`, so a file that grows past
//! 250 lines or a directory that fills up with a fifth file fails the build.
//! 250 *code* lines (comments and blank lines excluded) or a directory that
//! fills up with a fifth file fails the build.
mod rules;
@ -48,6 +49,26 @@ mod tests {
assert!(found[0].contains("lines >"), "{found:?}");
}
#[test]
fn ignores_comments_and_blank_lines() {
// A file padded with comment and blank lines stays under the limit:
// only real code counts toward `MAX_FILE_LINES`.
let tmp = TempDir::new("comments");
let mut body = String::new();
for _ in 0..super::MAX_FILE_LINES * 2 {
body.push_str("// a comment line\n");
body.push_str("/// a doc line\n");
body.push_str("//! a module line\n");
body.push('\n');
}
for index in 0..40 {
body.push_str(&format!("pub fn f{index}() {{}}\n"));
}
fs::write(tmp.0.join("lib.rs"), body).expect("write");
let found = violations(&tmp.0);
assert!(found.is_empty(), "{found:?}");
}
#[test]
fn rejects_directory_over_file_limit() {
let tmp = TempDir::new("many");
@ -79,8 +100,8 @@ mod tests {
#[test]
fn ignores_markdown_size_and_count() {
// Правила — только про Rust-код: `.md` не лимитирован ни по
// строкам, ни в счётчике файлов директории.
// Rules cover Rust code only: `.md` is bounded neither by the line
// limit nor by the per-directory file count.
let tmp = TempDir::new("md");
for index in 0..super::MAX_DIR_FILES + 3 {
fs::write(tmp.0.join(format!("doc{index}.md")), "line\n".repeat(300)).expect("write");

View file

@ -1,7 +1,8 @@
//! Project structure rules, invoked from every crate's `build.rs`.
//!
//! Rules apply to Rust code only, not to documentation:
//! * every `.rs` file is at most [`MAX_FILE_LINES`] lines long;
//! * every `.rs` file holds at most [`MAX_FILE_LINES`] *code* lines — blank
//! lines and full-line comments (`//`, `///`, `//!`) are not counted;
//! * every directory inside a crate's `src/`, `tests/` or `benches/` holds
//! at most [`MAX_DIR_FILES`] files — subdirectories are unlimited.
//!
@ -13,7 +14,10 @@ use std::ffi::OsStr;
use std::fs;
use std::path::{Path, PathBuf};
/// Maximum number of lines in a Rust source file.
/// Maximum number of code lines in a Rust source file.
///
/// Comment and blank lines are excluded (see [`count_code_lines`]) so the
/// gate measures real code bulk, not documentation weight.
pub const MAX_FILE_LINES: usize = 250;
/// Maximum number of files per directory; subdirectories are not counted.
@ -35,9 +39,9 @@ pub fn check() {
let manifest = manifest_dir();
let mut violations = Vec::new();
// `src/` обязателен; `tests/` и `benches/` проверяются, если созданы —
// иначе правило «250 строк» обходится выносом кода в тесты. `docs/` вне
// гейта: правила — только про Rust-код.
// `src/` is required; `tests/` and `benches/` are checked only when they
// exist — otherwise the line rule is bypassed by moving code into tests.
// `docs/` stays out of the gate: the rules are about Rust code only.
let roots = [
manifest.join("src"),
manifest.join("tests"),
@ -124,25 +128,34 @@ pub fn scan(root: &Path, violations: &mut Vec<String>) {
}
fn check_line_limit(path: &Path, violations: &mut Vec<String>) {
let lines = count_lines(path);
let lines = count_code_lines(path);
if lines > MAX_FILE_LINES {
violations.push(format!(
"{}: {lines} lines > {MAX_FILE_LINES}",
"{}: {lines} code lines > {MAX_FILE_LINES}",
path.display()
));
}
}
/// Counts lines without requiring the file to be valid UTF-8.
fn count_lines(path: &Path) -> usize {
let Ok(bytes) = fs::read(path) else {
return 0;
/// Counts *code* lines, skipping blanks and full-line comments.
///
/// A line is code when, after leading whitespace is stripped, it is non-empty
/// and does not start with `//` (which also covers `///` and `//!`). A line
/// that carries code followed by a trailing comment still counts once. Falls
/// back to a raw newline count when the file is not valid UTF-8.
fn count_code_lines(path: &Path) -> usize {
let Ok(text) = fs::read_to_string(path) else {
let Ok(bytes) = fs::read(path) else {
return 0;
};
return bytes.iter().filter(|&&byte| byte == b'\n').count();
};
if bytes.is_empty() {
return 0;
}
let newlines = bytes.iter().filter(|&&b| b == b'\n').count();
newlines + usize::from(bytes.last() != Some(&b'\n'))
text.lines()
.filter(|line| {
let trimmed = line.trim_start();
!trimmed.is_empty() && !trimmed.starts_with("//")
})
.count()
}
fn manifest_dir() -> PathBuf {