Skip to content

Commit

Permalink
feat(cli): Support GitHub Workflow Commands (#681)
Browse files Browse the repository at this point in the history
  • Loading branch information
nikeee authored Dec 4, 2023
1 parent 6739a51 commit 5505a1a
Show file tree
Hide file tree
Showing 9 changed files with 242 additions and 32 deletions.
4 changes: 2 additions & 2 deletions crates/biome_cli/src/commands/ci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::configuration::LoadedConfiguration;
use crate::vcs::store_path_to_ignore_from_vcs;
use crate::{
configuration::load_configuration, execute_mode, setup_cli_subscriber, CliDiagnostic,
CliSession, Execution, TraversalMode,
CliSession, Execution,
};
use biome_service::configuration::organize_imports::OrganizeImports;
use biome_service::configuration::{FormatterConfiguration, LinterConfiguration};
Expand Down Expand Up @@ -94,7 +94,7 @@ pub(crate) fn ci(mut session: CliSession, payload: CiCommandPayload) -> Result<(
.update_settings(UpdateSettingsParams { configuration })?;

execute_mode(
Execution::new(TraversalMode::CI),
Execution::new_ci(),
session,
&payload.cli_options,
payload.paths,
Expand Down
40 changes: 34 additions & 6 deletions crates/biome_cli/src/execute/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ impl Execution {
}
}

#[derive(Debug)]
pub(crate) enum ExecutionEnvironment {
GitHub,
}

#[derive(Debug)]
pub(crate) enum TraversalMode {
/// This mode is enabled when running the command `biome check`
Expand Down Expand Up @@ -62,7 +67,10 @@ pub(crate) enum TraversalMode {
stdin: Option<(PathBuf, String)>,
},
/// This mode is enabled when running the command `biome ci`
CI,
CI {
/// Whether the CI is running in a specific environment, e.g. GitHub, GitLab, etc.
environment: Option<ExecutionEnvironment>,
},
/// This mode is enabled when running the command `biome format`
Format {
/// It ignores parse errors
Expand Down Expand Up @@ -113,6 +121,26 @@ impl Execution {
}
}

pub(crate) fn new_ci() -> Self {
// Ref: https://docs.github.com/actions/learn-github-actions/variables#default-environment-variables
let is_github = std::env::var("GITHUB_ACTIONS")
.ok()
.map(|value| value == "true")
.unwrap_or(false);

Self {
report_mode: ReportMode::default(),
traversal_mode: TraversalMode::CI {
environment: if is_github {
Some(ExecutionEnvironment::GitHub)
} else {
None
},
},
max_diagnostics: MAXIMUM_DISPLAYABLE_DIAGNOSTICS,
}
}

/// Creates an instance of [Execution] by passing [traversal mode](TraversalMode) and [report mode](ReportMode)
pub(crate) fn with_report(traversal_mode: TraversalMode, report_mode: ReportMode) -> Self {
Self {
Expand Down Expand Up @@ -140,17 +168,17 @@ impl Execution {
match &self.traversal_mode {
TraversalMode::Check { fix_file_mode, .. }
| TraversalMode::Lint { fix_file_mode, .. } => fix_file_mode.as_ref(),
TraversalMode::Format { .. } | TraversalMode::CI | TraversalMode::Migrate { .. } => {
None
}
TraversalMode::Format { .. }
| TraversalMode::CI { .. }
| TraversalMode::Migrate { .. } => None,
}
}

pub(crate) fn as_diagnostic_category(&self) -> &'static Category {
match self.traversal_mode {
TraversalMode::Check { .. } => category!("check"),
TraversalMode::Lint { .. } => category!("lint"),
TraversalMode::CI => category!("ci"),
TraversalMode::CI { .. } => category!("ci"),
TraversalMode::Format { .. } => category!("format"),
TraversalMode::Migrate { .. } => category!("migrate"),
}
Expand Down Expand Up @@ -205,7 +233,7 @@ impl Execution {
match self.traversal_mode {
TraversalMode::Check { fix_file_mode, .. }
| TraversalMode::Lint { fix_file_mode, .. } => fix_file_mode.is_some(),
TraversalMode::CI => false,
TraversalMode::CI { .. } => false,
TraversalMode::Format { write, .. } => write,
TraversalMode::Migrate { write: dry_run, .. } => dry_run,
}
Expand Down
4 changes: 3 additions & 1 deletion crates/biome_cli/src/execute/process_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,9 @@ pub(crate) fn process_file(ctx: &TraversalOptions, path: &Path) -> FileResult {
TraversalMode::Check { .. } => {
check_file(shared_context, path, &file_features, category!("check"))
}
TraversalMode::CI => check_file(shared_context, path, &file_features, category!("ci")),
TraversalMode::CI { .. } => {
check_file(shared_context, path, &file_features, category!("ci"))
}
TraversalMode::Migrate { .. } => {
unreachable!("The migration should not be called for this file")
}
Expand Down
12 changes: 12 additions & 0 deletions crates/biome_cli/src/execute/traverse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::process_file::{process_file, DiffKind, FileStatus, Message};
use super::ExecutionEnvironment;
use crate::cli_options::CliOptions;
use crate::execute::diagnostics::{
CIFormatDiffDiagnostic, CIOrganizeImportsDiffDiagnostic, ContentDiffAdvice,
Expand All @@ -9,6 +10,7 @@ use crate::{
Report, ReportDiagnostic, ReportDiff, ReportErrorKind, ReportKind, TraversalMode,
};
use biome_console::{fmt, markup, Console, ConsoleExt};
use biome_diagnostics::PrintGitHubDiagnostic;
use biome_diagnostics::{
adapters::StdError, category, DiagnosticExt, Error, PrintDescription, PrintDiagnostic,
Resource, Severity,
Expand Down Expand Up @@ -570,13 +572,23 @@ fn process_messages(options: ProcessMessagesOptions) {
}
}
}
let running_on_github = matches!(
mode.traversal_mode(),
TraversalMode::CI {
environment: Some(ExecutionEnvironment::GitHub),
}
);

for diagnostic in diagnostics_to_print {
if diagnostic.severity() >= *diagnostic_level {
console.error(markup! {
{if verbose { PrintDiagnostic::verbose(&diagnostic) } else { PrintDiagnostic::simple(&diagnostic) }}
});
}

if running_on_github {
console.log(markup! {{PrintGitHubDiagnostic::simple(&diagnostic)}});
}
}

if mode.is_check() && total_skipped_suggested_fixes > 0 {
Expand Down
69 changes: 53 additions & 16 deletions crates/biome_cli/tests/snap_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,9 @@ impl CliSnapshot {
let mut content = String::new();

if let Some(configuration) = &self.configuration {
let parsed = parse_json(
&redact_snapshot(configuration),
JsonParserOptions::default(),
);
let redacted = redact_snapshot(configuration).unwrap_or(String::new().into());

let parsed = parse_json(&redacted, JsonParserOptions::default());
let formatted = format_node(
JsonFormatOptions::default()
.with_indent_style(IndentStyle::Space)
Expand All @@ -75,10 +74,14 @@ impl CliSnapshot {
if !name.starts_with("biome.json") {
let extension = name.split('.').last().unwrap();

let _ = write!(content, "## `{}`\n\n", redact_snapshot(name));
let redacted_name = redact_snapshot(name).unwrap_or(String::new().into());
let redacted_content =
redact_snapshot(file_content).unwrap_or(String::new().into());

let _ = write!(content, "## `{}`\n\n", redacted_name);
let _ = write!(content, "```{extension}");
content.push('\n');
content.push_str(&redact_snapshot(file_content));
content.push_str(&redacted_content);
content.push('\n');
content.push_str("```");
content.push_str("\n\n")
Expand All @@ -97,22 +100,29 @@ impl CliSnapshot {

if let Some(termination) = &self.termination {
let message = print_diagnostic_to_string(termination);
content.push_str("# Termination Message\n\n");
content.push_str("```block");
content.push('\n');
content.push_str(&redact_snapshot(&message));
content.push('\n');
content.push_str("```");
content.push_str("\n\n");

if let Some(redacted) = &redact_snapshot(&message) {
content.push_str("# Termination Message\n\n");
content.push_str("```block");
content.push('\n');
content.push_str(redacted);
content.push('\n');
content.push_str("```");
content.push_str("\n\n");
}
}

if !self.messages.is_empty() {
content.push_str("# Emitted Messages\n\n");

for message in &self.messages {
let Some(redacted) = &redact_snapshot(message) else {
continue;
};

content.push_str("```block");
content.push('\n');
content.push_str(&redact_snapshot(message));
content.push_str(redacted);
content.push('\n');
content.push_str("```");
content.push_str("\n\n")
Expand All @@ -123,7 +133,7 @@ impl CliSnapshot {
}
}

fn redact_snapshot(input: &str) -> Cow<'_, str> {
fn redact_snapshot(input: &str) -> Option<Cow<'_, str>> {
let mut output = Cow::Borrowed(input);

// There are some logs that print the timing, and we can't snapshot that message
Expand All @@ -148,6 +158,33 @@ fn redact_snapshot(input: &str) -> Cow<'_, str> {

output = replace_temp_dir(output);

// Ref: https://docs.github.com/actions/learn-github-actions/variables#default-environment-variables
let is_github = std::env::var("GITHUB_ACTIONS")
.ok()
.map(|value| value == "true")
.unwrap_or(false);

if is_github {
// GitHub actions sets the env var GITHUB_ACTIONS=true in CI
// Based on that, biome also has a feature that detects if it's running on GH Actions and
// emits additional information in the output (workflow commands, see PR + discussion at #681)
// To avoid polluting snapshots with that, we filter out those lines

let lines: Vec<_> = output
.split('\n')
.filter(|line| {
!line.starts_with("::notice ")
&& !line.starts_with("::warning ")
&& !line.starts_with("::error ")
})
.collect();

output = Cow::Owned(lines.join("\n"));
if output.is_empty() {
return None;
}
}

// Normalize Windows-specific path separators to "/"
if cfg!(windows) {
let mut rest = &*output;
Expand Down Expand Up @@ -185,7 +222,7 @@ fn redact_snapshot(input: &str) -> Cow<'_, str> {
}
}

output
Some(output)
}

/// Replace the path to the temporary directory with "<TEMP_DIR>"
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_diagnostics/src/display.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use unicode_width::UnicodeWidthStr;

mod backtrace;
mod diff;
mod frame;
pub(super) mod frame;
mod message;

use crate::display::frame::SourceFile;
Expand Down
12 changes: 6 additions & 6 deletions crates/biome_diagnostics/src/display/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,17 +407,17 @@ fn show_invisible_char(char: char) -> Option<&'static str> {

/// A user-facing location in a source file.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(super) struct SourceLocation {
pub struct SourceLocation {
/// The user-facing line number.
pub(super) line_number: OneIndexed,
pub line_number: OneIndexed,
/// The user-facing column number.
pub(super) column_number: OneIndexed,
pub column_number: OneIndexed,
}

/// Representation of a single source file holding additional information for
/// efficiently rendering code frames
#[derive(Clone)]
pub(super) struct SourceFile<'diagnostic> {
pub struct SourceFile<'diagnostic> {
/// The source code of the file.
source: &'diagnostic str,
/// The starting byte indices in the source code.
Expand All @@ -426,7 +426,7 @@ pub(super) struct SourceFile<'diagnostic> {

impl<'diagnostic> SourceFile<'diagnostic> {
/// Create a new [SourceFile] from a slice of text
pub(super) fn new(source_code: BorrowedSourceCode<'diagnostic>) -> Self {
pub fn new(source_code: BorrowedSourceCode<'diagnostic>) -> Self {
// Either re-use the existing line index provided by the diagnostic or create one
Self {
source: source_code.text,
Expand Down Expand Up @@ -484,7 +484,7 @@ impl<'diagnostic> SourceFile<'diagnostic> {
}

/// Get a source location from a byte index into the text of this file
pub(super) fn location(&self, byte_index: TextSize) -> io::Result<SourceLocation> {
pub fn location(&self, byte_index: TextSize) -> io::Result<SourceLocation> {
let line_index = self.line_index(byte_index);

Ok(SourceLocation {
Expand Down
Loading

0 comments on commit 5505a1a

Please sign in to comment.