Skip to content

Commit 17b71ad

Browse files
authored
[FEAT] style warnings (#354)
* add style warnings to LSP * accept test * accept test help * cargo insta review. * simpler functions * fix unnecessary advice * uppercase search sanitization * update nets
1 parent 18274d7 commit 17b71ad

31 files changed

+537
-40
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ env_logger = "0.11"
5656
indicatif = "0.17.8"
5757
inquire = "0.7"
5858
itertools = "0.13.0"
59+
heck = "0.5.0"
5960
log = "0.4"
6061
lru = "0.12"
6162
minijinja = "2.5"

crates/pcb-diode-api/src/component.rs

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -855,20 +855,34 @@ fn sanitize_mpn_for_path(mpn: &str) -> String {
855855
}
856856
}
857857

858-
/// Sanitize a pin name to create a valid Starlark identifier
858+
/// Sanitize a pin name to create a valid Starlark identifier.
859+
/// Output follows UPPERCASE convention for io() parameters.
860+
///
861+
/// Special handling:
862+
/// - `~` or `!` at start: becomes `N_` prefix (e.g., `~CS` → `N_CS`)
863+
/// - `+` at end: becomes `_POS` suffix (e.g., `V+` → `V_POS`)
864+
/// - `-` at end: becomes `_NEG` suffix (e.g., `V-` → `V_NEG`)
865+
/// - `+` or `-` elsewhere: becomes `_` (e.g., `A+B` → `A_B`)
866+
/// - `#`: becomes `H` (e.g., `CS#` → `CSH`)
867+
/// - All alphanumeric chars: uppercased
859868
fn sanitize(name: &str) -> String {
860-
let result = name
861-
.chars()
862-
.map(|c| match c {
863-
'+' => 'P', // Plus becomes P (e.g., V+ → VP)
864-
'-' => 'M', // Minus becomes M (e.g., V- → VM)
865-
'~' => 'n', // Tilde (NOT) becomes n (e.g., ~CS → nCS)
866-
'!' => 'n', // Bang (NOT) also becomes n
867-
'#' => 'h', // Hash becomes h (e.g., CS# → CSh)
868-
c if c.is_alphanumeric() => c,
869-
_ => '_',
870-
})
871-
.collect::<String>();
869+
let chars: Vec<char> = name.chars().collect();
870+
let len = chars.len();
871+
let mut result = String::with_capacity(len + 8);
872+
873+
for (i, &c) in chars.iter().enumerate() {
874+
let is_last = i == len - 1;
875+
876+
match c {
877+
'+' if is_last => result.push_str("_POS"),
878+
'-' if is_last => result.push_str("_NEG"),
879+
'+' | '-' => result.push('_'),
880+
'~' | '!' => result.push_str("N_"), // NOT prefix
881+
'#' => result.push('H'),
882+
c if c.is_alphanumeric() => result.push(c.to_ascii_uppercase()),
883+
_ => result.push('_'),
884+
}
885+
}
872886

873887
// Remove consecutive underscores and trim underscores from start/end
874888
let sanitized = result
@@ -1835,4 +1849,71 @@ mod tests {
18351849

18361850
insta::assert_snapshot!(result);
18371851
}
1852+
1853+
#[test]
1854+
fn test_sanitize_pin_name_basic() {
1855+
// Basic alphanumeric names get uppercased
1856+
assert_eq!(sanitize("VCC"), "VCC");
1857+
assert_eq!(sanitize("gnd"), "GND");
1858+
assert_eq!(sanitize("GPIO1"), "GPIO1");
1859+
assert_eq!(sanitize("sda"), "SDA");
1860+
}
1861+
1862+
#[test]
1863+
fn test_sanitize_pin_name_plus_minus_at_end() {
1864+
// + and - at end become _POS and _NEG
1865+
assert_eq!(sanitize("V+"), "V_POS");
1866+
assert_eq!(sanitize("V-"), "V_NEG");
1867+
assert_eq!(sanitize("IN+"), "IN_POS");
1868+
assert_eq!(sanitize("OUT-"), "OUT_NEG");
1869+
assert_eq!(sanitize("VCC+"), "VCC_POS");
1870+
}
1871+
1872+
#[test]
1873+
fn test_sanitize_pin_name_plus_minus_in_middle() {
1874+
// + and - in middle become underscores
1875+
assert_eq!(sanitize("A+B"), "A_B");
1876+
assert_eq!(sanitize("IN-OUT"), "IN_OUT");
1877+
assert_eq!(sanitize("V+REF"), "V_REF");
1878+
}
1879+
1880+
#[test]
1881+
fn test_sanitize_pin_name_not_prefix() {
1882+
// ~ and ! at start become N_ prefix
1883+
assert_eq!(sanitize("~CS"), "N_CS");
1884+
assert_eq!(sanitize("!RESET"), "N_RESET");
1885+
assert_eq!(sanitize("~WR"), "N_WR");
1886+
assert_eq!(sanitize("!OE"), "N_OE");
1887+
}
1888+
1889+
#[test]
1890+
fn test_sanitize_pin_name_hash_suffix() {
1891+
// # becomes H
1892+
assert_eq!(sanitize("CS#"), "CSH");
1893+
assert_eq!(sanitize("WE#"), "WEH");
1894+
}
1895+
1896+
#[test]
1897+
fn test_sanitize_pin_name_special_chars() {
1898+
// Other special chars become underscores
1899+
assert_eq!(sanitize("PIN/A"), "PIN_A");
1900+
assert_eq!(sanitize("PIN.B"), "PIN_B");
1901+
assert_eq!(sanitize("PIN A"), "PIN_A");
1902+
}
1903+
1904+
#[test]
1905+
fn test_sanitize_pin_name_leading_digit() {
1906+
// Leading digit gets P prefix
1907+
assert_eq!(sanitize("1"), "P1");
1908+
assert_eq!(sanitize("1A"), "P1A");
1909+
assert_eq!(sanitize("123"), "P123");
1910+
}
1911+
1912+
#[test]
1913+
fn test_sanitize_pin_name_consecutive_underscores() {
1914+
// Consecutive underscores get collapsed
1915+
assert_eq!(sanitize("A__B"), "A_B");
1916+
assert_eq!(sanitize("A___B"), "A_B");
1917+
assert_eq!(sanitize("_A_"), "A");
1918+
}
18381919
}

