Skip to content

Commit e396103

Browse files
Respect .styluaignore in language server mode (#1037)
* Check against styluaignore in LSP * Update changelog
1 parent e0b9d34 commit e396103

File tree

4 files changed

+156
-77
lines changed

4 files changed

+156
-77
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2222
- Fixed comments lost from expression after parentheses are removed when we are attempting to "hang" the expression. ([#1033](https://github.com/JohnnyMorganz/StyLua/issues/1033))
2323
- Fixed `document_range_formatting_provider` capability missing from `ServerCapabilities` in language server mode
2424
- Fixed current working directory incorrectly used as config search root in language server mode -- now, the root of the opened workspace is used instead ([#1032](https://github.com/JohnnyMorganz/StyLua/issues/1032))
25+
- Language server mode now correctly respects `.styluaignore` files ([#1035](https://github.com/JohnnyMorganz/StyLua/issues/1035))
2526

2627
## [2.2.0] - 2025-09-14
2728

src/cli/lsp.rs

Lines changed: 76 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
1212
use similar::{DiffOp, TextDiff};
1313
use stylua_lib::{format_code, IndentType, OutputVerification};
1414

15-
use crate::{config::ConfigResolver, opt};
15+
use crate::{config::ConfigResolver, opt, stylua_ignore};
1616

1717
fn diffop_to_textedit(
1818
op: DiffOp,
@@ -64,6 +64,7 @@ struct LanguageServer<'a> {
6464
documents: TextDocuments,
6565
workspace_folders: Vec<WorkspaceFolder>,
6666
root_uri: Option<Uri>,
67+
search_parent_directories: bool,
6768
respect_editor_formatting_options: bool,
6869
config_resolver: &'a mut ConfigResolver<'a>,
6970
}
@@ -72,19 +73,22 @@ enum FormattingError {
7273
StyLuaError,
7374
NotLuaDocument,
7475
DocumentNotFound,
76+
FileIsIgnored,
7577
}
7678

7779
impl LanguageServer<'_> {
7880
fn new<'a>(
7981
workspace_folders: Vec<WorkspaceFolder>,
8082
root_uri: Option<Uri>,
83+
search_parent_directories: bool,
8184
respect_editor_formatting_options: bool,
8285
config_resolver: &'a mut ConfigResolver<'a>,
8386
) -> LanguageServer<'a> {
8487
LanguageServer {
8588
documents: TextDocuments::new(),
8689
workspace_folders,
8790
root_uri,
91+
search_parent_directories,
8892
respect_editor_formatting_options,
8993
config_resolver,
9094
}
@@ -140,14 +144,24 @@ impl LanguageServer<'_> {
140144
return Err(FormattingError::NotLuaDocument);
141145
}
142146

147+
let search_root = Some(self.find_config_root(uri));
148+
let path = uri.path().as_str().as_ref();
149+
150+
if stylua_ignore::path_is_stylua_ignored(
151+
path,
152+
self.search_parent_directories,
153+
search_root.clone(),
154+
)
155+
.unwrap_or(false)
156+
{
157+
return Err(FormattingError::FileIsIgnored);
158+
}
159+
143160
let contents = document.get_content(None);
144161

145162
let mut config = self
146163
.config_resolver
147-
.load_configuration_with_search_root(
148-
uri.path().as_str().as_ref(),
149-
Some(self.find_config_root(uri)),
150-
)
164+
.load_configuration_with_search_root(path, search_root)
151165
.unwrap_or_default();
152166

153167
if let Some(formatting_options) = formatting_options {
@@ -193,7 +207,8 @@ impl LanguageServer<'_> {
193207
) {
194208
Ok(edits) => Response::new_ok(request.id, edits),
195209
Err(FormattingError::StyLuaError)
196-
| Err(FormattingError::NotLuaDocument) => {
210+
| Err(FormattingError::NotLuaDocument)
211+
| Err(FormattingError::FileIsIgnored) => {
197212
Response::new_ok(request.id, serde_json::Value::Null)
198213
}
199214
Err(FormattingError::DocumentNotFound) => Response::new_err(
@@ -224,7 +239,8 @@ impl LanguageServer<'_> {
224239
) {
225240
Ok(edits) => Response::new_ok(request.id, edits),
226241
Err(FormattingError::StyLuaError)
227-
| Err(FormattingError::NotLuaDocument) => {
242+
| Err(FormattingError::NotLuaDocument)
243+
| Err(FormattingError::FileIsIgnored) => {
228244
Response::new_ok(request.id, serde_json::Value::Null)
229245
}
230246
Err(FormattingError::DocumentNotFound) => Response::new_err(
@@ -266,6 +282,7 @@ struct InitializationOptions {
266282

267283
fn main_loop<'a>(
268284
connection: Connection,
285+
search_parent_directories: bool,
269286
config_resolver: &'a mut ConfigResolver<'a>,
270287
) -> anyhow::Result<()> {
271288
let initialize_result = InitializeResult {
@@ -298,6 +315,7 @@ fn main_loop<'a>(
298315
initialize_params.workspace_folders.unwrap_or_default(),
299316
#[allow(deprecated)]
300317
initialize_params.root_uri,
318+
search_parent_directories,
301319
respect_editor_formatting_options,
302320
config_resolver,
303321
);
@@ -328,7 +346,11 @@ pub fn run(opt: opt::Opt) -> anyhow::Result<()> {
328346

329347
let (connection, io_threads) = Connection::stdio();
330348

331-
main_loop(connection, &mut config_resolver)?;
349+
main_loop(
350+
connection,
351+
opt.search_parent_directories,
352+
&mut config_resolver,
353+
)?;
332354

333355
io_threads.join()?;
334356

@@ -396,7 +418,7 @@ mod tests {
396418
client.sender.send($messages).unwrap();
397419
)*
398420

399-
main_loop(server, &mut config_resolver).unwrap();
421+
main_loop(server, false, &mut config_resolver).unwrap();
400422

401423
$(
402424
$tests(&client.receiver);
@@ -540,7 +562,7 @@ mod tests {
540562
client.sender.send(shutdown(2)).unwrap();
541563
client.sender.send(exit()).unwrap();
542564

543-
main_loop(server, &mut config_resolver).unwrap();
565+
main_loop(server, false, &mut config_resolver).unwrap();
544566

545567
expect_server_initialized(&client.receiver, 1);
546568
expect_server_shutdown(&client.receiver, 2);
@@ -602,7 +624,7 @@ mod tests {
602624
client.sender.send(shutdown(3)).unwrap();
603625
client.sender.send(exit()).unwrap();
604626

605-
main_loop(server, &mut config_resolver).unwrap();
627+
main_loop(server, false, &mut config_resolver).unwrap();
606628

607629
expect_server_initialized(&client.receiver, 1);
608630

@@ -667,7 +689,7 @@ mod tests {
667689
client.sender.send(shutdown(3)).unwrap();
668690
client.sender.send(exit()).unwrap();
669691

670-
main_loop(server, &mut config_resolver).unwrap();
692+
main_loop(server, false, &mut config_resolver).unwrap();
671693

672694
expect_server_initialized(&client.receiver, 1);
673695

@@ -751,7 +773,7 @@ mod tests {
751773
client.sender.send(shutdown(3)).unwrap();
752774
client.sender.send(exit()).unwrap();
753775

754-
main_loop(server, &mut config_resolver).unwrap();
776+
main_loop(server, false, &mut config_resolver).unwrap();
755777

756778
expect_server_initialized(&client.receiver, 1);
757779

@@ -783,7 +805,7 @@ mod tests {
783805
client.sender.send(shutdown(3)).unwrap();
784806
client.sender.send(exit()).unwrap();
785807

786-
main_loop(server, &mut config_resolver).unwrap();
808+
main_loop(server, false, &mut config_resolver).unwrap();
787809

788810
expect_server_initialized(&client.receiver, 1);
789811

@@ -1047,4 +1069,44 @@ mod tests {
10471069
]
10481070
);
10491071
}
1072+
1073+
#[test]
1074+
fn test_lsp_stylua_ignore() {
1075+
let contents = "local x = 1";
1076+
let cwd = construct_tree!({
1077+
".styluaignore": "ignored/",
1078+
"foo.lua": contents,
1079+
"ignored/bar.lua": contents,
1080+
});
1081+
1082+
let foo_uri = Uri::from_str(cwd.child("foo.lua").to_str().unwrap()).unwrap();
1083+
let bar_uri = Uri::from_str(cwd.child("ignored/bar.lua").to_str().unwrap()).unwrap();
1084+
1085+
lsp_test!(
1086+
[],
1087+
[
1088+
initialize(1, Some(cwd.path())),
1089+
initialized(),
1090+
open_text_document(foo_uri.clone(), contents.to_string()),
1091+
open_text_document(bar_uri.clone(), contents.to_string()),
1092+
format_document(2, foo_uri.clone(), FormattingOptions::default()),
1093+
format_document(3, bar_uri.clone(), FormattingOptions::default()),
1094+
shutdown(4),
1095+
exit()
1096+
],
1097+
[
1098+
|receiver| expect_server_initialized(receiver, 1),
1099+
|receiver| {
1100+
let edits: Vec<TextEdit> = expect_response(receiver, 2);
1101+
let formatted = apply_text_edits_to(contents, edits);
1102+
assert_eq!(formatted, "local x = 1\n");
1103+
},
1104+
|receiver| {
1105+
let edits: serde_json::Value = expect_response(receiver, 3);
1106+
assert_eq!(edits, serde_json::Value::Null);
1107+
},
1108+
|receiver| expect_server_shutdown(receiver, 4)
1109+
]
1110+
);
1111+
}
10501112
}

src/cli/main.rs

Lines changed: 10 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use anyhow::{bail, Context, Result};
22
use clap::StructOpt;
33
use console::style;
4-
use ignore::{gitignore::Gitignore, overrides::OverrideBuilder, WalkBuilder};
4+
use ignore::{overrides::OverrideBuilder, WalkBuilder};
55
use log::{LevelFilter, *};
66
use serde_json::json;
77
use std::collections::HashSet;
@@ -16,13 +16,14 @@ use threadpool::ThreadPool;
1616

1717
use stylua_lib::{format_code, Config, OutputVerification, Range};
1818

19-
use crate::config::find_ignore_file_path;
20-
2119
mod config;
2220
#[cfg(feature = "lsp")]
2321
mod lsp;
2422
mod opt;
2523
mod output_diff;
24+
mod stylua_ignore;
25+
26+
use stylua_ignore::{is_explicitly_provided, path_is_stylua_ignored, should_respect_ignores};
2627

2728
static EXIT_CODE: AtomicI32 = AtomicI32::new(0);
2829
static UNFORMATTED_FILE_COUNT: AtomicU32 = AtomicU32::new(0);
@@ -206,64 +207,6 @@ fn format_string(
206207
}
207208
}
208209

209-
fn get_ignore(
210-
directory: &Path,
211-
search_parent_directories: bool,
212-
) -> Result<Gitignore, ignore::Error> {
213-
let file_path = find_ignore_file_path(directory.to_path_buf(), search_parent_directories)
214-
.or_else(|| {
215-
std::env::current_dir()
216-
.ok()
217-
.and_then(|cwd| find_ignore_file_path(cwd, false))
218-
});
219-
220-
if let Some(file_path) = file_path {
221-
let (ignore, err) = Gitignore::new(file_path);
222-
if let Some(err) = err {
223-
Err(err)
224-
} else {
225-
Ok(ignore)
226-
}
227-
} else {
228-
Ok(Gitignore::empty())
229-
}
230-
}
231-
232-
/// Whether the provided path was explicitly provided to the tool
233-
fn is_explicitly_provided(opt: &opt::Opt, path: &Path) -> bool {
234-
opt.files.iter().any(|p| path == *p)
235-
}
236-
237-
/// By default, files explicitly passed to the command line will be formatted regardless of whether
238-
/// they are present in .styluaignore / not glob matched. If `--respect-ignores` is provided,
239-
/// then we enforce .styluaignore / glob matching on explicitly passed paths.
240-
fn should_respect_ignores(opt: &opt::Opt, path: &Path) -> bool {
241-
!is_explicitly_provided(opt, path) || opt.respect_ignores
242-
}
243-
244-
fn path_is_stylua_ignored(path: &Path, search_parent_directories: bool) -> Result<bool> {
245-
let ignore = get_ignore(
246-
path.parent().expect("cannot get parent directory"),
247-
search_parent_directories,
248-
)
249-
.context("failed to parse ignore file")?;
250-
251-
// matched_path_or_any_parents panics when path is not in cwd
252-
// can happen when `--respect-ignores --stdin-filepath {path}`
253-
if !path
254-
.canonicalize()
255-
.unwrap_or_default()
256-
.starts_with(ignore.path().canonicalize().unwrap_or_default())
257-
{
258-
return Ok(false);
259-
}
260-
261-
Ok(matches!(
262-
ignore.matched_path_or_any_parents(path, false),
263-
ignore::Match::Ignore(_)
264-
))
265-
}
266-
267210
fn format(opt: opt::Opt) -> Result<i32> {
268211
debug!("resolved options: {:#?}", opt);
269212

@@ -436,7 +379,11 @@ fn format(opt: opt::Opt) -> Result<i32> {
436379
let should_skip_format = match &opt.stdin_filepath {
437380
Some(path) => {
438381
opt.respect_ignores
439-
&& path_is_stylua_ignored(path, opt.search_parent_directories)?
382+
&& path_is_stylua_ignored(
383+
path,
384+
opt.search_parent_directories,
385+
None,
386+
)?
440387
}
441388
None => false,
442389
};
@@ -501,7 +448,7 @@ fn format(opt: opt::Opt) -> Result<i32> {
501448
// we should check .styluaignore
502449
if is_explicitly_provided(opt.as_ref(), &path)
503450
&& should_respect_ignores(opt.as_ref(), &path)
504-
&& path_is_stylua_ignored(&path, opt.search_parent_directories)?
451+
&& path_is_stylua_ignored(&path, opt.search_parent_directories, None)?
505452
{
506453
continue;
507454
}

0 commit comments

Comments
 (0)