Skip to content

Commit

Permalink
feat: plugin loader (#4234)
Browse files Browse the repository at this point in the history
  • Loading branch information
arendjr authored and ematipico committed Oct 23, 2024
1 parent ad0ccef commit 5da32e1
Show file tree
Hide file tree
Showing 27 changed files with 742 additions and 48 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

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

69 changes: 36 additions & 33 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ biome_json_syntax = { version = "0.5.7", path = "./crates/biome_json_
biome_markdown_factory = { version = "0.0.1", path = "./crates/biome_markdown_factory" }
biome_markdown_parser = { version = "0.0.1", path = "./crates/biome_markdown_parser" }
biome_markdown_syntax = { version = "0.0.1", path = "./crates/biome_markdown_syntax" }
biome_plugin_loader = { version = "0.0.1", path = "./crates/biome_plugin_loader" }
biome_yaml_factory = { version = "0.0.1", path = "./crates/biome_yaml_factory" }
biome_yaml_parser = { version = "0.0.1", path = "./crates/biome_yaml_parser" }
biome_yaml_syntax = { version = "0.0.1", path = "./crates/biome_yaml_syntax" }
Expand Down Expand Up @@ -171,40 +172,42 @@ biome_ungrammar = { path = "./crates/biome_ungrammar" }
tests_macros = { path = "./crates/tests_macros" }

# Crates needed in the workspace
anyhow = "1.0.90"
bpaf = { version = "0.9.15", features = ["derive"] }
countme = "3.0.1"
crossbeam = "0.8.4"
dashmap = "6.1.0"
enumflags2 = "0.7.10"
getrandom = "0.2.15"
anyhow = "1.0.90"
bpaf = { version = "0.9.15", features = ["derive"] }
countme = "3.0.1"
crossbeam = "0.8.4"
dashmap = "6.1.0"
enumflags2 = "0.7.10"
getrandom = "0.2.15"
globset = "0.4.15"
ignore = "0.4.23"
indexmap = { version = "2.6.0", features = ["serde"] }
insta = "1.40.0"
natord = "1.0.9"
oxc_resolver = "1.12.0"
proc-macro2 = "1.0.86"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
quote = "1.0.37"
rayon = "1.10.0"
regex = "1.11.0"
rustc-hash = "1.1.0"
schemars = { version = "0.8.21", features = ["indexmap2", "smallvec"] }
serde = { version = "1.0.210", features = ["derive"] }
serde_ini = "0.2.0"
serde_json = "1.0.132"
similar = "2.6.0"
slotmap = "1.0.7"
smallvec = { version = "1.13.2", features = ["union", "const_new", "serde"] }
syn = "1.0.109"
termcolor = "1.4.1"
tokio = "1.40.0"
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
tracing-subscriber = "0.3.18"
unicode-bom = "2.0.3"
unicode-width = "0.1.12"
grit-pattern-matcher = "0.4"
grit-util = "0.4"
ignore = "0.4.23"
indexmap = { version = "2.6.0", features = ["serde"] }
insta = "1.40.0"
natord = "1.0.9"
oxc_resolver = "1.12.0"
proc-macro2 = "1.0.86"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
quote = "1.0.37"
rayon = "1.10.0"
regex = "1.11.0"
rustc-hash = "1.1.0"
schemars = { version = "0.8.21", features = ["indexmap2", "smallvec"] }
serde = { version = "1.0.210", features = ["derive"] }
serde_ini = "0.2.0"
serde_json = "1.0.132"
similar = "2.6.0"
slotmap = "1.0.7"
smallvec = { version = "1.13.2", features = ["union", "const_new", "serde"] }
syn = "1.0.109"
termcolor = "1.4.1"
tokio = "1.40.0"
tracing = { version = "0.1.40", default-features = false, features = ["std"] }
tracing-subscriber = "0.3.18"
unicode-bom = "2.0.3"
unicode-width = "0.1.12"
[profile.dev.package.biome_wasm]
debug = true
opt-level = "s"
Expand Down
8 changes: 8 additions & 0 deletions crates/biome_analyze/src/rule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1033,6 +1033,14 @@ impl RuleDiagnostic {
self
}

/// Marks this diagnostic as verbose.
///
/// The diagnostic will only be shown when using the `--verbose` argument.
pub fn verbose(mut self) -> Self {
self.tags |= DiagnosticTags::VERBOSE;
self
}

/// Attaches a label to this [`RuleDiagnostic`].
///
/// The given span has to be in the file that was provided while creating this [`RuleDiagnostic`].
Expand Down
8 changes: 4 additions & 4 deletions crates/biome_configuration/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,8 @@ pub struct InvalidIgnorePattern {

#[derive(Debug, Serialize, Deserialize, Diagnostic)]
#[diagnostic(
category = "configuration",
severity = Error,
category = "configuration",
severity = Error,
)]
pub struct CantLoadExtendFile {
#[location(resource)]
Expand Down Expand Up @@ -217,8 +217,8 @@ impl CantLoadExtendFile {

#[derive(Debug, Serialize, Deserialize, Diagnostic)]
#[diagnostic(
category = "configuration",
severity = Error,
category = "configuration",
severity = Error,
)]
pub struct InvalidConfiguration {
#[message]
Expand Down
6 changes: 6 additions & 0 deletions crates/biome_configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod javascript;
pub mod json;
pub mod organize_imports;
mod overrides;
pub mod plugins;
pub mod vcs;

use crate::analyzer::assists::{
Expand Down Expand Up @@ -58,6 +59,7 @@ pub use overrides::{
OverrideAssistsConfiguration, OverrideFormatterConfiguration, OverrideLinterConfiguration,
OverrideOrganizeImportsConfiguration, OverridePattern, Overrides,
};
use plugins::Plugins;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::num::NonZeroU64;
Expand Down Expand Up @@ -132,6 +134,10 @@ pub struct Configuration {
#[partial(bpaf(hide))]
pub overrides: Overrides,

/// List of plugins to load.
#[partial(bpaf(hide))]
pub plugins: Plugins,

/// Specific configuration for assists
#[partial(type, bpaf(external(partial_assists_configuration), optional))]
pub assists: AssistsConfiguration,
Expand Down
48 changes: 48 additions & 0 deletions crates/biome_configuration/src/plugins.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
use biome_deserialize::{
Deserializable, DeserializableType, DeserializableValue, DeserializationDiagnostic,
};
use biome_deserialize_macros::{Deserializable, Merge};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::str::FromStr;

#[derive(Clone, Debug, Default, Deserialize, Deserializable, Eq, Merge, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Plugins(pub Vec<PluginConfiguration>);

impl FromStr for Plugins {
type Err = String;

fn from_str(_s: &str) -> Result<Self, Self::Err> {
Ok(Self::default())
}
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "camelCase", deny_unknown_fields, untagged)]
pub enum PluginConfiguration {
Path(String),
// TODO: PathWithOptions(PluginPathWithOptions),
}

impl Deserializable for PluginConfiguration {
fn deserialize(
value: &impl DeserializableValue,
rule_name: &str,
diagnostics: &mut Vec<DeserializationDiagnostic>,
) -> Option<Self> {
if value.visitable_type()? == DeserializableType::Str {
Deserializable::deserialize(value, rule_name, diagnostics).map(Self::Path)
} else {
// TODO: Fix this to allow plugins to receive options.
// Difficulty is that we need a `Deserializable` implementation
// for `serde_json::Value`, since plugin options are untyped.
// Also, we don't have a way to configure Grit plugins yet.
/*Deserializable::deserialize(value, rule_name, diagnostics)
.map(|plugin| Self::PathWithOptions(plugin))*/
None
}
}
}
1 change: 1 addition & 0 deletions crates/biome_diagnostics_categories/src/categories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ define_categories! {
"assists",
"migrate",
"deserialize",
"plugin",
"project",
"search",
"internalError/io",
Expand Down
2 changes: 1 addition & 1 deletion crates/biome_fs/src/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ pub trait FileSystem: Send + Sync + RefUnwindSafe {
/// This method logs an error message and returns a `FileSystemDiagnostic` error in two scenarios:
/// - If the file cannot be opened, possibly due to incorrect path or permission issues.
/// - If the file is opened but its content cannot be read, potentially due to the file being damaged.
fn read_file_from_path(&self, file_path: &PathBuf) -> Result<String, FileSystemDiagnostic> {
fn read_file_from_path(&self, file_path: &Path) -> Result<String, FileSystemDiagnostic> {
match self.open_with_options(file_path, OpenOptions::default().read(true)) {
Ok(mut file) => {
let mut content = String::new();
Expand Down
6 changes: 3 additions & 3 deletions crates/biome_grit_patterns/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
authors.workspace = true
categories.workspace = true
description = "Biome implementing for matching Grit Patterns"
description = "Biome implementation for Grit Patterns"
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
Expand All @@ -21,8 +21,8 @@ biome_js_syntax = { workspace = true }
biome_parser = { workspace = true }
biome_rowan = { workspace = true }
biome_string_case = { workspace = true }
grit-pattern-matcher = { version = "0.4" }
grit-util = { version = "0.4" }
grit-pattern-matcher = { workspace = true }
grit-util = { workspace = true }
path-absolutize = { version = "3.1.1", optional = false, features = ["use_unix_paths_on_wasm"] }
rand = { version = "0.8.5" }
regex = { workspace = true }
Expand Down
5 changes: 4 additions & 1 deletion crates/biome_js_analyze/src/lint/nursery/use_collapsed_if.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use biome_analyze::{context::RuleContext, declare_lint_rule, ActionCategory, Ast, FixKind, QueryMatch, Rule, RuleDiagnostic, RuleSource};
use biome_analyze::{
context::RuleContext, declare_lint_rule, ActionCategory, Ast, FixKind, QueryMatch, Rule,
RuleDiagnostic, RuleSource,
};
use biome_console::markup;
use biome_js_factory::make;
use biome_js_syntax::parentheses::NeedsParentheses;
Expand Down
9 changes: 9 additions & 0 deletions crates/biome_parser/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,15 @@ pub struct AnyParse {
pub(crate) diagnostics: Vec<ParseDiagnostic>,
}

impl From<SendNode> for AnyParse {
fn from(root: SendNode) -> Self {
Self {
root,
diagnostics: Vec::new(),
}
}
}

impl AnyParse {
pub fn new(root: SendNode, diagnostics: Vec<ParseDiagnostic>) -> AnyParse {
AnyParse { root, diagnostics }
Expand Down
32 changes: 32 additions & 0 deletions crates/biome_plugin_loader/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@

[package]
authors.workspace = true
categories.workspace = true
description = "biome_plugin_loader2"
edition.workspace = true
homepage.workspace = true
keywords.workspace = true
license.workspace = true
name = "biome_plugin_loader"
repository.workspace = true
version = "0.0.1"

[dependencies]
biome_analyze = { workspace = true }
biome_console = { workspace = true }
biome_deserialize = { workspace = true }
biome_deserialize_macros = { workspace = true }
biome_diagnostics = { workspace = true }
biome_fs = { workspace = true }
biome_grit_patterns = { workspace = true }
biome_json_parser = { workspace = true }
biome_parser = { workspace = true }
biome_rowan = { workspace = true }
grit-util = { workspace = true }
serde = { workspace = true }

[dev-dependencies]
insta = { workspace = true }

[lints]
workspace = true
81 changes: 81 additions & 0 deletions crates/biome_plugin_loader/src/analyzer_grit_plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
use std::{
fmt::Debug,
path::{Path, PathBuf},
rc::Rc,
};

use biome_analyze::RuleDiagnostic;
use biome_console::markup;
use biome_diagnostics::category;
use biome_fs::FileSystem;
use biome_grit_patterns::{
compile_pattern, GritQuery, GritQueryResult, GritTargetFile, GritTargetLanguage,
JsTargetLanguage,
};
use biome_parser::AnyParse;
use biome_rowan::TextRange;

use crate::{AnalyzerPlugin, PluginDiagnostic};

/// Definition of an analyzer plugin.
#[derive(Clone, Debug)]
pub struct AnalyzerGritPlugin {
grit_query: Rc<GritQuery>,
}

impl AnalyzerGritPlugin {
pub fn load(fs: &dyn FileSystem, path: &Path) -> Result<Self, PluginDiagnostic> {
let source = fs.read_file_from_path(path)?;
let query = compile_pattern(
&source,
Some(path),
// TODO: Target language should be determined dynamically.
GritTargetLanguage::JsTargetLanguage(JsTargetLanguage),
)?;

Ok(Self {
grit_query: Rc::new(query),
})
}
}

impl AnalyzerPlugin for AnalyzerGritPlugin {
fn evaluate(&self, root: AnyParse, path: PathBuf) -> Vec<RuleDiagnostic> {
let name: &str = self.grit_query.name.as_deref().unwrap_or("anonymous");

let file = GritTargetFile { parse: root, path };
match self.grit_query.execute(file) {
Ok((results, logs)) => results
.into_iter()
.filter_map(|result| match result {
GritQueryResult::Match(match_) => Some(match_),
GritQueryResult::Rewrite(_) | GritQueryResult::CreateFile(_) => None,
})
.map(|match_| {
RuleDiagnostic::new(
category!("plugin"),
match_.ranges.into_iter().next().map(from_grit_range),
markup!(<Emphasis>{name}</Emphasis>" matched"),
)
})
.chain(logs.iter().map(|log| {
RuleDiagnostic::new(
category!("plugin"),
log.range.map(from_grit_range),
markup!(<Emphasis>{name}</Emphasis>" logged: "<Info>{log.message}</Info>),
)
.verbose()
}))
.collect(),
Err(error) => vec![RuleDiagnostic::new(
category!("plugin"),
None::<TextRange>,
markup!(<Emphasis>{name}</Emphasis>" errored: "<Error>{error.to_string()}</Error>),
)],
}
}
}

fn from_grit_range(range: grit_util::Range) -> TextRange {
TextRange::new(range.start_byte.into(), range.end_byte.into())
}
Loading

0 comments on commit 5da32e1

Please sign in to comment.