Skip to content
This repository has been archived by the owner on Dec 29, 2022. It is now read-only.

Commit

Permalink
Merge pull request #990 from Xanewok/external-rustfmt
Browse files Browse the repository at this point in the history
Support using external Rustfmt
  • Loading branch information
nrc authored Aug 28, 2018
2 parents f92b4e8 + fe62a7c commit 6bda4d5
Show file tree
Hide file tree
Showing 7 changed files with 286 additions and 158 deletions.
97 changes: 51 additions & 46 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ languageserver-types = "0.45"
lazy_static = "1"
log = "0.4"
num_cpus = "1"
racer = { version = "2.1.4", default-features = false }
racer = { version = "2.1.5", default-features = false }
rayon = "1"
rls-analysis = "0.16"
rls-blacklist = "0.1.2"
rls-data = { version = "0.18", features = ["serialize-serde"] }
rls-rustc = "0.5.0"
rls-span = { version = "0.4", features = ["serialize-serde"] }
rls-vfs = "0.4.6"
rustfmt-nightly = "0.99.2"
rustfmt-nightly = "0.99.4"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
Expand Down
145 changes: 145 additions & 0 deletions src/actions/format.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.

//! Code formatting using Rustfmt - by default using statically-linked one or
//! possibly running Rustfmt binary specified by the user.
use std::env::temp_dir;
use std::fs::File;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};

use rand::{Rng, thread_rng};
use log::{log, debug};
use rustfmt_nightly::{Config, Session, Input};
use serde_json;

/// Specified which `rustfmt` to use.
#[derive(Clone)]
pub enum Rustfmt {
/// (Path to external `rustfmt`, cwd where it should be spawned at)
External(PathBuf, PathBuf),
/// Statically linked `rustfmt`
Internal
}

impl From<Option<(String, PathBuf)>> for Rustfmt {
fn from(value: Option<(String, PathBuf)>) -> Rustfmt {
match value {
Some((path, cwd)) => Rustfmt::External(PathBuf::from(path), cwd),
None => Rustfmt::Internal
}
}
}

impl Rustfmt {
pub fn format(&self, input: String, cfg: Config) -> Result<String, String> {
match self {
Rustfmt::Internal => format_internal(input, cfg),
Rustfmt::External(path, cwd) => format_external(path, cwd, input, cfg),
}
}
}

fn format_external(path: &PathBuf, cwd: &PathBuf, input: String, cfg: Config) -> Result<String, String> {
let (_file_handle, config_path) = gen_config_file(&cfg)?;
let args = rustfmt_args(&cfg, &config_path);

let mut rustfmt = Command::new(path)
.args(args)
.current_dir(cwd)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.map_err(|_| format!("Couldn't spawn `{}`", path.display()))?;

{
let stdin = rustfmt.stdin.as_mut()
.ok_or_else(|| "Failed to open rustfmt stdin".to_string())?;
stdin.write_all(input.as_bytes())
.map_err(|_| "Failed to pass input to rustfmt".to_string())?;
}

rustfmt.wait_with_output()
.map_err(|err| format!("Error running rustfmt: {}", err))
.and_then(|out| String::from_utf8(out.stdout)
.map_err(|_| "Formatted code is not valid UTF-8".to_string()))
}

fn format_internal(input: String, config:Config) -> Result<String, String> {
let mut buf = Vec::<u8>::new();

{
let mut session = Session::new(config, Some(&mut buf));

match session.format(Input::Text(input)) {
Ok(report) => {
// Session::format returns Ok even if there are any errors, i.e., parsing errors.
if session.has_operational_errors() || session.has_parsing_errors() {
debug!(
"reformat: format_input failed: has errors, report = {}",
report
);

return Err("Reformat failed to complete successfully".into());
}
}
Err(e) => {
debug!("Reformat failed: {:?}", e);

return Err("Reformat failed to complete successfully".into());
}
}
}

String::from_utf8(buf)
.map_err(|_| "Reformat output is not a valid UTF-8".into())
}

fn random_file() -> Result<(File, PathBuf), String> {
const SUFFIX_LEN: usize = 10;

let suffix: String = thread_rng().gen_ascii_chars().take(SUFFIX_LEN).collect();
let path = temp_dir().join(suffix);

Ok(File::create(&path)
.map(|file| (file, path))
.map_err(|_| "Config file could not be created".to_string())?)
}

fn gen_config_file(config: &Config) -> Result<(File, PathBuf), String> {
let (mut file, path) = random_file()?;
let toml = config.all_options().to_toml()?;
file.write(toml.as_bytes())
.map_err(|_| "Could not write config TOML file contents".to_string())?;

Ok((file, path))
}

fn rustfmt_args(config: &Config, config_path: &Path) -> Vec<String> {
let mut args = vec![
"--unstable-features".into(),
"--skip-children".into(),
"--emit".into(),
"stdout".into(),
"--quiet".into(),
];

args.push("--file-lines".into());
let file_lines_json = config.file_lines().to_json_spans();
let lines: String = serde_json::to_string(&file_lines_json).unwrap();
args.push(lines);

args.push("--config-path".into());
args.push(config_path.to_str().map(|x| x.to_string()).unwrap());

args
}
Loading

0 comments on commit 6bda4d5

Please sign in to comment.