Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- 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))
- Fixed `document_range_formatting_provider` capability missing from `ServerCapabilities` in language server mode
- 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))
- Language server mode now correctly respects `.styluaignore` files ([#1035](https://github.com/JohnnyMorganz/StyLua/issues/1035))

## [2.2.0] - 2025-09-14

Expand Down
90 changes: 76 additions & 14 deletions src/cli/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use serde::{Deserialize, Serialize};
use similar::{DiffOp, TextDiff};
use stylua_lib::{format_code, IndentType, OutputVerification};

use crate::{config::ConfigResolver, opt};
use crate::{config::ConfigResolver, opt, stylua_ignore};

fn diffop_to_textedit(
op: DiffOp,
Expand Down Expand Up @@ -64,6 +64,7 @@ struct LanguageServer<'a> {
documents: TextDocuments,
workspace_folders: Vec<WorkspaceFolder>,
root_uri: Option<Uri>,
search_parent_directories: bool,
respect_editor_formatting_options: bool,
config_resolver: &'a mut ConfigResolver<'a>,
}
Expand All @@ -72,19 +73,22 @@ enum FormattingError {
StyLuaError,
NotLuaDocument,
DocumentNotFound,
FileIsIgnored,
}

impl LanguageServer<'_> {
fn new<'a>(
workspace_folders: Vec<WorkspaceFolder>,
root_uri: Option<Uri>,
search_parent_directories: bool,
respect_editor_formatting_options: bool,
config_resolver: &'a mut ConfigResolver<'a>,
) -> LanguageServer<'a> {
LanguageServer {
documents: TextDocuments::new(),
workspace_folders,
root_uri,
search_parent_directories,
respect_editor_formatting_options,
config_resolver,
}
Expand Down Expand Up @@ -140,14 +144,24 @@ impl LanguageServer<'_> {
return Err(FormattingError::NotLuaDocument);
}

let search_root = Some(self.find_config_root(uri));
let path = uri.path().as_str().as_ref();

if stylua_ignore::path_is_stylua_ignored(
path,
self.search_parent_directories,
search_root.clone(),
)
.unwrap_or(false)
{
return Err(FormattingError::FileIsIgnored);
}

let contents = document.get_content(None);

let mut config = self
.config_resolver
.load_configuration_with_search_root(
uri.path().as_str().as_ref(),
Some(self.find_config_root(uri)),
)
.load_configuration_with_search_root(path, search_root)
.unwrap_or_default();

