Skip to content

Commit

Permalink
Feat: add option cli and config option for configuring formatter used
Browse files Browse the repository at this point in the history
  • Loading branch information
RDambrosio016 committed Oct 23, 2020
1 parent 93c43e4 commit b2080e0
Show file tree
Hide file tree
Showing 9 changed files with 181 additions and 14 deletions.
5 changes: 5 additions & 0 deletions crates/rslint_cli/CHANGELOG.md
Expand Up @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Added

- Added ways of configuring the formatter used through CLI and config
- Made all fields of config public

## [0.2.1] - 2020-10-21

### Changed
Expand Down
23 changes: 19 additions & 4 deletions crates/rslint_cli/src/config.rs
Expand Up @@ -28,19 +28,34 @@ pub const CONFIG_NAME: &str = "rslintrc.toml";
#[derive(Debug, Deserialize, Serialize)]
pub struct Config {
pub rules: Option<RulesConfig>,
#[serde(default)]
pub errors: ErrorsConfig,
}

#[serde(default)]
#[derive(Debug, Deserialize, Serialize, Default)]
pub struct RulesConfig {
#[serde(deserialize_with = "from_rule_objects")]
errors: Vec<Box<dyn CstRule>>,
pub errors: Vec<Box<dyn CstRule>>,

#[serde(deserialize_with = "from_rule_objects")]
warnings: Vec<Box<dyn CstRule>>,
pub warnings: Vec<Box<dyn CstRule>>,

groups: Vec<String>,
allowed: Vec<String>,
pub groups: Vec<String>,
pub allowed: Vec<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ErrorsConfig {
pub formatter: String,
}

impl Default for ErrorsConfig {
fn default() -> Self {
Self {
formatter: "long".to_string(),
}
}
}

impl Config {
Expand Down
73 changes: 64 additions & 9 deletions crates/rslint_cli/src/lib.rs
Expand Up @@ -5,18 +5,20 @@ mod panic_hook;

pub use self::{cli::ExplanationRunner, config::*, files::*, panic_hook::*};
pub use rslint_core::Outcome;
pub use rslint_errors::{file, Diagnostic, Severity};
pub use rslint_errors::{
file, file::Files, Diagnostic, Emitter, Formatter, LongFormatter, Severity, ShortFormatter,
};

use colored::*;
use rayon::prelude::*;
use rslint_core::autofix::recursively_apply_fixes;
use rslint_core::{lint_file, CstRuleStore, LintResult, RuleLevel};
use rslint_core::{lint_file, util::find_best_match_for_name, CstRuleStore, LintResult, RuleLevel};
use std::fs::write;

pub(crate) const REPO_LINK: &str = "https://github.com/RDambrosio016/RSLint";

#[allow(unused_must_use)]
pub fn run(glob: String, verbose: bool, fix: bool, dirty: bool) {
pub fn run(glob: String, verbose: bool, fix: bool, dirty: bool, formatter: Option<String>) {
let res = glob::glob(&glob);
if let Err(err) = res {
lint_err!("Invalid glob pattern: {}", err);
Expand All @@ -33,6 +35,11 @@ pub fn run(glob: String, verbose: bool, fix: bool, dirty: bool) {
} else {
CstRuleStore::new().builtins()
};
let mut formatter = formatter
.or_else(|| config.as_ref().map(|c| c.errors.formatter.clone()))
.unwrap_or_else(|| String::from("long"));

verify_formatter(&mut formatter);

if walker.files.is_empty() {
lint_err!("No matching files found");
Expand Down Expand Up @@ -67,7 +74,13 @@ pub fn run(glob: String, verbose: bool, fix: bool, dirty: bool) {
} else {
0
};
print_results(&mut results, &walker, config.as_ref(), fix_count);
print_results(
&mut results,
&walker,
config.as_ref(),
fix_count,
&formatter,
);
}

pub fn apply_fixes(results: &mut Vec<LintResult>, walker: &mut FileWalker, dirty: bool) -> usize {
Expand Down Expand Up @@ -120,6 +133,7 @@ pub(crate) fn print_results(
walker: &FileWalker,
config: Option<&config::Config>,
fix_count: usize,
formatter: &str,
) {
// Map each diagnostic to the correct level according to configured rule level
for result in results.iter_mut() {
Expand Down Expand Up @@ -150,9 +164,11 @@ pub(crate) fn print_results(
let overall = Outcome::merge(results.iter().map(|res| res.outcome()));

for result in results.iter_mut() {
for diagnostic in result.diagnostics() {
emit_diagnostic(diagnostic, walker);
}
emit_diagnostics(
formatter,
&result.diagnostics().cloned().collect::<Vec<_>>(),
walker,
);
}

output_overall(failures, warnings, successes, fix_count);
Expand All @@ -161,6 +177,47 @@ pub(crate) fn print_results(
}
}

pub fn verify_formatter(formatter: &mut String) {
if !matches!(formatter.as_str(), "short" | "long") {
if let Some(suggestion) =
find_best_match_for_name(vec!["short", "long"].into_iter(), formatter, None)
{
lint_err!(
"unknown formatter `{}`, using default formatter, did you mean `{}`?",
formatter,
suggestion
);
} else {
lint_err!("unknown formatter `{}`, using default formatter", formatter);
}
*formatter = "long".to_string();
}
}

pub fn emit_diagnostics(formatter: &str, diagnostics: &[Diagnostic], files: &dyn Files) {
match formatter {
"short" => {
if let Err(err) = ShortFormatter.emit_stderr(diagnostics, files) {
lint_err!("failed to emit diagnostic: {}", err);
}
}
"long" => {
if let Err(err) = LongFormatter.emit_stderr(diagnostics, files) {
lint_err!("failed to emit diagnostic: {}", err);
}
}
f => {
if let Some(suggestion) =
find_best_match_for_name(vec!["short", "long"].into_iter(), f, None)
{
lint_err!("unknown formatter `{}`, did you mean `{}`?", f, suggestion);
} else {
lint_err!("unknown formatter `{}`", f);
}
}
}
}

#[allow(unused_must_use)]
fn output_overall(failures: usize, warnings: usize, successes: usize, fix_count: usize) {
println!(
Expand Down Expand Up @@ -196,8 +253,6 @@ pub fn remap_diagnostics_to_level(diagnostics: &mut Vec<Diagnostic>, level: Rule
}

pub fn emit_diagnostic(diagnostic: &Diagnostic, walker: &dyn file::Files) {
use rslint_errors::Emitter;

let mut emitter = Emitter::new(walker);
emitter
.emit_stderr(&diagnostic, true)
Expand Down
5 changes: 4 additions & 1 deletion crates/rslint_cli/src/main.rs
Expand Up @@ -21,6 +21,9 @@ pub(crate) struct Options {
/// Attempt to run autofixes even if the code contains syntax errors (may produce weird fixes or more errors)
#[structopt(short = "D", long)]
dirty: bool,
/// The error formatter to use, either "short" or "long" (default)
#[structopt(short = "F", long)]
formatter: Option<String>,
}

#[derive(Debug, StructOpt)]
Expand All @@ -38,6 +41,6 @@ fn main() {
if let Some(SubCommand::Explain { rules }) = opt.cmd {
ExplanationRunner::new(rules).print();
} else {
rslint_cli::run(opt.files, opt.verbose, opt.fix, opt.dirty);
rslint_cli::run(opt.files, opt.verbose, opt.fix, opt.dirty, opt.formatter);
}
}
27 changes: 27 additions & 0 deletions crates/rslint_errors/src/formatters.rs
@@ -1,5 +1,6 @@
use crate::termcolor::{ColorChoice, StandardStream, WriteColor};
use crate::*;
use codespan::files::Error;
use colored::*;
use file::Files;
use std::collections::HashSet;
Expand Down Expand Up @@ -124,3 +125,29 @@ impl Formatter for ShortFormatter {
Ok(())
}
}

#[derive(Debug, Copy, Clone)]
pub struct LongFormatter;

impl Formatter for LongFormatter {
fn emit_with_writer(
&mut self,
diagnostics: &[Diagnostic],
files: &dyn Files,
writer: &mut dyn WriteColor,
) -> io::Result<()> {
for diag in diagnostics {
match Emitter::new(files).emit_with_writer(diag, writer) {
Ok(_) => {}
Err(err) => {
if let Error::Io(io_err) = err {
return Err(io_err);
} else {
panic!("{}", err)
}
}
}
}
Ok(())
}
}
1 change: 1 addition & 0 deletions docs/SUMMARY.md
Expand Up @@ -4,6 +4,7 @@
- [Configuration](user/config.md)
- [Directives](user/directives.md)
- [Autofix](user/autofix.md)
- [Formatters](user/formatters.md)
- [Dev Docs](dev/README.md)
- [Project Layout](dev/project_layout.md)
- [Rules](dev/rules.md)
Expand Down
Binary file added docs/assets/long_rendering.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/short_rendering.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 61 additions & 0 deletions docs/user/formatters.md
@@ -0,0 +1,61 @@
# Formatters

Formatters are structures which take in raw diagnostics and render them in some way. RSLint provides a few natively implemented
formatters which allow you to configure how errors are shown in the terminal.

RSLint errors natively include a myriad of data inside of them including rule name, named labels, notes, suggestions, etc. However,
rendering full diagnostics generally takes a lot of space, therefore shorter options for rendering are provided.

You can configure which formatter RSLint uses through the `errors` key in the config:

```toml
[errors]
formatter = "short"
```

Alternatively you can also use the `--formatter` and `-F` flags through the CLI:

```
rslint_cli ./foo --formatter short
```

```
rslint_cli ./foo -F short
```

CLI options will override any config options.

The default `long` formatter will be used if you do not specify one or it is invalid.

## Long

This is the default formatter used if you do not configure an alternate one. It is also the most verbose, as it shows all info included in the diagnostics, it is helpful for learning how to fix an issue but may be distracting if there are a lot of errors.

Here is an example of the output with the following code and configuration:

```js
for let i = 5; i < 10; i-- {

}
let i = foo.hasOwnProperty();
```

```toml
[rules]
groups = ["errors"]

[rules.warnings]
no-empty = {}
```

![Long rendering](../assets/long_rendering.png)

## Short

This is a minimal formatter which you may be familiar with if you use ESLint. It only shows the message, code, and location.

![Short rendering](../assets/short_rendering.png)

# Note

Note however that the order of diagnostics is not guaranteed and it usually changes across linting runs, therefore you should not rely on the raw output. This is because files and rules are run in parallel and the order of linting is not guaranteed for now.

0 comments on commit b2080e0

Please sign in to comment.