linthis 0.0.7__tar.gz → 0.0.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.
- {linthis-0.0.7 → linthis-0.0.8}/Cargo.lock +8 -1
- {linthis-0.0.7 → linthis-0.0.8}/Cargo.toml +4 -1
- {linthis-0.0.7 → linthis-0.0.8}/PKG-INFO +15 -13
- {linthis-0.0.7 → linthis-0.0.8}/README.md +14 -12
- {linthis-0.0.7 → linthis-0.0.8}/pyproject.toml +1 -1
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/cpp.rs +70 -8
- {linthis-0.0.7 → linthis-0.0.8}/src/config/mod.rs +3 -0
- linthis-0.0.8/src/interactive/editor.rs +300 -0
- linthis-0.0.8/src/interactive/menu.rs +734 -0
- linthis-0.0.8/src/interactive/mod.rs +27 -0
- linthis-0.0.8/src/interactive/nolint.rs +728 -0
- linthis-0.0.8/src/interactive/quickfix.rs +171 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/lib.rs +176 -78
- {linthis-0.0.7 → linthis-0.0.8}/src/main.rs +410 -144
- {linthis-0.0.7 → linthis-0.0.8}/src/utils/mod.rs +63 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/utils/output.rs +36 -7
- {linthis-0.0.7 → linthis-0.0.8}/src/utils/types.rs +28 -7
- {linthis-0.0.7 → linthis-0.0.8}/src/utils/walker.rs +17 -3
- {linthis-0.0.7 → linthis-0.0.8}/.github/workflows/release.yml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/.gitignore +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/CHANGELOG.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/defaults/.clang-tidy +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/defaults/config.toml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/dev.sh +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/AUTO_SYNC.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/GLOBAL_HOOKS.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/SELF_UPDATE.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/config-cli-design.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/init-hooks-design.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/plan-ruff-integration.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/docs/tasks.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/scripts/release.sh +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/benchmark.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/go.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/java.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/mod.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/python.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/rust.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/traits.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/checkers/typescript.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/config/cli.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/fixers/cpplint.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/fixers/mod.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/fixers/source.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/cpp.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/go.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/java.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/mod.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/python.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/rust.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/traits.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/formatters/typescript.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/auto_sync.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/cache.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/config_manager.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/fetcher.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/loader.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/manifest.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/mod.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/plugin/registry.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/presets/mod.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/self_update.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/utils/language.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/src/utils/unicode.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/test-plugin-check/README.md +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/test-plugin-check/linthis-plugin.toml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/fixtures/test-plugin/linthis-plugin.toml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/fixtures/test-plugin/python/ruff.toml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/fixtures/test-plugin/rust/clippy.toml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/fixtures/test-plugin/rust/rustfmt.toml +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/fixtures/us1/good.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/fixtures/us1/unformatted.rs +0 -0
- {linthis-0.0.7 → linthis-0.0.8}/tests/integration/mod.rs +0 -0
|
@@ -497,7 +497,7 @@ dependencies = [
|
|
|
497
497
|
|
|
498
498
|
[[package]]
|
|
499
499
|
name = "linthis"
|
|
500
|
-
version = "0.0.
|
|
500
|
+
version = "0.0.8"
|
|
501
501
|
dependencies = [
|
|
502
502
|
"anyhow",
|
|
503
503
|
"chrono",
|
|
@@ -514,6 +514,7 @@ dependencies = [
|
|
|
514
514
|
"serde",
|
|
515
515
|
"serde_json",
|
|
516
516
|
"serde_yaml",
|
|
517
|
+
"similar",
|
|
517
518
|
"tempfile",
|
|
518
519
|
"thiserror",
|
|
519
520
|
"toml",
|
|
@@ -745,6 +746,12 @@ version = "1.3.0"
|
|
|
745
746
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
746
747
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
|
747
748
|
|
|
749
|
+
[[package]]
|
|
750
|
+
name = "similar"
|
|
751
|
+
version = "2.7.0"
|
|
752
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
753
|
+
checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa"
|
|
754
|
+
|
|
748
755
|
[[package]]
|
|
749
756
|
name = "strsim"
|
|
750
757
|
version = "0.10.0"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "linthis"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.8"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
authors = ["zhlinh"]
|
|
6
6
|
description = "A fast, cross-platform multi-language linter and formatter"
|
|
@@ -42,6 +42,9 @@ regex = "1.8"
|
|
|
42
42
|
# Glob pattern matching
|
|
43
43
|
globset = "0.4"
|
|
44
44
|
|
|
45
|
+
# Diff algorithm
|
|
46
|
+
similar = "2.4"
|
|
47
|
+
|
|
45
48
|
# Lazy initialization
|
|
46
49
|
lazy_static = "1.4"
|
|
47
50
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linthis
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -152,15 +152,16 @@ linthis plugin add --global <alias> <git-url>
|
|
|
152
152
|
|
|
153
153
|
### Use Plugin
|
|
154
154
|
|
|
155
|
+
Plugins are automatically loaded when running linthis. After adding a plugin:
|
|
156
|
+
|
|
155
157
|
```bash
|
|
156
|
-
#
|
|
157
|
-
linthis
|
|
158
|
-
linthis --plugin myplugin
|
|
158
|
+
# Plugin configs are auto-loaded
|
|
159
|
+
linthis
|
|
159
160
|
|
|
160
161
|
# Combine with other options
|
|
161
|
-
linthis -
|
|
162
|
-
linthis --
|
|
163
|
-
linthis --
|
|
162
|
+
linthis -l python -i src/
|
|
163
|
+
linthis --check-only
|
|
164
|
+
linthis --staged
|
|
164
165
|
```
|
|
165
166
|
|
|
166
167
|
### Remove Plugin
|
|
@@ -188,8 +189,9 @@ linthis plugin list
|
|
|
188
189
|
linthis plugin list -g
|
|
189
190
|
linthis plugin list --global
|
|
190
191
|
|
|
191
|
-
#
|
|
192
|
-
linthis
|
|
192
|
+
# Sync (update) plugins
|
|
193
|
+
linthis plugin sync # Sync local plugins
|
|
194
|
+
linthis plugin sync --global # Sync global plugins
|
|
193
195
|
|
|
194
196
|
# Initialize new plugin
|
|
195
197
|
linthis plugin init my-config
|
|
@@ -427,7 +429,6 @@ All modifications preserve TOML file format and comments.
|
|
|
427
429
|
| ----- | ----------------------- | ---------------------------------------- | ----------------------- |
|
|
428
430
|
| `-i` | `--include` | Specify files or directories to check | `-i src -i lib` |
|
|
429
431
|
| `-e` | `--exclude` | Exclude patterns (can be used multiple times) | `-e "*.test.js"` |
|
|
430
|
-
| `-p` | `--plugin` | Use plugin (alias or Git URL) | `-p myplugin` |
|
|
431
432
|
| `-c` | `--check-only` | Check only, no formatting | `-c` |
|
|
432
433
|
| `-f` | `--format-only` | Format only, no checking | `-f` |
|
|
433
434
|
| `-s` | `--staged` | Check only Git staged files | `-s` |
|
|
@@ -438,9 +439,9 @@ All modifications preserve TOML file format and comments.
|
|
|
438
439
|
| | `--config` | Specify config file path | `--config custom.toml` |
|
|
439
440
|
| | `--init` | Initialize .linthis.toml config file | `--init` |
|
|
440
441
|
| | `--preset` | Format preset | `--preset google` |
|
|
441
|
-
| | `--plugin-update` | Force update plugin cache | `--plugin-update` |
|
|
442
442
|
| | `--no-default-excludes` | Disable default exclude rules | `--no-default-excludes` |
|
|
443
443
|
| | `--no-gitignore` | Disable .gitignore rules | `--no-gitignore` |
|
|
444
|
+
| | `--no-plugin` | Skip loading plugins, use default config | `--no-plugin` |
|
|
444
445
|
|
|
445
446
|
### Plugin Management Subcommands
|
|
446
447
|
|
|
@@ -695,7 +696,7 @@ git push -u origin main
|
|
|
695
696
|
|
|
696
697
|
```bash
|
|
697
698
|
linthis plugin add company https://github.com/mycompany/linthis-standards.git
|
|
698
|
-
linthis
|
|
699
|
+
linthis # Plugin configs are auto-loaded
|
|
699
700
|
```
|
|
700
701
|
|
|
701
702
|
## FAQ
|
|
@@ -721,7 +722,8 @@ linthis -l python # Only check Python files
|
|
|
721
722
|
### Q: How to update plugins?
|
|
722
723
|
|
|
723
724
|
```bash
|
|
724
|
-
linthis
|
|
725
|
+
linthis plugin sync # Sync local plugins
|
|
726
|
+
linthis plugin sync --global # Sync global plugins
|
|
725
727
|
```
|
|
726
728
|
|
|
727
729
|
### Q: What is the plugin Git reference (ref) used for?
|
|
@@ -130,15 +130,16 @@ linthis plugin add --global <alias> <git-url>
|
|
|
130
130
|
|
|
131
131
|
### Use Plugin
|
|
132
132
|
|
|
133
|
+
Plugins are automatically loaded when running linthis. After adding a plugin:
|
|
134
|
+
|
|
133
135
|
```bash
|
|
134
|
-
#
|
|
135
|
-
linthis
|
|
136
|
-
linthis --plugin myplugin
|
|
136
|
+
# Plugin configs are auto-loaded
|
|
137
|
+
linthis
|
|
137
138
|
|
|
138
139
|
# Combine with other options
|
|
139
|
-
linthis -
|
|
140
|
-
linthis --
|
|
141
|
-
linthis --
|
|
140
|
+
linthis -l python -i src/
|
|
141
|
+
linthis --check-only
|
|
142
|
+
linthis --staged
|
|
142
143
|
```
|
|
143
144
|
|
|
144
145
|
### Remove Plugin
|
|
@@ -166,8 +167,9 @@ linthis plugin list
|
|
|
166
167
|
linthis plugin list -g
|
|
167
168
|
linthis plugin list --global
|
|
168
169
|
|
|
169
|
-
#
|
|
170
|
-
linthis
|
|
170
|
+
# Sync (update) plugins
|
|
171
|
+
linthis plugin sync # Sync local plugins
|
|
172
|
+
linthis plugin sync --global # Sync global plugins
|
|
171
173
|
|
|
172
174
|
# Initialize new plugin
|
|
173
175
|
linthis plugin init my-config
|
|
@@ -405,7 +407,6 @@ All modifications preserve TOML file format and comments.
|
|
|
405
407
|
| ----- | ----------------------- | ---------------------------------------- | ----------------------- |
|
|
406
408
|
| `-i` | `--include` | Specify files or directories to check | `-i src -i lib` |
|
|
407
409
|
| `-e` | `--exclude` | Exclude patterns (can be used multiple times) | `-e "*.test.js"` |
|
|
408
|
-
| `-p` | `--plugin` | Use plugin (alias or Git URL) | `-p myplugin` |
|
|
409
410
|
| `-c` | `--check-only` | Check only, no formatting | `-c` |
|
|
410
411
|
| `-f` | `--format-only` | Format only, no checking | `-f` |
|
|
411
412
|
| `-s` | `--staged` | Check only Git staged files | `-s` |
|
|
@@ -416,9 +417,9 @@ All modifications preserve TOML file format and comments.
|
|
|
416
417
|
| | `--config` | Specify config file path | `--config custom.toml` |
|
|
417
418
|
| | `--init` | Initialize .linthis.toml config file | `--init` |
|
|
418
419
|
| | `--preset` | Format preset | `--preset google` |
|
|
419
|
-
| | `--plugin-update` | Force update plugin cache | `--plugin-update` |
|
|
420
420
|
| | `--no-default-excludes` | Disable default exclude rules | `--no-default-excludes` |
|
|
421
421
|
| | `--no-gitignore` | Disable .gitignore rules | `--no-gitignore` |
|
|
422
|
+
| | `--no-plugin` | Skip loading plugins, use default config | `--no-plugin` |
|
|
422
423
|
|
|
423
424
|
### Plugin Management Subcommands
|
|
424
425
|
|
|
@@ -673,7 +674,7 @@ git push -u origin main
|
|
|
673
674
|
|
|
674
675
|
```bash
|
|
675
676
|
linthis plugin add company https://github.com/mycompany/linthis-standards.git
|
|
676
|
-
linthis
|
|
677
|
+
linthis # Plugin configs are auto-loaded
|
|
677
678
|
```
|
|
678
679
|
|
|
679
680
|
## FAQ
|
|
@@ -699,7 +700,8 @@ linthis -l python # Only check Python files
|
|
|
699
700
|
### Q: How to update plugins?
|
|
700
701
|
|
|
701
702
|
```bash
|
|
702
|
-
linthis
|
|
703
|
+
linthis plugin sync # Sync local plugins
|
|
704
|
+
linthis plugin sync --global # Sync global plugins
|
|
703
705
|
```
|
|
704
706
|
|
|
705
707
|
### Q: What is the plugin Git reference (ref) used for?
|
|
@@ -44,6 +44,10 @@ pub struct CppChecker {
|
|
|
44
44
|
cpplint_cpp_config: CpplintConfig,
|
|
45
45
|
/// Cpplint config for Objective-C files
|
|
46
46
|
cpplint_oc_config: CpplintConfig,
|
|
47
|
+
/// Clang-tidy checks to ignore for C++ files
|
|
48
|
+
cpp_ignored_checks: Vec<String>,
|
|
49
|
+
/// Clang-tidy checks to ignore for Objective-C files
|
|
50
|
+
oc_ignored_checks: Vec<String>,
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
impl CppChecker {
|
|
@@ -54,11 +58,16 @@ impl CppChecker {
|
|
|
54
58
|
// Load clang-tidy config from linthis plugin configs
|
|
55
59
|
let clang_tidy_config = Self::find_plugin_clang_tidy_config();
|
|
56
60
|
|
|
61
|
+
// Load ignored checks for clang-tidy
|
|
62
|
+
let (cpp_ignored, oc_ignored) = Self::load_ignored_checks();
|
|
63
|
+
|
|
57
64
|
Self {
|
|
58
65
|
config_path: clang_tidy_config,
|
|
59
66
|
compile_commands_dir: None,
|
|
60
67
|
cpplint_cpp_config: cpp_config,
|
|
61
68
|
cpplint_oc_config: oc_config,
|
|
69
|
+
cpp_ignored_checks: cpp_ignored,
|
|
70
|
+
oc_ignored_checks: oc_ignored,
|
|
62
71
|
}
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -147,6 +156,34 @@ impl CppChecker {
|
|
|
147
156
|
(cpp_config, oc_config)
|
|
148
157
|
}
|
|
149
158
|
|
|
159
|
+
/// Load clang-tidy ignored checks from linthis configuration
|
|
160
|
+
fn load_ignored_checks() -> (Vec<String>, Vec<String>) {
|
|
161
|
+
use crate::config::Config;
|
|
162
|
+
|
|
163
|
+
let project_dir = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
|
|
164
|
+
let merged = Config::load_merged(&project_dir);
|
|
165
|
+
|
|
166
|
+
// Default ignored checks for Objective-C (ARC-related false positives)
|
|
167
|
+
let default_oc_ignored = vec![
|
|
168
|
+
"clang-analyzer-osx.cocoa.RetainCount".to_string(),
|
|
169
|
+
"clang-analyzer-osx.cocoa.Dealloc".to_string(),
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
let cpp_ignored = merged
|
|
173
|
+
.language_overrides
|
|
174
|
+
.cpp
|
|
175
|
+
.and_then(|c| c.clang_tidy_ignored_checks)
|
|
176
|
+
.unwrap_or_default();
|
|
177
|
+
|
|
178
|
+
let oc_ignored = merged
|
|
179
|
+
.language_overrides
|
|
180
|
+
.oc
|
|
181
|
+
.and_then(|c| c.clang_tidy_ignored_checks)
|
|
182
|
+
.unwrap_or(default_oc_ignored);
|
|
183
|
+
|
|
184
|
+
(cpp_ignored, oc_ignored)
|
|
185
|
+
}
|
|
186
|
+
|
|
150
187
|
/// Merge two filter strings, removing duplicates
|
|
151
188
|
fn merge_filters(base: Option<&str>, additional: &str) -> String {
|
|
152
189
|
use std::collections::HashSet;
|
|
@@ -423,7 +460,16 @@ impl CppChecker {
|
|
|
423
460
|
.map_err(|e| crate::LintisError::Checker(format!("Failed to run clang-tidy: {}", e)))?;
|
|
424
461
|
|
|
425
462
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
|
426
|
-
|
|
463
|
+
|
|
464
|
+
// Select ignored checks based on file type
|
|
465
|
+
let is_oc = Self::is_objective_c(path);
|
|
466
|
+
let ignored_checks = if is_oc {
|
|
467
|
+
&self.oc_ignored_checks
|
|
468
|
+
} else {
|
|
469
|
+
&self.cpp_ignored_checks
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
let issues = Self::parse_clang_tidy_output(&stdout, path, ignored_checks);
|
|
427
473
|
|
|
428
474
|
Ok(issues)
|
|
429
475
|
}
|
|
@@ -470,11 +516,21 @@ impl CppChecker {
|
|
|
470
516
|
|
|
471
517
|
/// Parse clang-tidy output
|
|
472
518
|
/// Format: file:line:col: severity: message [check-name]
|
|
473
|
-
fn parse_clang_tidy_output(
|
|
519
|
+
fn parse_clang_tidy_output(
|
|
520
|
+
output: &str,
|
|
521
|
+
file_path: &Path,
|
|
522
|
+
ignored_checks: &[String],
|
|
523
|
+
) -> Vec<LintIssue> {
|
|
474
524
|
let mut issues = Vec::new();
|
|
475
525
|
|
|
476
526
|
for line in output.lines() {
|
|
477
527
|
if let Some(issue) = Self::parse_clang_tidy_line(line, file_path) {
|
|
528
|
+
// Filter out ignored checks
|
|
529
|
+
if let Some(ref code) = issue.code {
|
|
530
|
+
if ignored_checks.iter().any(|ignored| code == ignored) {
|
|
531
|
+
continue;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
478
534
|
issues.push(issue);
|
|
479
535
|
}
|
|
480
536
|
}
|
|
@@ -559,9 +615,12 @@ impl CppChecker {
|
|
|
559
615
|
issue = issue.with_code(c);
|
|
560
616
|
}
|
|
561
617
|
|
|
562
|
-
// Read the source code line
|
|
563
|
-
if let Some(
|
|
564
|
-
issue = issue
|
|
618
|
+
// Read the source code line with context
|
|
619
|
+
if let Some(ctx) = crate::utils::read_file_line_with_context(&file_path, line_num, 1) {
|
|
620
|
+
issue = issue
|
|
621
|
+
.with_code_line(ctx.line)
|
|
622
|
+
.with_context_before(ctx.before)
|
|
623
|
+
.with_context_after(ctx.after);
|
|
565
624
|
}
|
|
566
625
|
|
|
567
626
|
Some(issue)
|
|
@@ -631,9 +690,12 @@ impl CppChecker {
|
|
|
631
690
|
issue = issue.with_code(c);
|
|
632
691
|
}
|
|
633
692
|
|
|
634
|
-
// Read the source code line
|
|
635
|
-
if let Some(
|
|
636
|
-
issue = issue
|
|
693
|
+
// Read the source code line with context
|
|
694
|
+
if let Some(ctx) = crate::utils::read_file_line_with_context(&file_path, line_num, 1) {
|
|
695
|
+
issue = issue
|
|
696
|
+
.with_code_line(ctx.line)
|
|
697
|
+
.with_context_before(ctx.before)
|
|
698
|
+
.with_context_after(ctx.after);
|
|
637
699
|
}
|
|
638
700
|
|
|
639
701
|
Some(issue)
|
|
@@ -176,6 +176,9 @@ pub struct CppLanguageConfig {
|
|
|
176
176
|
/// Cpplint filter rules (e.g., "-build/c++11,-build/header_guard")
|
|
177
177
|
#[serde(default)]
|
|
178
178
|
pub cpplint_filter: Option<String>,
|
|
179
|
+
/// Clang-tidy checks to ignore (e.g., ["clang-analyzer-osx.cocoa.RetainCount"])
|
|
180
|
+
#[serde(default)]
|
|
181
|
+
pub clang_tidy_ignored_checks: Option<Vec<String>>,
|
|
179
182
|
}
|
|
180
183
|
|
|
181
184
|
impl LanguageOverrides {
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
// Copyright 2024 zhlinh and linthis Project Authors. All rights reserved.
|
|
2
|
+
// Use of this source code is governed by a MIT-style
|
|
3
|
+
// license that can be found at
|
|
4
|
+
//
|
|
5
|
+
// https://opensource.org/license/MIT
|
|
6
|
+
//
|
|
7
|
+
// The above copyright notice and this permission
|
|
8
|
+
// notice shall be included in all copies or
|
|
9
|
+
// substantial portions of the Software.
|
|
10
|
+
|
|
11
|
+
//! Cross-platform editor integration for opening files at specific lines.
|
|
12
|
+
|
|
13
|
+
use std::fs;
|
|
14
|
+
use std::path::Path;
|
|
15
|
+
use std::process::Command;
|
|
16
|
+
use similar::{ChangeTag, TextDiff};
|
|
17
|
+
|
|
18
|
+
/// Result of opening a file in an editor
|
|
19
|
+
#[derive(Debug)]
|
|
20
|
+
pub struct EditorResult {
|
|
21
|
+
/// Whether the editor operation succeeded
|
|
22
|
+
pub success: bool,
|
|
23
|
+
/// Lines that were changed (if any)
|
|
24
|
+
pub changes: Vec<LineChange>,
|
|
25
|
+
/// Error message if operation failed
|
|
26
|
+
pub error: Option<String>,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Information about a changed line
|
|
30
|
+
#[derive(Debug, Clone)]
|
|
31
|
+
pub struct LineChange {
|
|
32
|
+
pub line_number: usize,
|
|
33
|
+
pub old_content: String,
|
|
34
|
+
pub new_content: String,
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Open a file in the user's preferred editor at a specific line and detect changes.
|
|
38
|
+
///
|
|
39
|
+
/// # Platform Support
|
|
40
|
+
/// - Unix: Uses $EDITOR environment variable, defaults to vim
|
|
41
|
+
/// - Windows: Uses $EDITOR if set, otherwise tries code, notepad++, then notepad
|
|
42
|
+
///
|
|
43
|
+
/// # Editor-specific line number arguments
|
|
44
|
+
/// - vim/nvim/vi: +{line}
|
|
45
|
+
/// - code (VS Code): --goto {file}:{line}:{column}
|
|
46
|
+
/// - emacs: +{line} {file}
|
|
47
|
+
/// - nano: +{line} {file}
|
|
48
|
+
/// - sublime/subl: {file}:{line}
|
|
49
|
+
/// - notepad++: -n{line} {file}
|
|
50
|
+
/// - atom: {file}:{line}
|
|
51
|
+
///
|
|
52
|
+
/// # Arguments
|
|
53
|
+
/// * `file` - Path to the file to open
|
|
54
|
+
/// * `line` - Line number (1-indexed)
|
|
55
|
+
/// * `column` - Optional column number (1-indexed)
|
|
56
|
+
///
|
|
57
|
+
/// # Returns
|
|
58
|
+
/// * `EditorResult` with change information and success status
|
|
59
|
+
pub fn open_in_editor(file: &Path, line: usize, column: Option<usize>) -> EditorResult {
|
|
60
|
+
// Read file content before editing
|
|
61
|
+
let original_content = match fs::read_to_string(file) {
|
|
62
|
+
Ok(content) => content,
|
|
63
|
+
Err(e) => {
|
|
64
|
+
return EditorResult {
|
|
65
|
+
success: false,
|
|
66
|
+
changes: vec![],
|
|
67
|
+
error: Some(format!("Failed to read file: {}", e)),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
let editor = get_editor();
|
|
72
|
+
let editor_lower = editor.to_lowercase();
|
|
73
|
+
|
|
74
|
+
// Determine editor type and build command
|
|
75
|
+
let mut cmd = Command::new(&editor);
|
|
76
|
+
|
|
77
|
+
// Get the base name of the editor for matching
|
|
78
|
+
let editor_name = Path::new(&editor_lower)
|
|
79
|
+
.file_stem()
|
|
80
|
+
.and_then(|s| s.to_str())
|
|
81
|
+
.unwrap_or(&editor_lower);
|
|
82
|
+
|
|
83
|
+
match editor_name {
|
|
84
|
+
// VS Code family
|
|
85
|
+
"code" | "code-insiders" | "codium" => {
|
|
86
|
+
let col = column.unwrap_or(1);
|
|
87
|
+
cmd.arg("--goto")
|
|
88
|
+
.arg(format!("{}:{}:{}", file.display(), line, col));
|
|
89
|
+
}
|
|
90
|
+
// Vim family
|
|
91
|
+
"vim" | "nvim" | "vi" | "gvim" | "mvim" => {
|
|
92
|
+
cmd.arg(format!("+{}", line)).arg(file);
|
|
93
|
+
}
|
|
94
|
+
// Emacs
|
|
95
|
+
"emacs" | "emacsclient" => {
|
|
96
|
+
cmd.arg(format!("+{}", line)).arg(file);
|
|
97
|
+
}
|
|
98
|
+
// Nano
|
|
99
|
+
"nano" => {
|
|
100
|
+
cmd.arg(format!("+{}", line)).arg(file);
|
|
101
|
+
}
|
|
102
|
+
// Sublime Text
|
|
103
|
+
"sublime" | "subl" | "sublime_text" => {
|
|
104
|
+
let col = column.unwrap_or(1);
|
|
105
|
+
cmd.arg(format!("{}:{}:{}", file.display(), line, col));
|
|
106
|
+
}
|
|
107
|
+
// Notepad++
|
|
108
|
+
"notepad++" => {
|
|
109
|
+
cmd.arg(format!("-n{}", line)).arg(file);
|
|
110
|
+
}
|
|
111
|
+
// Atom (deprecated but still used)
|
|
112
|
+
"atom" => {
|
|
113
|
+
let col = column.unwrap_or(1);
|
|
114
|
+
cmd.arg(format!("{}:{}:{}", file.display(), line, col));
|
|
115
|
+
}
|
|
116
|
+
// Helix
|
|
117
|
+
"hx" | "helix" => {
|
|
118
|
+
let col = column.unwrap_or(1);
|
|
119
|
+
cmd.arg(format!("{}:{}:{}", file.display(), line, col));
|
|
120
|
+
}
|
|
121
|
+
// Kakoune
|
|
122
|
+
"kak" => {
|
|
123
|
+
cmd.arg(format!("+{}", line)).arg(file);
|
|
124
|
+
}
|
|
125
|
+
// JetBrains IDEs (idea, goland, pycharm, etc.) via command line
|
|
126
|
+
name if name.contains("idea") || name.contains("goland") || name.contains("pycharm") => {
|
|
127
|
+
let col = column.unwrap_or(1);
|
|
128
|
+
cmd.arg("--line")
|
|
129
|
+
.arg(line.to_string())
|
|
130
|
+
.arg("--column")
|
|
131
|
+
.arg(col.to_string())
|
|
132
|
+
.arg(file);
|
|
133
|
+
}
|
|
134
|
+
// Default: try vim-style +line argument
|
|
135
|
+
_ => {
|
|
136
|
+
cmd.arg(format!("+{}", line)).arg(file);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Spawn the editor
|
|
141
|
+
let spawn_result = cmd.spawn();
|
|
142
|
+
|
|
143
|
+
match spawn_result {
|
|
144
|
+
Ok(mut child) => {
|
|
145
|
+
// Wait for the editor to close
|
|
146
|
+
match child.wait() {
|
|
147
|
+
Ok(status) => {
|
|
148
|
+
if !status.success() {
|
|
149
|
+
return EditorResult {
|
|
150
|
+
success: false,
|
|
151
|
+
changes: vec![],
|
|
152
|
+
error: Some(format!(
|
|
153
|
+
"Editor '{}' exited with status: {}",
|
|
154
|
+
editor,
|
|
155
|
+
status.code().unwrap_or(-1)
|
|
156
|
+
)),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Read file content after editing
|
|
161
|
+
let new_content = match fs::read_to_string(file) {
|
|
162
|
+
Ok(content) => content,
|
|
163
|
+
Err(e) => {
|
|
164
|
+
return EditorResult {
|
|
165
|
+
success: false,
|
|
166
|
+
changes: vec![],
|
|
167
|
+
error: Some(format!("Failed to read file after editing: {}", e)),
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// Detect changes
|
|
173
|
+
let changes = detect_changes(&original_content, &new_content);
|
|
174
|
+
|
|
175
|
+
EditorResult {
|
|
176
|
+
success: true,
|
|
177
|
+
changes,
|
|
178
|
+
error: None,
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
Err(e) => EditorResult {
|
|
182
|
+
success: false,
|
|
183
|
+
changes: vec![],
|
|
184
|
+
error: Some(format!("Failed to wait for editor '{}': {}", editor, e)),
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
Err(e) => EditorResult {
|
|
189
|
+
success: false,
|
|
190
|
+
changes: vec![],
|
|
191
|
+
error: Some(format!("Failed to launch editor '{}': {}", editor, e)),
|
|
192
|
+
},
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// Detect changes between two versions of file content using proper diff algorithm
|
|
197
|
+
fn detect_changes(original: &str, new: &str) -> Vec<LineChange> {
|
|
198
|
+
let mut changes = Vec::new();
|
|
199
|
+
|
|
200
|
+
// Use the similar crate's TextDiff to compute proper line-based diff
|
|
201
|
+
let diff = TextDiff::from_lines(original, new);
|
|
202
|
+
|
|
203
|
+
// Track current line number in new version
|
|
204
|
+
let mut new_line_num = 0;
|
|
205
|
+
|
|
206
|
+
// Process each change operation
|
|
207
|
+
for change in diff.iter_all_changes() {
|
|
208
|
+
match change.tag() {
|
|
209
|
+
ChangeTag::Equal => {
|
|
210
|
+
// Line unchanged, just increment counter
|
|
211
|
+
new_line_num += 1;
|
|
212
|
+
}
|
|
213
|
+
ChangeTag::Delete => {
|
|
214
|
+
// Line deleted from old version
|
|
215
|
+
changes.push(LineChange {
|
|
216
|
+
line_number: new_line_num + 1, // Position in new file where deletion occurred
|
|
217
|
+
old_content: change.to_string().trim_end().to_string(),
|
|
218
|
+
new_content: String::new(), // Deleted, so new content is empty
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
ChangeTag::Insert => {
|
|
222
|
+
// Line inserted in new version
|
|
223
|
+
new_line_num += 1;
|
|
224
|
+
changes.push(LineChange {
|
|
225
|
+
line_number: new_line_num,
|
|
226
|
+
old_content: String::new(), // Inserted, so old content is empty
|
|
227
|
+
new_content: change.to_string().trim_end().to_string(),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
changes
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/// Get the user's preferred editor from environment variables.
|
|
237
|
+
///
|
|
238
|
+
/// Checks in order:
|
|
239
|
+
/// 1. $EDITOR
|
|
240
|
+
/// 2. $VISUAL
|
|
241
|
+
/// 3. Platform-specific defaults
|
|
242
|
+
fn get_editor() -> String {
|
|
243
|
+
// Check EDITOR first
|
|
244
|
+
if let Ok(editor) = std::env::var("EDITOR") {
|
|
245
|
+
if !editor.is_empty() {
|
|
246
|
+
return editor;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Check VISUAL
|
|
251
|
+
if let Ok(visual) = std::env::var("VISUAL") {
|
|
252
|
+
if !visual.is_empty() {
|
|
253
|
+
return visual;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Platform-specific defaults
|
|
258
|
+
#[cfg(windows)]
|
|
259
|
+
{
|
|
260
|
+
// On Windows, try to find a reasonable editor
|
|
261
|
+
// Check if common editors are available in PATH
|
|
262
|
+
for editor in &["code", "notepad++", "notepad"] {
|
|
263
|
+
if which_exists(editor) {
|
|
264
|
+
return editor.to_string();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
"notepad".to_string()
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#[cfg(not(windows))]
|
|
271
|
+
{
|
|
272
|
+
// On Unix, default to vim
|
|
273
|
+
"vim".to_string()
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/// Check if a command exists in PATH (Windows-compatible)
|
|
278
|
+
#[cfg(windows)]
|
|
279
|
+
fn which_exists(cmd: &str) -> bool {
|
|
280
|
+
use std::process::Stdio;
|
|
281
|
+
Command::new("where")
|
|
282
|
+
.arg(cmd)
|
|
283
|
+
.stdout(Stdio::null())
|
|
284
|
+
.stderr(Stdio::null())
|
|
285
|
+
.status()
|
|
286
|
+
.map(|s| s.success())
|
|
287
|
+
.unwrap_or(false)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
#[cfg(test)]
|
|
291
|
+
mod tests {
|
|
292
|
+
use super::*;
|
|
293
|
+
|
|
294
|
+
#[test]
|
|
295
|
+
fn test_get_editor_default() {
|
|
296
|
+
// This test depends on environment, just ensure it returns something
|
|
297
|
+
let editor = get_editor();
|
|
298
|
+
assert!(!editor.is_empty());
|
|
299
|
+
}
|
|
300
|
+
}
|