crates/pcb-zen-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ debugserver-types = { workspace = true }
2929
either = { workspace = true }
3030
globset = { workspace = true }
3131
itertools = { workspace = true }
32+
heck = { workspace = true }
3233
lru = { workspace = true }
3334
lsp-types = { workspace = true }
3435
serde = { workspace = true }

crates/pcb-zen-core/src/lang/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ pub(crate) mod file;
2323
// Add public error module and Result alias
2424
pub mod error;
2525

26+
// Naming convention checks
27+
pub mod naming;
28+
2629
// Validation utilities
2730
pub(crate) mod validation;

crates/pcb-zen-core/src/lang/module.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use crate::lang::evaluator_ext::EvaluatorExt;
3131
use crate::lang::interface::{
3232
FrozenInterfaceFactory, FrozenInterfaceValue, InterfaceFactory, InterfaceValue,
3333
};
34+
use crate::lang::naming;
3435
use crate::lang::validation::validate_identifier_name;
3536
use regex::Regex;
3637
use starlark::codemap::{CodeMap, Pos, Span};
@@ -1700,6 +1701,15 @@ pub fn module_globals(builder: &mut GlobalsBuilder) {
17001701
);
17011702
}
17021703

1704+
// Check naming convention (io parameters should be UPPERCASE)
1705+
let (path, span) = eval
1706+
.call_stack_top_location()
1707+
.map(|loc| (loc.file.filename().to_string(), Some(loc.resolve_span())))
1708+
.unwrap_or_else(|| (eval.source_path().unwrap_or_default(), None));
1709+
if let Some(diag) = naming::check_io_naming(&name, span, Path::new(&path)) {
1710+
eval.add_diagnostic(diag);
1711+
}
1712+
17031713
Ok(result_value)
17041714
}
17051715

@@ -1764,6 +1774,15 @@ pub fn module_globals(builder: &mut GlobalsBuilder) {
17641774
);
17651775
}
17661776

1777+
// Check naming convention (config parameters should be snake_case)
1778+
let (path, span) = eval
1779+
.call_stack_top_location()
1780+
.map(|loc| (loc.file.filename().to_string(), Some(loc.resolve_span())))
1781+
.unwrap_or_else(|| (eval.source_path().unwrap_or_default(), None));
1782+
if let Some(diag) = naming::check_config_naming(&name, span, Path::new(&path)) {
1783+
eval.add_diagnostic(diag);
1784+
}
1785+
17671786
Ok(result_value)
17681787
}
17691788

0 commit comments

Comments
 (0)