Skip to content

Commit 25431a4

Browse files
committed
refactor(language_server): move sub option for flags to the root + deprecate flags
1 parent 5a2832d commit 25431a4

File tree

4 files changed

+161
-82
lines changed

4 files changed

+161
-82
lines changed

crates/oxc_language_server/README.md

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,27 @@ This crate provides an [LSP](https://microsoft.github.io/language-server-protoco
1919

2020
These options can be passed with [initialize](#initialize), [workspace/didChangeConfiguration](#workspace/didChangeConfiguration) and [workspace/configuration](#workspace/configuration).
2121

22-
| Option Key | Value(s) | Default | Description |
23-
| ------------------------- | ------------------------------ | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
24-
| `run` | `"onSave" \| "onType"` | `"onType"` | Should the server lint the files when the user is typing or saving |
25-
| `configPath` | `<string>` \| `null` | `null` | Path to a oxlint configuration file, passing a string will disable nested configuration |
26-
| `tsConfigPath` | `<string>` \| `null` | `null` | Path to a TypeScript configuration file. If your `tsconfig.json` is not at the root, alias paths will not be resolve correctly for the `import` plugin |
27-
| `unusedDisableDirectives` | `"allow" \| "warn"` \| "deny"` | `"allow"` | Define how directive comments like `// oxlint-disable-line` should be reported, when no errors would have been reported on that line anyway |
28-
| `typeAware` | `true` \| `false` | `false` | Enables type-aware linting |
29-
| `flags` | `Map<string, string>` | `<empty>` | Special oxc language server flags, currently only one flag key is supported: `disable_nested_config` |
30-
| `fmt.experimental` | `true` \| `false` | `false` | Enables experimental formatting with `oxc_formatter` |
31-
| `fmt.configPath` | `<string>` \| `null` | `null` | Path to a oxfmt configuration file, when `null` is passed, the server will use `.oxfmtrc.json` and the workspace root |
22+
| Option Key | Value(s) | Default | Description |
23+
| ------------------------- | --------------------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
24+
| `run` | `"onSave" \| "onType"` | `"onType"` | Should the server lint the files when the user is typing or saving |
25+
| `configPath` | `<string>` \| `null` | `null` | Path to a oxlint configuration file, passing a string will disable nested configuration |
26+
| `tsConfigPath` | `<string>` \| `null` | `null` | Path to a TypeScript configuration file. If your `tsconfig.json` is not at the root, alias paths will not be resolve correctly for the `import` plugin |
27+
| `unusedDisableDirectives` | `"allow" \| "warn"` \| "deny"` | `"allow"` | Define how directive comments like `// oxlint-disable-line` should be reported, when no errors would have been reported on that line anyway |
28+
| `typeAware` | `true` \| `false` | `false` | Enables type-aware linting |
29+
| `disableNestedConfig` | `false` \| `true` | `false` | Disabled nested configuration and searches only for `configPath`. |
30+
| `fixKind` | [fixKind values](#fixkind-values) | `safe_fix` | The level of a possible fix for d diagnostic, will be applied for the complete workspace (diagnostic, code action, commands and more). |
31+
| `fmt.experimental` | `true` \| `false` | `false` | Enables experimental formatting with `oxc_formatter` |
32+
| `fmt.configPath` | `<string>` \| `null` | `null` | Path to a oxfmt configuration file, when `null` is passed, the server will use `.oxfmtrc.json` and the workspace root |
33+
| `flags` | `Map<string, string>` | `<empty>` | (deprecated) Custom flags passed to the language server. |
34+
35+
### `fixKind` values:
36+
37+
- `"safe_fix"` (default)
38+
- `"safe_fix_or_suggestion`
39+
- `"dangerous_fix"`
40+
- `"dangerous_fix_or_suggestion"`
41+
- `"none"`
42+
- `"all"`
3243

3344
## Supported LSP Specifications from Server
3445

@@ -47,18 +58,19 @@ The client can pass the workspace options like following:
4758
"tsConfigPath": null,
4859
"unusedDisableDirectives": "allow",
4960
"typeAware": false,
50-
"flags": {},
61+
"disableNestedConfig": false,
62+
"fixKind": "safe_fix",
5163
"fmt.experimental": false,
5264
"fmt.configPath": null
5365
}
5466
}]
5567
}
5668
```
5769

58-
#### Flags
70+
#### Flags (deprecated)
5971

6072
- `key: disable_nested_config`: Disabled nested configuration and searches only for `configPath`
61-
- `key: fix_kind`: default: `"safe_fix"`, possible values `"safe_fix" | "safe_fix_or_suggestion" | "dangerous_fix" | "dangerous_fix_or_suggestion" | "none" | "all"`
73+
- `key: fix_kind`: see [FixKind values](#fixkind-values) for possible values
6274

6375
### [initialized](https://microsoft.github.io/language-server-protocol/specification#initialized)
6476

@@ -85,7 +97,8 @@ The client can pass the workspace options like following:
8597
"tsConfigPath": null,
8698
"unusedDisableDirectives": "allow",
8799
"typeAware": false,
88-
"flags": {},
100+
"disableNestedConfig": false,
101+
"fixKind": "safe_fix",
89102
"fmt.experimental": false,
90103
"fmt.configPath": null
91104
}
@@ -180,7 +193,8 @@ The client can return a response like:
180193
"tsConfigPath": null,
181194
"unusedDisableDirectives": "allow",
182195
"typeAware": false,
183-
"flags": {},
196+
"disableNestedConfig": false,
197+
"fixKind": "safe_fix",
184198
"fmt.experimental": false,
185199
"fmt.configPath": null
186200
}]

crates/oxc_language_server/src/linter/options.rs

Lines changed: 119 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use log::info;
21
use rustc_hash::{FxBuildHasher, FxHashMap};
32
use serde::{Deserialize, Deserializer, Serialize, de::Error};
43
use serde_json::Value;
@@ -30,27 +29,38 @@ pub struct LintOptions {
3029
pub ts_config_path: Option<String>,
3130
pub unused_disable_directives: UnusedDisableDirectives,
3231
pub type_aware: bool,
33-
pub flags: FxHashMap<String, String>,
32+
pub disable_nested_config: bool,
33+
pub fix_kind: LintFixKindFlag,
3434
}
3535

36-
impl LintOptions {
37-
pub fn use_nested_configs(&self) -> bool {
38-
!self.flags.contains_key("disable_nested_config") && self.config_path.is_none()
36+
#[derive(Debug, Default, Serialize, PartialEq, Eq, Deserialize, Clone)]
37+
#[serde(rename_all = "snake_case")]
38+
pub enum LintFixKindFlag {
39+
#[default]
40+
SafeFix,
41+
SafeFixOrSuggestion,
42+
DangerousFix,
43+
DangerousFixOrSuggestion,
44+
None,
45+
All,
46+
}
47+
48+
impl From<LintFixKindFlag> for FixKind {
49+
fn from(flag: LintFixKindFlag) -> Self {
50+
match flag {
51+
LintFixKindFlag::SafeFix => FixKind::SafeFix,
52+
LintFixKindFlag::SafeFixOrSuggestion => FixKind::SafeFixOrSuggestion,
53+
LintFixKindFlag::DangerousFix => FixKind::DangerousFix,
54+
LintFixKindFlag::DangerousFixOrSuggestion => FixKind::DangerousFixOrSuggestion,
55+
LintFixKindFlag::None => FixKind::None,
56+
LintFixKindFlag::All => FixKind::All,
57+
}
3958
}
59+
}
4060

41-
pub fn fix_kind(&self) -> FixKind {
42-
self.flags.get("fix_kind").map_or(FixKind::SafeFix, |kind| match kind.as_str() {
43-
"safe_fix" => FixKind::SafeFix,
44-
"safe_fix_or_suggestion" => FixKind::SafeFixOrSuggestion,
45-
"dangerous_fix" => FixKind::DangerousFix,
46-
"dangerous_fix_or_suggestion" => FixKind::DangerousFixOrSuggestion,
47-
"none" => FixKind::None,
48-
"all" => FixKind::All,
49-
_ => {
50-
info!("invalid fix_kind flag `{kind}`, fallback to `safe_fix`");
51-
FixKind::SafeFix
52-
}
53-
})
61+
impl LintOptions {
62+
pub fn use_nested_configs(&self) -> bool {
63+
!self.disable_nested_config && self.config_path.is_none()
5464
}
5565
}
5666

@@ -72,17 +82,17 @@ impl TryFrom<Value> for LintOptions {
7282
return Err("no object passed".to_string());
7383
};
7484

85+
// deprecated flags field
7586
let mut flags = FxHashMap::with_capacity_and_hasher(2, FxBuildHasher);
7687
if let Some(json_flags) = object.get("flags").and_then(|value| value.as_object()) {
7788
if let Some(disable_nested_config) =
7889
json_flags.get("disable_nested_config").and_then(|value| value.as_str())
7990
{
80-
flags
81-
.insert("disable_nested_config".to_string(), disable_nested_config.to_string());
91+
flags.insert("disable_nested_config".to_string(), disable_nested_config);
8292
}
8393

8494
if let Some(fix_kind) = json_flags.get("fix_kind").and_then(|value| value.as_str()) {
85-
flags.insert("fix_kind".to_string(), fix_kind.to_string());
95+
flags.insert("fix_kind".to_string(), fix_kind);
8696
}
8797
}
8898

@@ -107,14 +117,30 @@ impl TryFrom<Value> for LintOptions {
107117
type_aware: object
108118
.get("typeAware")
109119
.is_some_and(|key| serde_json::from_value::<bool>(key.clone()).unwrap_or_default()),
110-
flags,
120+
disable_nested_config: object
121+
.get("disableNestedConfig")
122+
.and_then(|key| serde_json::from_value::<bool>(key.clone()).ok())
123+
.unwrap_or(flags.contains_key("disable_nested_config")),
124+
fix_kind: object
125+
.get("fixKind")
126+
.and_then(|key| serde_json::from_value::<LintFixKindFlag>(key.clone()).ok())
127+
.unwrap_or_else(|| match flags.get("fix_kind") {
128+
Some(&"safe_fix") => LintFixKindFlag::SafeFix,
129+
Some(&"safe_fix_or_suggestion") => LintFixKindFlag::SafeFixOrSuggestion,
130+
Some(&"dangerous_fix") => LintFixKindFlag::DangerousFix,
131+
Some(&"dangerous_fix_or_suggestion") => {
132+
LintFixKindFlag::DangerousFixOrSuggestion
133+
}
134+
Some(&"none") => LintFixKindFlag::None,
135+
Some(&"all") => LintFixKindFlag::All,
136+
_ => LintFixKindFlag::default(),
137+
}),
111138
})
112139
}
113140
}
114141

115142
#[cfg(test)]
116143
mod test {
117-
use rustc_hash::FxHashMap;
118144
use serde_json::json;
119145

120146
use super::{LintOptions, Run, UnusedDisableDirectives};
@@ -126,19 +152,17 @@ mod test {
126152
"configPath": "./custom.json",
127153
"unusedDisableDirectives": "warn",
128154
"typeAware": true,
129-
"flags": {
130-
"disable_nested_config": "true",
131-
"fix_kind": "dangerous_fix"
132-
}
155+
"disableNestedConfig": true,
156+
"fixKind": "dangerous_fix"
133157
});
134158

135159
let options = LintOptions::try_from(json).unwrap();
136160
assert_eq!(options.run, Run::OnSave);
137161
assert_eq!(options.config_path, Some("./custom.json".into()));
138162
assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Warn);
139163
assert!(options.type_aware);
140-
assert_eq!(options.flags.get("disable_nested_config"), Some(&"true".to_string()));
141-
assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string()));
164+
assert!(options.disable_nested_config);
165+
assert_eq!(options.fix_kind, super::LintFixKindFlag::DangerousFix);
142166
}
143167

144168
#[test]
@@ -150,7 +174,8 @@ mod test {
150174
assert_eq!(options.config_path, None);
151175
assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Allow);
152176
assert!(!options.type_aware);
153-
assert!(options.flags.is_empty());
177+
assert!(!options.disable_nested_config);
178+
assert_eq!(options.fix_kind, super::LintFixKindFlag::SafeFix);
154179
}
155180

156181
#[test]
@@ -163,24 +188,6 @@ mod test {
163188
let options = LintOptions::try_from(json).unwrap();
164189
assert_eq!(options.run, Run::OnType); // fallback
165190
assert_eq!(options.config_path, Some("./custom.json".into()));
166-
assert!(options.flags.is_empty());
167-
}
168-
169-
#[test]
170-
fn test_invalid_flags_options_json() {
171-
let json = json!({
172-
"configPath": "./custom.json",
173-
"flags": {
174-
"disable_nested_config": true, // should be string
175-
"fix_kind": "dangerous_fix"
176-
}
177-
});
178-
179-
let options = LintOptions::try_from(json).unwrap();
180-
assert_eq!(options.run, Run::OnType); // fallback
181-
assert_eq!(options.config_path, Some("./custom.json".into()));
182-
assert_eq!(options.flags.get("disable_nested_config"), None);
183-
assert_eq!(options.flags.get("fix_kind"), Some(&"dangerous_fix".to_string()));
184191
}
185192

186193
#[test]
@@ -192,10 +199,70 @@ mod test {
192199
LintOptions { config_path: Some("config.json".to_string()), ..Default::default() };
193200
assert!(!options.use_nested_configs());
194201

195-
let mut flags = FxHashMap::default();
196-
flags.insert("disable_nested_config".to_string(), "true".to_string());
197-
198-
let options = LintOptions { flags, ..Default::default() };
202+
let options = LintOptions { disable_nested_config: true, ..Default::default() };
199203
assert!(!options.use_nested_configs());
200204
}
205+
206+
mod deprecated_flags {
207+
use serde_json::json;
208+
209+
use crate::linter::options::LintFixKindFlag;
210+
211+
use super::{LintOptions, Run, UnusedDisableDirectives};
212+
213+
#[test]
214+
fn test_valid_options_json_deprecated_flags() {
215+
let json = json!({
216+
"run": "onSave",
217+
"configPath": "./custom.json",
218+
"unusedDisableDirectives": "warn",
219+
"typeAware": true,
220+
"flags": {
221+
"disable_nested_config": "true",
222+
"fix_kind": "dangerous_fix"
223+
}
224+
});
225+
226+
let options = LintOptions::try_from(json).unwrap();
227+
assert_eq!(options.run, Run::OnSave);
228+
assert_eq!(options.config_path, Some("./custom.json".into()));
229+
assert_eq!(options.unused_disable_directives, UnusedDisableDirectives::Warn);
230+
assert!(options.type_aware);
231+
assert!(options.disable_nested_config);
232+
assert_eq!(options.fix_kind, LintFixKindFlag::DangerousFix);
233+
}
234+
235+
#[test]
236+
fn test_invalid_flags_options_json() {
237+
let json = json!({
238+
"configPath": "./custom.json",
239+
"flags": {
240+
"disable_nested_config": true, // should be string
241+
"fix_kind": "dangerous_fix"
242+
}
243+
});
244+
245+
let options = LintOptions::try_from(json).unwrap();
246+
assert_eq!(options.run, Run::OnType); // fallback
247+
assert_eq!(options.config_path, Some("./custom.json".into()));
248+
assert!(!options.disable_nested_config); // fallback
249+
assert_eq!(options.fix_kind, LintFixKindFlag::DangerousFix);
250+
}
251+
252+
#[test]
253+
fn test_root_options_overrides_flags() {
254+
let json = json!({
255+
"disableNestedConfig": false,
256+
"fixKind": "safe_fix_or_suggestion",
257+
"flags": {
258+
"disable_nested_config": "true",
259+
"fix_kind": "dangerous_fix"
260+
}
261+
});
262+
263+
let options = LintOptions::try_from(json).unwrap();
264+
assert!(!options.disable_nested_config); // root option takes precedence
265+
assert_eq!(options.fix_kind, LintFixKindFlag::SafeFixOrSuggestion);
266+
}
267+
}
201268
}

0 commit comments

Comments
 (0)