Skip to content

Commit

Permalink
Merge branch 'main' into faulty/assignment-breaks
Browse files Browse the repository at this point in the history
  • Loading branch information
faultyserver committed Dec 4, 2023
2 parents 692d439 + d6a0923 commit d47bcc2
Show file tree
Hide file tree
Showing 93 changed files with 9,078 additions and 566 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ Read our [guidelines for writing a good changelog entry](https://github.com/biom

#### New features

- Add [useForOf](https://biomejs.dev/linter/rules/use-for-of) rule.
The rule recommends a for-of loop when the loop index is only used to read from an array that is being iterated.
Contributed by @victor-teles

#### Enhancement

- Implements [#924](https://github.com/biomejs/biome/issues/924) and [#920](https://github.com/biomejs/biome/issues/920). [noUselessElse](https://biomejs.dev/linter/rules/no-useless-else) now ignores `else` clauses that follow at least one `if` statement that doesn't break early. Contributed by @Conaclos
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 54 additions & 0 deletions crates/biome_analyze/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -681,3 +681,57 @@ impl Rule for ExampleRule {
}
}
```

### Semantic Model

The semantic model provides information about the references of a binding (variable) within a program, indicating if it is written (e.g., `const a = 4`), read (e.g., `const b = a`, where `a` is read), or exported.


#### How to use the query `Semantic<>` in a lint rule

We have a for loop that creates an index i, and we need to identify where this index is used inside the body of the loop

```js
for (let i = 0; i < array.length; i++) {
array[i] = i
}
```

To get started we need to create a new rule using the semantic type `type Query = Semantic<JsForStatement>;`
We can now use the `ctx.model()` to get information about bindings in the for loop.

```rust,ignore
impl Rule for ForLoopCountReferences {
type Query = Semantic<JsForStatement>;
type State = ();
type Signals = Option<Self::State>;
type Options = ();
fn run(ctx: &RuleContext<Self>) -> Self::Signals {
let node = ctx.query();
// The model holds all informations about the semantic, like scopes and declarations
let model = ctx.model();
// Here we are extracting the `let i = 0;` declaration in for loop
let initializer = node.initializer()?;
let declarators = initializer.as_js_variable_declaration()?.declarators();
let initializer = declarators.first()?.ok()?;
let initializer_id = initializer.id().ok()?;
// Now we have the binding of this declaration
let binding = initializer_id
.as_any_js_binding()?
.as_js_identifier_binding()?;
// How many times this variable appers in the code
let count = binding.all_references(model).count();
// Get all read references
let readonly_references = binding.all_reads(model);
// Get all write references
let write_references = binding.all_writes(model);
}
}
```
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 d47bcc2

Please sign in to comment.