if let Some(formatting_options) = formatting_options {
Expand Down Expand Up @@ -193,7 +207,8 @@ impl LanguageServer<'_> {
) {
Ok(edits) => Response::new_ok(request.id, edits),
Err(FormattingError::StyLuaError)
| Err(FormattingError::NotLuaDocument) => {
| Err(FormattingError::NotLuaDocument)
| Err(FormattingError::FileIsIgnored) => {
Response::new_ok(request.id, serde_json::Value::Null)
}
Err(FormattingError::DocumentNotFound) => Response::new_err(
Expand Down Expand Up @@ -224,7 +239,8 @@ impl LanguageServer<'_> {
) {
Ok(edits) => Response::new_ok(request.id, edits),
Err(FormattingError::StyLuaError)
| Err(FormattingError::NotLuaDocument) => {
| Err(FormattingError::NotLuaDocument)
| Err(FormattingError::FileIsIgnored) => {
Response::new_ok(request.id, serde_json::Value::Null)
}
Err(FormattingError::DocumentNotFound) => Response::new_err(
Expand Down Expand Up @@ -266,6 +282,7 @@ struct InitializationOptions {

fn main_loop<'a>(
connection: Connection,
search_parent_directories: bool,
config_resolver: &'a mut ConfigResolver<'a>,
) -> anyhow::Result<()> {
let initialize_result = InitializeResult {
Expand Down Expand Up @@ -298,6 +315,7 @@ fn main_loop<'a>(
initialize_params.workspace_folders.unwrap_or_default(),
#[allow(deprecated)]
initialize_params.root_uri,
search_parent_directories,
respect_editor_formatting_options,
config_resolver,
);
Expand Down Expand Up @@ -328,7 +346,11 @@ pub fn run(opt: opt::Opt) -> anyhow::Result<()> {

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

main_loop(connection, &mut config_resolver)?;
main_loop(
connection,
opt.search_parent_directories,
&mut config_resolver,
)?;

io_threads.join()?;

Expand Down Expand Up @@ -396,7 +418,7 @@ mod tests {
client.sender.send($messages).unwrap();
)*

main_loop(server, &mut config_resolver).unwrap();
main_loop(server, false, &mut config_resolver).unwrap();

$(
$tests(&client.receiver);
Expand Down Expand Up @@ -540,7 +562,7 @@ mod tests {
client.sender.send(shutdown(2)).unwrap();
client.sender.send(exit()).unwrap();

main_loop(server, &mut config_resolver).unwrap();
main_loop(server, false, &mut config_resolver).unwrap();

expect_server_initialized(&client.receiver, 1);
expect_server_shutdown(&client.receiver, 2);
Expand Down Expand Up @@ -602,7 +624,7 @@ mod tests {
client.sender.send(shutdown(3)).unwrap();
client.sender.send(exit()).unwrap();

main_loop(server, &mut config_resolver).unwrap();
main_loop(server, false, &mut config_resolver).unwrap();

expect_server_initialized(&client.receiver, 1);

Expand Down Expand Up @@ -667,7 +689,7 @@ mod tests {
client.sender.send(shutdown(3)).unwrap();
client.sender.send(exit()).unwrap();

main_loop(server, &mut config_resolver).unwrap();
main_loop(server, false, &mut config_resolver).unwrap();

expect_server_initialized(&client.receiver, 1);

Expand Down Expand Up @@ -751,7 +773,7 @@ mod tests {
client.sender.send(shutdown(3)).unwrap();
client.sender.send(exit()).unwrap();

main_loop(server, &mut config_resolver).unwrap();
main_loop(server, false, &mut config_resolver).unwrap();

expect_server_initialized(&client.receiver, 1);

Expand Down Expand Up @@ -783,7 +805,7 @@ mod tests {
client.sender.send(shutdown(3)).unwrap();
client.sender.send(exit()).unwrap();

main_loop(server, &mut config_resolver).unwrap();
main_loop(server, false, &mut config_resolver).unwrap();

expect_server_initialized(&client.receiver, 1);

Expand Down Expand Up @@ -1047,4 +1069,44 @@ mod tests {
]
);
}

#[test]
fn test_lsp_stylua_ignore() {
let contents = "local x = 1";
let cwd = construct_tree!({
".styluaignore": "ignored/",
"foo.lua": contents,
"ignored/bar.lua": contents,
});

let foo_uri = Uri::from_str(cwd.child("foo.lua").to_str().unwrap()).unwrap();
let bar_uri = Uri::from_str(cwd.child("ignored/bar.lua").to_str().unwrap()).unwrap();

lsp_test!(
[],
[
initialize(1, Some(cwd.path())),
initialized(),
open_text_document(foo_uri.clone(), contents.to_string()),
open_text_document(bar_uri.clone(), contents.to_string()),
format_document(2, foo_uri.clone(), FormattingOptions::default()),
format_document(3, bar_uri.clone(), FormattingOptions::default()),
shutdown(4),
exit()
],
[
|receiver| expect_server_initialized(receiver, 1),
|receiver| {
let edits: Vec<TextEdit> = expect_response(receiver, 2);
let formatted = apply_text_edits_to(contents, edits);
assert_eq!(formatted, "local x = 1\n");
},
|receiver| {
let edits: serde_json::Value = expect_response(receiver, 3);
assert_eq!(edits, serde_json::Value::Null);
},
|receiver| expect_server_shutdown(receiver, 4)
]
);
}
}
73 changes: 10 additions & 63 deletions src/cli/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{bail, Context, Result};
use clap::StructOpt;
use console::style;
use ignore::{gitignore::Gitignore, overrides::OverrideBuilder, WalkBuilder};
use ignore::{overrides::OverrideBuilder, WalkBuilder};
use log::{LevelFilter, *};
use serde_json::json;
use std::collections::HashSet;
Expand All @@ -16,13 +16,14 @@ use threadpool::ThreadPool;

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

use crate::config::find_ignore_file_path;

mod config;
#[cfg(feature = "lsp")]
mod lsp;
mod opt;
mod output_diff;
mod stylua_ignore;

use stylua_ignore::{is_explicitly_provided, path_is_stylua_ignored, should_respect_ignores};

static EXIT_CODE: AtomicI32 = AtomicI32::new(0);
static UNFORMATTED_FILE_COUNT: AtomicU32 = AtomicU32::new(0);
Expand Down Expand Up @@ -206,64 +207,6 @@ fn format_string(
}
}

fn get_ignore(
directory: &Path,
search_parent_directories: bool,
) -> Result<Gitignore, ignore::Error> {
let file_path = find_ignore_file_path(directory.to_path_buf(), search_parent_directories)
.or_else(|| {
std::env::current_dir()
.ok()
.and_then(|cwd| find_ignore_file_path(cwd, false))
});

if let Some(file_path) = file_path {
let (ignore, err) = Gitignore::new(file_path);
if let Some(err) = err {
Err(err)
} else {
Ok(ignore)
}
} else {
Ok(Gitignore::empty())
}
}

/// Whether the provided path was explicitly provided to the tool
fn is_explicitly_provided(opt: &opt::Opt, path: &Path) -> bool {
opt.files.iter().any(|p| path == *p)
}

/// By default, files explicitly passed to the command line will be formatted regardless of whether
/// they are present in .styluaignore / not glob matched. If `--respect-ignores` is provided,
/// then we enforce .styluaignore / glob matching on explicitly passed paths.
fn should_respect_ignores(opt: &opt::Opt, path: &Path) -> bool {
!is_explicitly_provided(opt, path) || opt.respect_ignores
}

fn path_is_stylua_ignored(path: &Path, search_parent_directories: bool) -> Result<bool> {
let ignore = get_ignore(
path.parent().expect("cannot get parent directory"),
search_parent_directories,
)
.context("failed to parse ignore file")?;

// matched_path_or_any_parents panics when path is not in cwd
// can happen when `--respect-ignores --stdin-filepath {path}`
if !path
.canonicalize()
.unwrap_or_default()
.starts_with(ignore.path().canonicalize().unwrap_or_default())
{
return Ok(false);
}

Ok(matches!(
ignore.matched_path_or_any_parents(path, false),
ignore::Match::Ignore(_)
))
}

fn format(opt: opt::Opt) -> Result<i32> {
debug!("resolved options: {:#?}", opt);

Expand Down Expand Up @@ -436,7 +379,11 @@ fn format(opt: opt::Opt) -> Result<i32> {
let should_skip_format = match &opt.stdin_filepath {
Some(path) => {
opt.respect_ignores
&& path_is_stylua_ignored(path, opt.search_parent_directories)?
&& path_is_stylua_ignored(
path,
opt.search_parent_directories,
None,
)?
}
None => false,
};
Expand Down Expand Up @@ -501,7 +448,7 @@ fn format(opt: opt::Opt) -> Result<i32> {
// we should check .styluaignore
if is_explicitly_provided(opt.as_ref(), &path)
&& should_respect_ignores(opt.as_ref(), &path)
&& path_is_stylua_ignored(&path, opt.search_parent_directories)?
&& path_is_stylua_ignored(&path, opt.search_parent_directories, None)?
{
continue;
}
Expand Down
Loading
Loading