ffmt 0.2.6__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ffmt-0.2.6 → ffmt-0.2.8}/Cargo.lock +1 -1
- {ffmt-0.2.6 → ffmt-0.2.8}/Cargo.toml +1 -1
- {ffmt-0.2.6 → ffmt-0.2.8}/PKG-INFO +1 -1
- {ffmt-0.2.6 → ffmt-0.2.8}/pyproject.toml +1 -1
- {ffmt-0.2.6 → ffmt-0.2.8}/src/align.rs +5 -5
- {ffmt-0.2.6 → ffmt-0.2.8}/src/formatter.rs +150 -10
- {ffmt-0.2.6 → ffmt-0.2.8}/.github/workflows/ci.yml +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/.github/workflows/release.yml +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/.github/workflows/update-readme-version.yml +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/.gitignore +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/.pre-commit-hooks.yaml +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/LICENSE +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/README.md +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/action.yml +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/case_norm.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/classifier.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/cli.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/config.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/keyword_norm.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/lib.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/lsp.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/main.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/reader.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/scope.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/unicode.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/src/whitespace.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/case_norm.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/classifier.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/config_integration.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/blank_lines.expected.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/blank_lines.input.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/call_block.expected.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/call_block.input.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/directives.expected.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/directives.input.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/doxygen_spacing.expected.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/doxygen_spacing.input.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/fypp.expected.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/fypp.input.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/simple.expected.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/fixtures/simple.input.fpp +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/integration.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/reader.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/scope.rs +0 -0
- {ffmt-0.2.6 → ffmt-0.2.8}/tests/whitespace.rs +0 -0
|
@@ -167,7 +167,7 @@ fn align_inline_comments(lines: &mut [String], line_length: usize) {
|
|
|
167
167
|
if let Some(pos) = find_inline_doxygen(line) {
|
|
168
168
|
if !has_doxygen_text(line, pos) { continue; }
|
|
169
169
|
let comment = &line[pos..];
|
|
170
|
-
let aligned_len = max_code_len +
|
|
170
|
+
let aligned_len = max_code_len + 2 + comment.len();
|
|
171
171
|
if aligned_len > line_length {
|
|
172
172
|
would_overflow = true;
|
|
173
173
|
break;
|
|
@@ -176,14 +176,14 @@ fn align_inline_comments(lines: &mut [String], line_length: usize) {
|
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
if would_overflow {
|
|
179
|
-
// Fall back to minimal spacing:
|
|
179
|
+
// Fall back to minimal spacing: ensure 2 spaces before !<
|
|
180
180
|
for line in lines.iter_mut() {
|
|
181
181
|
if let Some(pos) = find_inline_doxygen(line) {
|
|
182
182
|
if !has_doxygen_text(line, pos) { continue; }
|
|
183
183
|
let before = &line[..pos];
|
|
184
184
|
let comment = &line[pos..];
|
|
185
185
|
let trimmed_before = before.trim_end();
|
|
186
|
-
*line = format!("{}
|
|
186
|
+
*line = format!("{} {}", trimmed_before, comment);
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
return;
|
|
@@ -196,8 +196,8 @@ fn align_inline_comments(lines: &mut [String], line_length: usize) {
|
|
|
196
196
|
let before = &line[..pos];
|
|
197
197
|
let comment = &line[pos..];
|
|
198
198
|
let trimmed_before = before.trim_end();
|
|
199
|
-
let padding = max_code_len - trimmed_before.len();
|
|
200
|
-
*line = format!("{}{}
|
|
199
|
+
let padding = max_code_len + 2 - trimmed_before.len();
|
|
200
|
+
*line = format!("{}{}{}", trimmed_before, " ".repeat(padding), comment);
|
|
201
201
|
}
|
|
202
202
|
}
|
|
203
203
|
}
|
|
@@ -435,7 +435,8 @@ pub fn format_with_config(
|
|
|
435
435
|
if config.rewrap_code {
|
|
436
436
|
let mut final_lines: Vec<String> = Vec::new();
|
|
437
437
|
for line in &output_lines {
|
|
438
|
-
if line.len() > config.line_length && find_inline_doxygen_in_line(line).is_none()
|
|
438
|
+
if line.len() > config.line_length && find_inline_doxygen_in_line(line).is_none()
|
|
439
|
+
&& !line.trim_end().ends_with('&') {
|
|
439
440
|
final_lines.extend(rewrap_line(line, config.line_length, config.indent_width));
|
|
440
441
|
} else {
|
|
441
442
|
final_lines.push(line.clone());
|
|
@@ -447,6 +448,13 @@ pub fn format_with_config(
|
|
|
447
448
|
// Align :: in consecutive declaration lines
|
|
448
449
|
if config.align_declarations { output_lines = crate::align::align_declarations(&output_lines, config.compact_declarations, config.align_comments, config.line_length); }
|
|
449
450
|
|
|
451
|
+
// Ensure at least 2 spaces before inline comments (Fortitude S102)
|
|
452
|
+
// Runs after alignment so !< comments are already positioned
|
|
453
|
+
output_lines = ensure_two_spaces_before_inline_comment(&output_lines);
|
|
454
|
+
|
|
455
|
+
// Strip trailing semicolons (Fortitude S081)
|
|
456
|
+
output_lines = strip_trailing_semicolons(&output_lines);
|
|
457
|
+
|
|
450
458
|
let mut result = output_lines.join("\n");
|
|
451
459
|
if !result.is_empty() && !result.ends_with('\n') {
|
|
452
460
|
result.push('\n');
|
|
@@ -1200,6 +1208,108 @@ fn is_declaration_line(line: &str) -> bool {
|
|
|
1200
1208
|
false
|
|
1201
1209
|
}
|
|
1202
1210
|
|
|
1211
|
+
/// Ensure at least 2 spaces before inline `!` comments on code lines.
|
|
1212
|
+
/// This satisfies Fortitude rule S102. Only applies to lines that have both
|
|
1213
|
+
/// code and a trailing comment (not standalone comment lines, not `!$` directives,
|
|
1214
|
+
/// not `!>` or `!<` Doxygen, not Fypp lines).
|
|
1215
|
+
fn ensure_two_spaces_before_inline_comment(lines: &[String]) -> Vec<String> {
|
|
1216
|
+
lines.iter().map(|line| {
|
|
1217
|
+
let trimmed = line.trim_start();
|
|
1218
|
+
// Skip blank lines, standalone comments, preprocessor, Fypp
|
|
1219
|
+
if trimmed.is_empty() || trimmed.starts_with('!') || trimmed.starts_with('#')
|
|
1220
|
+
|| trimmed.starts_with("$:") || trimmed.starts_with("@:") {
|
|
1221
|
+
return line.clone();
|
|
1222
|
+
}
|
|
1223
|
+
// Find the inline comment position (! outside strings)
|
|
1224
|
+
let bytes = line.as_bytes();
|
|
1225
|
+
let mut in_string = false;
|
|
1226
|
+
let mut delim = b' ';
|
|
1227
|
+
for i in 0..bytes.len() {
|
|
1228
|
+
if in_string {
|
|
1229
|
+
if bytes[i] == delim {
|
|
1230
|
+
// Check for doubled quote escape
|
|
1231
|
+
if i + 1 < bytes.len() && bytes[i + 1] == delim {
|
|
1232
|
+
continue; // skip
|
|
1233
|
+
}
|
|
1234
|
+
in_string = false;
|
|
1235
|
+
}
|
|
1236
|
+
} else if bytes[i] == b'\'' || bytes[i] == b'"' {
|
|
1237
|
+
in_string = true;
|
|
1238
|
+
delim = bytes[i];
|
|
1239
|
+
} else if bytes[i] == b'!' {
|
|
1240
|
+
// Found inline comment. Check spacing before it.
|
|
1241
|
+
if i == 0 { return line.clone(); } // standalone comment
|
|
1242
|
+
// Skip directives (!$acc, !$omp), standalone Doxygen (!>), and !! continuations
|
|
1243
|
+
let rest = &line[i..];
|
|
1244
|
+
if rest.starts_with("!$") || rest.starts_with("!>") || rest.starts_with("!!") {
|
|
1245
|
+
return line.clone();
|
|
1246
|
+
}
|
|
1247
|
+
// Count spaces before the `!`
|
|
1248
|
+
let mut spaces = 0;
|
|
1249
|
+
let mut j = i;
|
|
1250
|
+
while j > 0 && bytes[j - 1] == b' ' {
|
|
1251
|
+
spaces += 1;
|
|
1252
|
+
j -= 1;
|
|
1253
|
+
}
|
|
1254
|
+
if j == 0 { return line.clone(); } // all spaces before ! = standalone
|
|
1255
|
+
if spaces >= 2 { return line.clone(); } // already ok
|
|
1256
|
+
// Add spaces to reach 2, but only if it won't exceed line_length
|
|
1257
|
+
let code_part = &line[..i].trim_end();
|
|
1258
|
+
let comment_part = &line[i..];
|
|
1259
|
+
return format!("{} {}", code_part, comment_part);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
line.clone()
|
|
1263
|
+
}).collect()
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
/// Strip trailing semicolons from lines (Fortitude S081).
|
|
1267
|
+
/// Removes `;` at the end of a line (before any trailing comment), but only if
|
|
1268
|
+
/// it's truly trailing — not between statements on the same line.
|
|
1269
|
+
fn strip_trailing_semicolons(lines: &[String]) -> Vec<String> {
|
|
1270
|
+
lines.iter().map(|line| {
|
|
1271
|
+
let trimmed = line.trim_start();
|
|
1272
|
+
// Skip blank, comment-only, preprocessor, Fypp lines
|
|
1273
|
+
if trimmed.is_empty() || trimmed.starts_with('!') || trimmed.starts_with('#')
|
|
1274
|
+
|| trimmed.starts_with("$:") || trimmed.starts_with("@:") {
|
|
1275
|
+
return line.clone();
|
|
1276
|
+
}
|
|
1277
|
+
// Find the code portion (before any inline comment)
|
|
1278
|
+
let bytes = line.as_bytes();
|
|
1279
|
+
let mut in_string = false;
|
|
1280
|
+
let mut delim = b' ';
|
|
1281
|
+
let mut comment_start = bytes.len();
|
|
1282
|
+
for i in 0..bytes.len() {
|
|
1283
|
+
if in_string {
|
|
1284
|
+
if bytes[i] == delim {
|
|
1285
|
+
if i + 1 < bytes.len() && bytes[i + 1] == delim {
|
|
1286
|
+
continue;
|
|
1287
|
+
}
|
|
1288
|
+
in_string = false;
|
|
1289
|
+
}
|
|
1290
|
+
} else if bytes[i] == b'\'' || bytes[i] == b'"' {
|
|
1291
|
+
in_string = true;
|
|
1292
|
+
delim = bytes[i];
|
|
1293
|
+
} else if bytes[i] == b'!' {
|
|
1294
|
+
comment_start = i;
|
|
1295
|
+
break;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
let code = &line[..comment_start];
|
|
1299
|
+
let comment = &line[comment_start..];
|
|
1300
|
+
let code_trimmed = code.trim_end();
|
|
1301
|
+
if code_trimmed.ends_with(';') {
|
|
1302
|
+
let stripped = code_trimmed.strip_suffix(';').unwrap().trim_end();
|
|
1303
|
+
if comment.is_empty() {
|
|
1304
|
+
return format!("{}{}", &line[..line.len() - line.trim_start().len()], stripped.trim_start());
|
|
1305
|
+
} else {
|
|
1306
|
+
return format!("{} {}", stripped, comment.trim_start());
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
line.clone()
|
|
1310
|
+
}).collect()
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1203
1313
|
/// Ensure a blank line between the last declaration and the first executable statement
|
|
1204
1314
|
/// within procedure bodies. Only acts when there is no blank line already.
|
|
1205
1315
|
fn separate_declarations_from_code(lines: &[String]) -> Vec<String> {
|
|
@@ -1442,10 +1552,27 @@ fn rewrap_line(line: &str, max_length: usize, indent_width: usize) -> Vec<String
|
|
|
1442
1552
|
return vec![line.to_string()];
|
|
1443
1553
|
}
|
|
1444
1554
|
|
|
1445
|
-
// Align continuation
|
|
1446
|
-
|
|
1447
|
-
let paren_col =
|
|
1448
|
-
|
|
1555
|
+
// Align continuation with opening paren (string-aware), capped to avoid extreme indentation
|
|
1556
|
+
let flat_indent = indent + indent_width;
|
|
1557
|
+
let paren_col = {
|
|
1558
|
+
let mut in_str = false;
|
|
1559
|
+
let mut qch = b' ';
|
|
1560
|
+
let mut found = None;
|
|
1561
|
+
let cb = content.as_bytes();
|
|
1562
|
+
for i in 0..cb.len() {
|
|
1563
|
+
if in_str {
|
|
1564
|
+
if cb[i] == qch { if i + 1 < cb.len() && cb[i + 1] == qch { continue; } in_str = false; }
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
if cb[i] == b'\'' || cb[i] == b'"' { in_str = true; qch = cb[i]; continue; }
|
|
1568
|
+
if cb[i] == b'(' { found = Some(indent + i + 1); break; }
|
|
1569
|
+
}
|
|
1570
|
+
found
|
|
1571
|
+
};
|
|
1572
|
+
let cont_indent = match paren_col {
|
|
1573
|
+
Some(col) if col <= indent + 40 => col,
|
|
1574
|
+
_ => flat_indent,
|
|
1575
|
+
};
|
|
1449
1576
|
let cont_prefix = " ".repeat(cont_indent);
|
|
1450
1577
|
|
|
1451
1578
|
// Available width: max_length minus indent minus " &" suffix
|
|
@@ -1498,13 +1625,26 @@ fn rewrap_line(line: &str, max_length: usize, indent_width: usize) -> Vec<String
|
|
|
1498
1625
|
} else if last_other > 0 {
|
|
1499
1626
|
last_other
|
|
1500
1627
|
} else {
|
|
1501
|
-
// No break
|
|
1502
|
-
|
|
1503
|
-
|
|
1628
|
+
// No break within limit — find first comma AFTER limit (long string literal)
|
|
1629
|
+
let mut first_comma_after = 0usize;
|
|
1630
|
+
for &(bp, kind) in &breaks {
|
|
1631
|
+
if bp <= pos { continue; }
|
|
1632
|
+
if matches!(kind, BreakKind::Comma) {
|
|
1633
|
+
first_comma_after = bp - pos;
|
|
1634
|
+
break;
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
if first_comma_after > 0 {
|
|
1638
|
+
first_comma_after
|
|
1504
1639
|
} else {
|
|
1505
|
-
|
|
1640
|
+
// No break point at all — emit as-is
|
|
1641
|
+
if is_first {
|
|
1642
|
+
result.push(format!("{}{}", " ".repeat(indent), remaining));
|
|
1643
|
+
} else {
|
|
1644
|
+
result.push(format!("{}& {}", cont_prefix, remaining));
|
|
1645
|
+
}
|
|
1646
|
+
break;
|
|
1506
1647
|
}
|
|
1507
|
-
break;
|
|
1508
1648
|
};
|
|
1509
1649
|
|
|
1510
1650
|
let chunk = content[pos..pos + best_break].trim_end();
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|