Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: plugin loader #4234

Merged
merged 4 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,39 +172,41 @@ biome_ungrammar = { path = "./crates/biome_ungrammar" }
tests_macros = { path = "./crates/tests_macros" }

# Crates needed in the workspace
anyhow = "1.0.89"
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"
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.128"
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"
anyhow = "1.0.89"
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"
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.128"
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]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible you created the crate manually or with cargo. If so you, you need to update manually knope.toml and add the crate. We have just new-crate that updates the file automatically

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

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
Loading