mirror of
https://github.com/biomejs/biome.git
synced 2025-12-23 08:21:13 +00:00
feat(core): implement workspace plugin loading (#5160)
This commit is contained in:
parent
5741b80349
commit
a7ebbf0db0
33 changed files with 653 additions and 331 deletions
1
.github/workflows/benchmark.yml
vendored
1
.github/workflows/benchmark.yml
vendored
|
|
@ -50,6 +50,7 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Compile
|
||||
timeout-minutes: 15
|
||||
run: cargo codspeed build --features codspeed -p xtask_bench
|
||||
|
||||
- name: Run the benchmarks
|
||||
|
|
|
|||
3
Cargo.lock
generated
3
Cargo.lock
generated
|
|
@ -1218,6 +1218,8 @@ dependencies = [
|
|||
"grit-pattern-matcher",
|
||||
"grit-util",
|
||||
"insta",
|
||||
"papaya",
|
||||
"rustc-hash 2.1.1",
|
||||
"serde",
|
||||
]
|
||||
|
||||
|
|
@ -1290,6 +1292,7 @@ dependencies = [
|
|||
"biome_json_syntax",
|
||||
"biome_package",
|
||||
"biome_parser",
|
||||
"biome_plugin_loader",
|
||||
"biome_project_layout",
|
||||
"biome_rowan",
|
||||
"biome_string_case",
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
use crate::RuleDiagnostic;
|
||||
use biome_parser::AnyParse;
|
||||
use camino::Utf8PathBuf;
|
||||
use std::fmt::Debug;
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
/// Slice of analyzer plugins that can be cheaply cloned.
|
||||
pub type AnalyzerPluginSlice<'a> = &'a [Arc<Box<dyn AnalyzerPlugin>>];
|
||||
|
||||
/// Vector of analyzer plugins that can be cheaply cloned.
|
||||
pub type AnalyzerPluginVec = Vec<Arc<Box<dyn AnalyzerPlugin>>>;
|
||||
|
||||
/// Definition of an analyzer plugin.
|
||||
pub trait AnalyzerPlugin: Debug {
|
||||
pub trait AnalyzerPlugin: Debug + Send + Sync {
|
||||
fn evaluate(&self, root: AnyParse, path: Utf8PathBuf) -> Vec<RuleDiagnostic>;
|
||||
|
||||
fn supports_css(&self) -> bool;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use biome_parser::AnyParse;
|
|||
use std::collections::{BTreeMap, BinaryHeap};
|
||||
use std::fmt::{Debug, Display, Formatter};
|
||||
use std::ops;
|
||||
use std::sync::Arc;
|
||||
|
||||
mod analyzer_plugin;
|
||||
mod categories;
|
||||
|
|
@ -25,7 +26,7 @@ mod visitor;
|
|||
// Re-exported for use in the `declare_group` macro
|
||||
pub use biome_diagnostics::category_concat;
|
||||
|
||||
pub use crate::analyzer_plugin::AnalyzerPlugin;
|
||||
pub use crate::analyzer_plugin::{AnalyzerPlugin, AnalyzerPluginSlice, AnalyzerPluginVec};
|
||||
pub use crate::categories::{
|
||||
ActionCategory, OtherActionCategory, RefactorKind, RuleCategories, RuleCategoriesBuilder,
|
||||
RuleCategory, SourceActionKind, SUPPRESSION_INLINE_ACTION_CATEGORY,
|
||||
|
|
@ -72,7 +73,7 @@ pub struct Analyzer<'analyzer, L: Language, Matcher, Break, Diag> {
|
|||
/// List of visitors being run by this instance of the analyzer for each phase
|
||||
phases: BTreeMap<Phases, Vec<Box<dyn Visitor<Language = L> + 'analyzer>>>,
|
||||
/// Plugins to be run after the phases for built-in rules.
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginVec,
|
||||
/// Holds the metadata for all the rules statically known to the analyzer
|
||||
metadata: &'analyzer MetadataRegistry,
|
||||
/// Executor for the query matches emitted by the visitors
|
||||
|
|
@ -128,7 +129,7 @@ where
|
|||
}
|
||||
|
||||
/// Registers an [AnalyzerPlugin] to be executed after the regular phases.
|
||||
pub fn add_plugin(&mut self, plugin: Box<dyn AnalyzerPlugin>) {
|
||||
pub fn add_plugin(&mut self, plugin: Arc<Box<dyn AnalyzerPlugin>>) {
|
||||
self.plugins.push(plugin);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -801,13 +801,16 @@ pub(crate) trait CommandRunner: Sized {
|
|||
open_uninitialized: true,
|
||||
})?;
|
||||
|
||||
workspace.update_settings(UpdateSettingsParams {
|
||||
let result = workspace.update_settings(UpdateSettingsParams {
|
||||
project_key,
|
||||
workspace_directory: configuration_path.map(BiomePath::from),
|
||||
configuration,
|
||||
vcs_base_path: vcs_base_path.map(BiomePath::from),
|
||||
gitignore_matches,
|
||||
})?;
|
||||
for diagnostic in &result.diagnostics {
|
||||
console.log(markup! {{PrintDiagnostic::simple(diagnostic)}});
|
||||
}
|
||||
|
||||
let execution = self.get_execution(cli_options, console, workspace, project_key)?;
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,12 @@ use std::str::FromStr;
|
|||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
pub struct Plugins(pub Vec<PluginConfiguration>);
|
||||
|
||||
impl Plugins {
|
||||
pub fn iter(&self) -> impl Iterator<Item = &PluginConfiguration> {
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Plugins {
|
||||
type Err = String;
|
||||
|
||||
|
|
@ -36,8 +42,9 @@ impl Deserializable for PluginConfiguration {
|
|||
Deserializable::deserialize(ctx, value, rule_name).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.
|
||||
// We probably need to pass them as `AnyJsonValue` or
|
||||
// `biome_json_value::JsonValue`, 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))*/
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ mod utils;
|
|||
pub use crate::registry::visit_registry;
|
||||
use crate::suppression_action::CssSuppressionAction;
|
||||
use biome_analyze::{
|
||||
to_analyzer_suppressions, AnalysisFilter, AnalyzerOptions, AnalyzerPlugin, AnalyzerSignal,
|
||||
to_analyzer_suppressions, AnalysisFilter, AnalyzerOptions, AnalyzerPluginSlice, AnalyzerSignal,
|
||||
AnalyzerSuppression, ControlFlow, LanguageRoot, MatchQueryParams, MetadataRegistry, RuleAction,
|
||||
RuleRegistry,
|
||||
};
|
||||
|
|
@ -36,7 +36,7 @@ pub fn analyze<'a, F, B>(
|
|||
root: &LanguageRoot<CssLanguage>,
|
||||
filter: AnalysisFilter,
|
||||
options: &'a AnalyzerOptions,
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginSlice<'a>,
|
||||
emit_signal: F,
|
||||
) -> (Option<B>, Vec<Error>)
|
||||
where
|
||||
|
|
@ -57,7 +57,7 @@ pub fn analyze_with_inspect_matcher<'a, V, F, B>(
|
|||
filter: AnalysisFilter,
|
||||
inspect_matcher: V,
|
||||
options: &'a AnalyzerOptions,
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginSlice<'a>,
|
||||
mut emit_signal: F,
|
||||
) -> (Option<B>, Vec<Error>)
|
||||
where
|
||||
|
|
@ -111,7 +111,7 @@ where
|
|||
|
||||
for plugin in plugins {
|
||||
if plugin.supports_css() {
|
||||
analyzer.add_plugin(plugin);
|
||||
analyzer.add_plugin(plugin.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ mod tests {
|
|||
..AnalysisFilter::default()
|
||||
},
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
error_ranges.push(diag.location().span.unwrap());
|
||||
|
|
@ -234,7 +234,7 @@ mod tests {
|
|||
};
|
||||
|
||||
let options = AnalyzerOptions::default();
|
||||
analyze(&parsed.tree(), filter, &options, Vec::new(), |signal| {
|
||||
analyze(&parsed.tree(), filter, &options, &[], |signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
let error = diag
|
||||
.with_file_path("dummyFile")
|
||||
|
|
@ -274,7 +274,7 @@ a {
|
|||
};
|
||||
|
||||
let options = AnalyzerOptions::default();
|
||||
analyze(&parsed.tree(), filter, &options, Vec::new(), |signal| {
|
||||
analyze(&parsed.tree(), filter, &options, &[], |signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
let error = diag
|
||||
.with_file_path("dummyFile")
|
||||
|
|
@ -310,7 +310,7 @@ a {
|
|||
};
|
||||
|
||||
let options = AnalyzerOptions::default();
|
||||
analyze(&parsed.tree(), filter, &options, Vec::new(), |signal| {
|
||||
analyze(&parsed.tree(), filter, &options, &[], |signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
let error = diag
|
||||
.with_file_path("dummyFile")
|
||||
|
|
@ -343,7 +343,7 @@ a {
|
|||
};
|
||||
|
||||
let options = AnalyzerOptions::default();
|
||||
analyze(&parsed.tree(), filter, &options, Vec::new(), |signal| {
|
||||
analyze(&parsed.tree(), filter, &options, &[], |signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
let code = diag.category().unwrap();
|
||||
if code != category!("suppressions/unused") {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use biome_analyze::{
|
||||
AnalysisFilter, AnalyzerAction, AnalyzerPlugin, ControlFlow, Never, RuleFilter,
|
||||
AnalysisFilter, AnalyzerAction, AnalyzerPluginSlice, ControlFlow, Never, RuleFilter,
|
||||
};
|
||||
use biome_css_parser::{parse_css, CssParserOptions};
|
||||
use biome_css_syntax::{CssFileSource, CssLanguage};
|
||||
|
|
@ -14,6 +14,7 @@ use biome_test_utils::{
|
|||
};
|
||||
use camino::Utf8Path;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::{fs::read_to_string, slice};
|
||||
|
||||
tests_macros::gen_tests! {"tests/specs/**/*.{css,json,jsonc}", crate::run_test, "module"}
|
||||
|
|
@ -72,7 +73,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
|
|||
input_file,
|
||||
CheckActionType::Lint,
|
||||
parser_options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +91,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
|
|||
input_file,
|
||||
CheckActionType::Lint,
|
||||
parser_options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -116,7 +117,7 @@ pub(crate) fn analyze_and_snap(
|
|||
input_file: &Utf8Path,
|
||||
check_action_type: CheckActionType,
|
||||
parser_options: CssParserOptions,
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginSlice,
|
||||
) -> usize {
|
||||
let parsed = parse_css(input_code, parser_options);
|
||||
let root = parsed.tree();
|
||||
|
|
@ -241,7 +242,7 @@ pub(crate) fn run_suppression_test(input: &'static str, _: &str, _: &str, _: &st
|
|||
input_file,
|
||||
CheckActionType::Suppression,
|
||||
CssParserOptions::default(),
|
||||
Vec::new(),
|
||||
&[],
|
||||
);
|
||||
|
||||
insta::with_settings!({
|
||||
|
|
@ -288,7 +289,7 @@ fn run_plugin_test(input: &'static str, _: &str, _: &str, _: &str) {
|
|||
&input_path,
|
||||
CheckActionType::Lint,
|
||||
CssParserOptions::default(),
|
||||
vec![Box::new(plugin)],
|
||||
&[Arc::new(Box::new(plugin))],
|
||||
);
|
||||
|
||||
insta::with_settings!({
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
use crate::suppression_action::JsSuppressionAction;
|
||||
use biome_analyze::{
|
||||
to_analyzer_suppressions, AnalysisFilter, Analyzer, AnalyzerContext, AnalyzerOptions,
|
||||
AnalyzerPlugin, AnalyzerSignal, AnalyzerSuppression, ControlFlow, InspectMatcher, LanguageRoot,
|
||||
MatchQueryParams, MetadataRegistry, RuleAction, RuleRegistry,
|
||||
AnalyzerPluginSlice, AnalyzerSignal, AnalyzerSuppression, ControlFlow, InspectMatcher,
|
||||
LanguageRoot, MatchQueryParams, MetadataRegistry, RuleAction, RuleRegistry,
|
||||
};
|
||||
use biome_aria::AriaRoles;
|
||||
use biome_dependency_graph::DependencyGraph;
|
||||
|
|
@ -74,7 +74,7 @@ pub fn analyze_with_inspect_matcher<'a, V, F, B>(
|
|||
filter: AnalysisFilter,
|
||||
inspect_matcher: V,
|
||||
options: &'a AnalyzerOptions,
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginSlice<'a>,
|
||||
services: JsAnalyzerServices,
|
||||
mut emit_signal: F,
|
||||
) -> (Option<B>, Vec<DiagnosticError>)
|
||||
|
|
@ -135,7 +135,7 @@ where
|
|||
|
||||
for plugin in plugins {
|
||||
if plugin.supports_js() {
|
||||
analyzer.add_plugin(plugin);
|
||||
analyzer.add_plugin(plugin.clone());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -167,7 +167,7 @@ pub fn analyze<'a, F, B>(
|
|||
root: &LanguageRoot<JsLanguage>,
|
||||
filter: AnalysisFilter,
|
||||
options: &'a AnalyzerOptions,
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginSlice<'a>,
|
||||
services: JsAnalyzerServices,
|
||||
emit_signal: F,
|
||||
) -> (Option<B>, Vec<DiagnosticError>)
|
||||
|
|
@ -234,7 +234,7 @@ let bar = 33;
|
|||
..AnalysisFilter::default()
|
||||
},
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
services,
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -282,7 +282,7 @@ let bar = 33;
|
|||
&parsed.tree(),
|
||||
AnalysisFilter::default(),
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -367,7 +367,7 @@ let bar = 33;
|
|||
&parsed.tree(),
|
||||
AnalysisFilter::default(),
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -438,7 +438,7 @@ let bar = 33;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -482,7 +482,7 @@ let bar = 33;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -533,7 +533,7 @@ debugger;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -579,7 +579,7 @@ debugger;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -627,7 +627,7 @@ debugger;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -676,7 +676,7 @@ let bar = 33;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -723,7 +723,7 @@ let bar = 33;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -773,7 +773,7 @@ let c;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -824,7 +824,7 @@ debugger;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -876,7 +876,7 @@ let d;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
@ -919,7 +919,7 @@ const foo0 = function (bar: string) {
|
|||
let services =
|
||||
JsAnalyzerServices::from((Default::default(), Default::default(), JsFileSource::ts()));
|
||||
|
||||
analyze(&root, filter, &options, Vec::new(), services, |signal| {
|
||||
analyze(&root, filter, &options, &[], services, |signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
let error = diag
|
||||
.with_file_path("dummyFile")
|
||||
|
|
@ -963,7 +963,7 @@ a == b;
|
|||
&parsed.tree(),
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|signal| {
|
||||
if let Some(diag) = signal.diagnostic() {
|
||||
|
|
|
|||
|
|
@ -78,24 +78,23 @@ fn analyze(
|
|||
|
||||
let services = JsAnalyzerServices::from((dependency_graph, project_layout, source_type));
|
||||
|
||||
let (_, errors) =
|
||||
biome_js_analyze::analyze(&root, filter, &options, Vec::new(), services, |event| {
|
||||
if let Some(mut diag) = event.diagnostic() {
|
||||
for action in event.actions() {
|
||||
diag = diag.add_code_suggestion(CodeSuggestionAdvice::from(action));
|
||||
}
|
||||
|
||||
let error = diag.with_severity(Severity::Warning);
|
||||
diagnostics.push(diagnostic_to_string(file_name, input_code, error));
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
let (_, errors) = biome_js_analyze::analyze(&root, filter, &options, &[], services, |event| {
|
||||
if let Some(mut diag) = event.diagnostic() {
|
||||
for action in event.actions() {
|
||||
code_fixes.push(code_fix_to_string(input_code, action));
|
||||
diag = diag.add_code_suggestion(CodeSuggestionAdvice::from(action));
|
||||
}
|
||||
|
||||
ControlFlow::<Never>::Continue(())
|
||||
});
|
||||
let error = diag.with_severity(Severity::Warning);
|
||||
diagnostics.push(diagnostic_to_string(file_name, input_code, error));
|
||||
return ControlFlow::Continue(());
|
||||
}
|
||||
|
||||
for action in event.actions() {
|
||||
code_fixes.push(code_fix_to_string(input_code, action));
|
||||
}
|
||||
|
||||
ControlFlow::<Never>::Continue(())
|
||||
});
|
||||
|
||||
for error in errors {
|
||||
diagnostics.push(diagnostic_to_string(file_name, input_code, error));
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
use biome_analyze::{
|
||||
AnalysisFilter, AnalyzerAction, AnalyzerPlugin, ControlFlow, Never, RuleFilter,
|
||||
AnalysisFilter, AnalyzerAction, AnalyzerPluginSlice, ControlFlow, Never, RuleFilter,
|
||||
};
|
||||
use biome_diagnostics::advice::CodeSuggestionAdvice;
|
||||
use biome_fs::OsFileSystem;
|
||||
|
|
@ -17,6 +17,7 @@ use biome_test_utils::{
|
|||
};
|
||||
use camino::{Utf8Component, Utf8Path};
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
use std::{fs::read_to_string, slice};
|
||||
|
||||
tests_macros::gen_tests! {"tests/specs/**/*.{cjs,cts,js,jsx,tsx,ts,json,jsonc,svelte}", crate::run_test, "module"}
|
||||
|
|
@ -66,7 +67,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
|
|||
input_file,
|
||||
CheckActionType::Lint,
|
||||
JsParserOptions::default(),
|
||||
Vec::new(),
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -84,7 +85,7 @@ fn run_test(input: &'static str, _: &str, _: &str, _: &str) {
|
|||
input_file,
|
||||
CheckActionType::Lint,
|
||||
JsParserOptions::default(),
|
||||
Vec::new(),
|
||||
&[],
|
||||
)
|
||||
};
|
||||
|
||||
|
|
@ -110,7 +111,7 @@ pub(crate) fn analyze_and_snap(
|
|||
input_file: &Utf8Path,
|
||||
check_action_type: CheckActionType,
|
||||
parser_options: JsParserOptions,
|
||||
plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
plugins: AnalyzerPluginSlice,
|
||||
) -> usize {
|
||||
let mut diagnostics = Vec::new();
|
||||
let mut code_fixes = Vec::new();
|
||||
|
|
@ -304,7 +305,7 @@ pub(crate) fn run_suppression_test(input: &'static str, _: &str, _: &str, _: &st
|
|||
input_file,
|
||||
CheckActionType::Suppression,
|
||||
JsParserOptions::default(),
|
||||
Vec::new(),
|
||||
&[],
|
||||
);
|
||||
|
||||
insta::with_settings!({
|
||||
|
|
@ -351,7 +352,7 @@ fn run_plugin_test(input: &'static str, _: &str, _: &str, _: &str) {
|
|||
&input_path,
|
||||
CheckActionType::Lint,
|
||||
JsParserOptions::default(),
|
||||
vec![Box::new(plugin)],
|
||||
&[Arc::new(Box::new(plugin))],
|
||||
);
|
||||
|
||||
insta::with_settings!({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
authors.workspace = true
|
||||
categories.workspace = true
|
||||
description = "biome_plugin_loader2"
|
||||
description = "Functionality for loading plugins and caching them in memory"
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
keywords.workspace = true
|
||||
|
|
@ -24,6 +24,8 @@ biome_rowan = { workspace = true }
|
|||
camino = { workspace = true }
|
||||
grit-pattern-matcher = { workspace = true }
|
||||
grit-util = { workspace = true }
|
||||
papaya = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
|
|
|
|||
|
|
@ -12,14 +12,14 @@ use biome_rowan::TextRange;
|
|||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use grit_pattern_matcher::{binding::Binding, pattern::ResolvedPattern};
|
||||
use grit_util::{error::GritPatternError, AnalysisLogs};
|
||||
use std::{borrow::Cow, fmt::Debug, rc::Rc};
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
|
||||
use crate::{AnalyzerPlugin, PluginDiagnostic};
|
||||
|
||||
/// Definition of an analyzer plugin.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
pub struct AnalyzerGritPlugin {
|
||||
grit_query: Rc<GritQuery>,
|
||||
grit_query: GritQuery,
|
||||
}
|
||||
|
||||
impl AnalyzerGritPlugin {
|
||||
|
|
@ -39,11 +39,9 @@ impl AnalyzerGritPlugin {
|
|||
)
|
||||
.as_predicate()])
|
||||
.with_path(path);
|
||||
let query = compile_pattern_with_options(&source, options)?;
|
||||
let grit_query = compile_pattern_with_options(&source, options)?;
|
||||
|
||||
Ok(Self {
|
||||
grit_query: Rc::new(query),
|
||||
})
|
||||
Ok(Self { grit_query })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ impl std::fmt::Display for PluginDiagnostic {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<PluginDiagnostic> for biome_diagnostics::serde::Diagnostic {
|
||||
fn from(error: PluginDiagnostic) -> Self {
|
||||
biome_diagnostics::serde::Diagnostic::new(error)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Diagnostic)]
|
||||
#[diagnostic(
|
||||
category = "plugin",
|
||||
|
|
|
|||
|
|
@ -1,61 +1,47 @@
|
|||
mod analyzer_grit_plugin;
|
||||
mod diagnostics;
|
||||
mod plugin_cache;
|
||||
mod plugin_manifest;
|
||||
|
||||
pub use analyzer_grit_plugin::AnalyzerGritPlugin;
|
||||
use biome_analyze::AnalyzerPlugin;
|
||||
pub use diagnostics::PluginDiagnostic;
|
||||
pub use plugin_cache::*;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use biome_analyze::{AnalyzerPlugin, AnalyzerPluginVec};
|
||||
use biome_console::markup;
|
||||
use biome_deserialize::json::deserialize_from_json_str;
|
||||
use biome_diagnostics::ResolveError;
|
||||
use biome_fs::FileSystem;
|
||||
use biome_json_parser::JsonParserOptions;
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use diagnostics::PluginDiagnostic;
|
||||
use camino::{Utf8Component, Utf8Path, Utf8PathBuf};
|
||||
use plugin_manifest::PluginManifest;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BiomePlugin {
|
||||
pub analyzer_plugins: Vec<Box<dyn AnalyzerPlugin>>,
|
||||
pub analyzer_plugins: AnalyzerPluginVec,
|
||||
}
|
||||
|
||||
impl BiomePlugin {
|
||||
/// Loads a plugin from the given `plugin_path`.
|
||||
///
|
||||
/// Base paths are used to resolve relative paths and package specifiers.
|
||||
/// The base path is used to resolve relative paths.
|
||||
pub fn load(
|
||||
fs: &dyn FileSystem,
|
||||
plugin_path: &str,
|
||||
relative_resolution_base_path: &Utf8Path,
|
||||
external_resolution_base_path: &Utf8Path,
|
||||
base_path: &Utf8Path,
|
||||
) -> Result<Self, PluginDiagnostic> {
|
||||
let plugin_path = if let Some(plugin_path) = plugin_path.strip_prefix("./") {
|
||||
relative_resolution_base_path.join(plugin_path)
|
||||
} else if plugin_path.starts_with('.') {
|
||||
relative_resolution_base_path.join(plugin_path)
|
||||
} else {
|
||||
Utf8PathBuf::from_path_buf(
|
||||
fs.resolve_configuration(plugin_path, external_resolution_base_path)
|
||||
.map_err(|error| {
|
||||
PluginDiagnostic::cant_resolve(
|
||||
external_resolution_base_path.to_string(),
|
||||
Some(ResolveError::from(error)),
|
||||
)
|
||||
})?
|
||||
.into_path_buf(),
|
||||
)
|
||||
.expect("Valid UTF-8 path")
|
||||
};
|
||||
let plugin_path = normalize_path(&base_path.join(plugin_path));
|
||||
|
||||
// If the plugin path references a `.grit` file directly, treat it as
|
||||
// a single-rule plugin instead of going through the manifest process:
|
||||
if plugin_path
|
||||
.as_os_str()
|
||||
.as_encoded_bytes()
|
||||
.ends_with(b".grit")
|
||||
.extension()
|
||||
.is_some_and(|extension| extension == "grit")
|
||||
{
|
||||
let plugin = AnalyzerGritPlugin::load(fs, &plugin_path)?;
|
||||
return Ok(Self {
|
||||
analyzer_plugins: vec![Box::new(plugin) as Box<dyn AnalyzerPlugin>],
|
||||
analyzer_plugins: vec![Arc::new(Box::new(plugin) as Box<dyn AnalyzerPlugin>)],
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -90,7 +76,7 @@ impl BiomePlugin {
|
|||
.map(|rule| {
|
||||
if rule.as_os_str().as_encoded_bytes().ends_with(b".grit") {
|
||||
let plugin = AnalyzerGritPlugin::load(fs, &plugin_path.join(rule))?;
|
||||
Ok(Box::new(plugin) as Box<dyn AnalyzerPlugin>)
|
||||
Ok(Arc::new(Box::new(plugin) as Box<dyn AnalyzerPlugin>))
|
||||
} else {
|
||||
Err(PluginDiagnostic::unsupported_rule_format(markup!(
|
||||
"Unsupported rule format for plugin rule "
|
||||
|
|
@ -105,6 +91,39 @@ impl BiomePlugin {
|
|||
}
|
||||
}
|
||||
|
||||
/// Normalizes the given `path` without requiring filesystem access.
|
||||
///
|
||||
/// This only normalizes `.` and `..` entries, but does not resolve symlinks.
|
||||
fn normalize_path(path: &Utf8Path) -> Utf8PathBuf {
|
||||
let mut stack = Vec::new();
|
||||
|
||||
for component in path.components() {
|
||||
match component {
|
||||
Utf8Component::ParentDir => {
|
||||
if stack.last().is_some_and(|last| *last == "..") {
|
||||
stack.push("..");
|
||||
} else {
|
||||
stack.pop();
|
||||
}
|
||||
}
|
||||
Utf8Component::CurDir => {}
|
||||
Utf8Component::RootDir => {
|
||||
stack.clear();
|
||||
stack.push("/");
|
||||
}
|
||||
Utf8Component::Normal(c) => stack.push(c),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut result = Utf8PathBuf::new();
|
||||
for part in stack {
|
||||
result.push(part);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use biome_diagnostics::{print_diagnostic_to_string, Error};
|
||||
|
|
@ -138,7 +157,7 @@ mod test {
|
|||
|
||||
fs.insert("/my-plugin/rules/1.grit".into(), r#"`hello`"#);
|
||||
|
||||
let plugin = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"), Utf8Path::new("/"))
|
||||
let plugin = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"))
|
||||
.expect("Couldn't load plugin");
|
||||
assert_eq!(plugin.analyzer_plugins.len(), 1);
|
||||
}
|
||||
|
|
@ -148,7 +167,7 @@ mod test {
|
|||
let mut fs = MemoryFileSystem::default();
|
||||
fs.insert("/my-plugin/rules/1.grit".into(), r#"`hello`"#);
|
||||
|
||||
let error = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"), Utf8Path::new("/"))
|
||||
let error = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"))
|
||||
.expect_err("Plugin loading should've failed");
|
||||
snap_diagnostic("load_plugin_without_manifest", error.into());
|
||||
}
|
||||
|
|
@ -164,7 +183,7 @@ mod test {
|
|||
}"#,
|
||||
);
|
||||
|
||||
let error = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"), Utf8Path::new("/"))
|
||||
let error = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"))
|
||||
.expect_err("Plugin loading should've failed");
|
||||
snap_diagnostic("load_plugin_with_wrong_version", error.into());
|
||||
}
|
||||
|
|
@ -180,7 +199,7 @@ mod test {
|
|||
}"#,
|
||||
);
|
||||
|
||||
let error = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"), Utf8Path::new("/"))
|
||||
let error = BiomePlugin::load(&fs, "./my-plugin", Utf8Path::new("/"))
|
||||
.expect_err("Plugin loading should've failed");
|
||||
snap_diagnostic("load_plugin_with_wrong_rule_extension", error.into());
|
||||
}
|
||||
|
|
@ -190,13 +209,8 @@ mod test {
|
|||
let mut fs = MemoryFileSystem::default();
|
||||
fs.insert("/my-plugin.grit".into(), r#"`hello`"#);
|
||||
|
||||
let plugin = BiomePlugin::load(
|
||||
&fs,
|
||||
"./my-plugin.grit",
|
||||
Utf8Path::new("/"),
|
||||
Utf8Path::new("/"),
|
||||
)
|
||||
.expect("Couldn't load plugin");
|
||||
let plugin = BiomePlugin::load(&fs, "./my-plugin.grit", Utf8Path::new("/"))
|
||||
.expect("Couldn't load plugin");
|
||||
assert_eq!(plugin.analyzer_plugins.len(), 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
29
crates/biome_plugin_loader/src/plugin_cache.rs
Normal file
29
crates/biome_plugin_loader/src/plugin_cache.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use biome_analyze::AnalyzerPluginVec;
|
||||
use camino::Utf8PathBuf;
|
||||
use papaya::HashMap;
|
||||
use rustc_hash::FxBuildHasher;
|
||||
|
||||
use crate::BiomePlugin;
|
||||
|
||||
/// Cache for storing loaded plugins in memory.
|
||||
///
|
||||
/// Plugins are kept in a map from path to plugin instance. This allows for
|
||||
/// convenient reloading of plugins if they are modified on disk.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PluginCache(HashMap<Utf8PathBuf, BiomePlugin, FxBuildHasher>);
|
||||
|
||||
impl PluginCache {
|
||||
/// Inserts a new plugin into the cache.
|
||||
pub fn insert_plugin(&self, path: Utf8PathBuf, plugin: BiomePlugin) {
|
||||
self.0.pin().insert(path, plugin);
|
||||
}
|
||||
|
||||
/// Returns the loaded analyzer plugins.
|
||||
pub fn get_analyzer_plugins(&self) -> AnalyzerPluginVec {
|
||||
let mut plugins = AnalyzerPluginVec::new();
|
||||
for plugin in self.0.pin().values() {
|
||||
plugins.extend_from_slice(&plugin.analyzer_plugins);
|
||||
}
|
||||
plugins
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,7 @@ biome_json_parser = { workspace = true }
|
|||
biome_json_syntax = { workspace = true }
|
||||
biome_package = { workspace = true }
|
||||
biome_parser = { workspace = true }
|
||||
biome_plugin_loader = { workspace = true }
|
||||
biome_project_layout = { workspace = true }
|
||||
biome_rowan = { workspace = true, features = ["serde"] }
|
||||
biome_string_case = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use biome_formatter::{FormatError, PrintError};
|
|||
use biome_fs::{BiomePath, FileSystemDiagnostic};
|
||||
use biome_grit_patterns::CompileError;
|
||||
use biome_js_analyze::utils::rename::RenameError;
|
||||
use biome_plugin_loader::PluginDiagnostic;
|
||||
use camino::Utf8Path;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
|
@ -52,7 +53,9 @@ pub enum WorkspaceError {
|
|||
FileSystem(FileSystemDiagnostic),
|
||||
/// Raised when there's an issue around the VCS integration
|
||||
Vcs(VcsDiagnostic),
|
||||
/// Diagnostic raised when a file is protected
|
||||
/// One or more errors occurred during plugin loading.
|
||||
PluginErrors(PluginErrors),
|
||||
/// Diagnostic raised when a file is protected.
|
||||
ProtectedFile(ProtectedFile),
|
||||
/// Error when searching for a pattern
|
||||
SearchError(SearchError),
|
||||
|
|
@ -95,6 +98,10 @@ impl WorkspaceError {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn plugin_errors(diagnostics: Vec<PluginDiagnostic>) -> Self {
|
||||
Self::PluginErrors(PluginErrors { diagnostics })
|
||||
}
|
||||
|
||||
pub fn vcs_disabled() -> Self {
|
||||
Self::Vcs(VcsDiagnostic::DisabledVcs(DisabledVcs {}))
|
||||
}
|
||||
|
|
@ -530,6 +537,31 @@ pub struct NoVcsFolderFound {
|
|||
)]
|
||||
pub struct DisabledVcs {}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PluginErrors {
|
||||
diagnostics: Vec<PluginDiagnostic>,
|
||||
}
|
||||
|
||||
impl Diagnostic for PluginErrors {
|
||||
fn category(&self) -> Option<&'static Category> {
|
||||
Some(category!("plugin"))
|
||||
}
|
||||
|
||||
fn severity(&self) -> Severity {
|
||||
Severity::Error
|
||||
}
|
||||
|
||||
fn message(&self, fmt: &mut biome_console::fmt::Formatter<'_>) -> std::io::Result<()> {
|
||||
fmt.write_markup(markup!("Error(s) during loading of plugins:\n"))?;
|
||||
|
||||
for diagnostic in &self.diagnostics {
|
||||
diagnostic.message(fmt)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Diagnostic)]
|
||||
#[diagnostic(
|
||||
category = "project",
|
||||
|
|
|
|||
|
|
@ -520,10 +520,13 @@ fn lint(params: LintParams) -> LintResults {
|
|||
|
||||
let mut process_lint = ProcessLint::new(¶ms);
|
||||
|
||||
let (_, analyze_diagnostics) =
|
||||
analyze(&tree, filter, &analyzer_options, Vec::new(), |signal| {
|
||||
process_lint.process_signal(signal)
|
||||
});
|
||||
let (_, analyze_diagnostics) = analyze(
|
||||
&tree,
|
||||
filter,
|
||||
&analyzer_options,
|
||||
¶ms.plugins,
|
||||
|signal| process_lint.process_signal(signal),
|
||||
);
|
||||
|
||||
process_lint.into_result(params.parse.into_diagnostics(), analyze_diagnostics)
|
||||
}
|
||||
|
|
@ -542,6 +545,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult {
|
|||
skip,
|
||||
enabled_rules: rules,
|
||||
suppression_reason,
|
||||
plugins,
|
||||
} = params;
|
||||
let _ = debug_span!("Code actions CSS", range =? range, path =? path).entered();
|
||||
let tree = parse.tree();
|
||||
|
|
@ -578,7 +582,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult {
|
|||
|
||||
info!("CSS runs the analyzer");
|
||||
|
||||
analyze(&tree, filter, &analyzer_options, Vec::new(), |signal| {
|
||||
analyze(&tree, filter, &analyzer_options, &plugins, |signal| {
|
||||
actions.extend(signal.actions().into_code_action_iter().map(|item| {
|
||||
CodeAction {
|
||||
category: item.category.clone(),
|
||||
|
|
@ -638,48 +642,54 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result<FixFileResult, WorkspaceEr
|
|||
let mut errors: u16 = 0;
|
||||
|
||||
loop {
|
||||
let (action, _) = analyze(&tree, filter, &analyzer_options, Vec::new(), |signal| {
|
||||
let current_diagnostic = signal.diagnostic();
|
||||
let (action, _) = analyze(
|
||||
&tree,
|
||||
filter,
|
||||
&analyzer_options,
|
||||
¶ms.plugins,
|
||||
|signal| {
|
||||
let current_diagnostic = signal.diagnostic();
|
||||
|
||||
if let Some(diagnostic) = current_diagnostic.as_ref() {
|
||||
if is_diagnostic_error(diagnostic, rules.as_deref()) {
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
|
||||
for action in signal.actions() {
|
||||
// suppression actions should not be part of the fixes (safe or suggested)
|
||||
if action.is_suppression() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match params.fix_file_mode {
|
||||
FixFileMode::SafeFixes => {
|
||||
if action.applicability == Applicability::MaybeIncorrect {
|
||||
skipped_suggested_fixes += 1;
|
||||
}
|
||||
if action.applicability == Applicability::Always {
|
||||
errors = errors.saturating_sub(1);
|
||||
return ControlFlow::Break(action);
|
||||
}
|
||||
}
|
||||
FixFileMode::SafeAndUnsafeFixes => {
|
||||
if matches!(
|
||||
action.applicability,
|
||||
Applicability::Always | Applicability::MaybeIncorrect
|
||||
) {
|
||||
errors = errors.saturating_sub(1);
|
||||
return ControlFlow::Break(action);
|
||||
}
|
||||
}
|
||||
FixFileMode::ApplySuppressions => {
|
||||
// TODO: to implement
|
||||
if let Some(diagnostic) = current_diagnostic.as_ref() {
|
||||
if is_diagnostic_error(diagnostic, rules.as_deref()) {
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
});
|
||||
for action in signal.actions() {
|
||||
// suppression actions should not be part of the fixes (safe or suggested)
|
||||
if action.is_suppression() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match params.fix_file_mode {
|
||||
FixFileMode::SafeFixes => {
|
||||
if action.applicability == Applicability::MaybeIncorrect {
|
||||
skipped_suggested_fixes += 1;
|
||||
}
|
||||
if action.applicability == Applicability::Always {
|
||||
errors = errors.saturating_sub(1);
|
||||
return ControlFlow::Break(action);
|
||||
}
|
||||
}
|
||||
FixFileMode::SafeAndUnsafeFixes => {
|
||||
if matches!(
|
||||
action.applicability,
|
||||
Applicability::Always | Applicability::MaybeIncorrect
|
||||
) {
|
||||
errors = errors.saturating_sub(1);
|
||||
return ControlFlow::Break(action);
|
||||
}
|
||||
}
|
||||
FixFileMode::ApplySuppressions => {
|
||||
// TODO: to implement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ControlFlow::Continue(())
|
||||
},
|
||||
);
|
||||
|
||||
match action {
|
||||
Some(action) => {
|
||||
|
|
|
|||
|
|
@ -466,6 +466,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult {
|
|||
skip,
|
||||
suppression_reason,
|
||||
enabled_rules: rules,
|
||||
plugins: _,
|
||||
} = params;
|
||||
let _ = debug_span!("Code actions GraphQL", range =? range, path =? path).entered();
|
||||
let tree = parse.tree();
|
||||
|
|
|
|||
|
|
@ -605,7 +605,7 @@ fn debug_control_flow(parse: AnyParse, cursor: TextSize) -> String {
|
|||
}
|
||||
},
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|_| ControlFlow::<Never>::Continue(()),
|
||||
);
|
||||
|
|
@ -672,7 +672,7 @@ pub(crate) fn lint(params: LintParams) -> LintResults {
|
|||
&tree,
|
||||
filter,
|
||||
&analyzer_options,
|
||||
Vec::new(),
|
||||
¶ms.plugins,
|
||||
services,
|
||||
|signal| process_lint.process_signal(signal),
|
||||
);
|
||||
|
|
@ -694,6 +694,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult {
|
|||
skip,
|
||||
suppression_reason,
|
||||
enabled_rules: rules,
|
||||
plugins,
|
||||
} = params;
|
||||
let _ = debug_span!("Code actions JavaScript", range =? range, path =? path).entered();
|
||||
let tree = parse.tree();
|
||||
|
|
@ -735,7 +736,7 @@ pub(crate) fn code_actions(params: CodeActionsParams) -> PullActionsResult {
|
|||
&tree,
|
||||
filter,
|
||||
&analyzer_options,
|
||||
Vec::new(),
|
||||
&plugins,
|
||||
services,
|
||||
|signal| {
|
||||
actions.extend(signal.actions().into_code_action_iter().map(|item| {
|
||||
|
|
@ -814,7 +815,7 @@ pub(crate) fn fix_all(params: FixAllParams) -> Result<FixFileResult, WorkspaceEr
|
|||
&tree,
|
||||
filter,
|
||||
&analyzer_options,
|
||||
Vec::new(),
|
||||
¶ms.plugins,
|
||||
services,
|
||||
|signal| {
|
||||
let current_diagnostic = signal.diagnostic();
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ fn code_actions(params: CodeActionsParams) -> PullActionsResult {
|
|||
only,
|
||||
enabled_rules: rules,
|
||||
suppression_reason,
|
||||
plugins: _,
|
||||
} = params;
|
||||
|
||||
let _ = debug_span!("Code actions JSON", range =? range, path =? path).entered();
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ use crate::workspace::{
|
|||
};
|
||||
use crate::WorkspaceError;
|
||||
use biome_analyze::{
|
||||
AnalyzerDiagnostic, AnalyzerOptions, AnalyzerSignal, ControlFlow, GroupCategory, Never,
|
||||
Queryable, RegistryVisitor, Rule, RuleCategories, RuleCategory, RuleFilter, RuleGroup,
|
||||
AnalyzerDiagnostic, AnalyzerOptions, AnalyzerPluginVec, AnalyzerSignal, ControlFlow,
|
||||
GroupCategory, Never, Queryable, RegistryVisitor, Rule, RuleCategories, RuleCategory,
|
||||
RuleFilter, RuleGroup,
|
||||
};
|
||||
use biome_configuration::analyzer::{RuleDomainValue, RuleSelector};
|
||||
use biome_configuration::Rules;
|
||||
|
|
@ -397,6 +398,7 @@ pub struct FixAllParams<'a> {
|
|||
pub(crate) rule_categories: RuleCategories,
|
||||
pub(crate) suppression_reason: Option<String>,
|
||||
pub(crate) enabled_rules: Vec<RuleSelector>,
|
||||
pub(crate) plugins: AnalyzerPluginVec,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
|
@ -463,6 +465,7 @@ pub(crate) struct LintParams<'a> {
|
|||
pub(crate) project_layout: Arc<ProjectLayout>,
|
||||
pub(crate) suppression_reason: Option<String>,
|
||||
pub(crate) enabled_rules: Vec<RuleSelector>,
|
||||
pub(crate) plugins: AnalyzerPluginVec,
|
||||
}
|
||||
|
||||
pub(crate) struct LintResults {
|
||||
|
|
@ -590,6 +593,7 @@ pub(crate) struct CodeActionsParams<'a> {
|
|||
pub(crate) skip: Vec<RuleSelector>,
|
||||
pub(crate) suppression_reason: Option<String>,
|
||||
pub(crate) enabled_rules: Vec<RuleSelector>,
|
||||
pub(crate) plugins: AnalyzerPluginVec,
|
||||
}
|
||||
|
||||
type Lint = fn(LintParams) -> LintResults;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use biome_configuration::formatter::{FormatWithErrorsEnabled, FormatterEnabled};
|
|||
use biome_configuration::html::HtmlConfiguration;
|
||||
use biome_configuration::javascript::JsxRuntime;
|
||||
use biome_configuration::max_size::MaxSize;
|
||||
use biome_configuration::plugins::Plugins;
|
||||
use biome_configuration::{
|
||||
push_to_analyzer_assist, push_to_analyzer_rules, BiomeDiagnostic, Configuration,
|
||||
CssConfiguration, FilesConfiguration, FilesIgnoreUnknownEnabled, FormatterConfiguration,
|
||||
|
|
@ -57,6 +58,8 @@ pub struct Settings {
|
|||
pub files: FilesSettings,
|
||||
/// Assist settings
|
||||
pub assist: AssistSettings,
|
||||
/// Plugin settings.
|
||||
pub plugins: Plugins,
|
||||
/// overrides
|
||||
pub override_settings: OverrideSettings,
|
||||
}
|
||||
|
|
@ -117,6 +120,11 @@ impl Settings {
|
|||
self.languages.html = html.into()
|
||||
}
|
||||
|
||||
// plugin settings
|
||||
if let Some(plugins) = configuration.plugins {
|
||||
self.plugins = plugins;
|
||||
}
|
||||
|
||||
// NOTE: keep this last. Computing the overrides require reading the settings computed by the parent settings.
|
||||
if let Some(overrides) = configuration.overrides {
|
||||
self.override_settings =
|
||||
|
|
|
|||
|
|
@ -530,6 +530,13 @@ pub struct UpdateSettingsParams {
|
|||
pub workspace_directory: Option<BiomePath>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateSettingsResult {
|
||||
pub diagnostics: Vec<Diagnostic>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
|
@ -1035,10 +1042,13 @@ pub trait Workspace: Send + Sync + RefUnwindSafe {
|
|||
|
||||
/// Updates the global settings for the given project.
|
||||
///
|
||||
/// This method should not be used in combination with
|
||||
/// `scan_project_folder()`. When scanning is enabled, the server will
|
||||
/// TODO: This method should not be used in combination with
|
||||
/// `scan_project_folder()`. When scanning is enabled, the server should
|
||||
/// manage project settings on its own.
|
||||
fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), WorkspaceError>;
|
||||
fn update_settings(
|
||||
&self,
|
||||
params: UpdateSettingsParams,
|
||||
) -> Result<UpdateSettingsResult, WorkspaceError>;
|
||||
|
||||
/// Closes the project with the given key.
|
||||
///
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ use super::{
|
|||
GetSyntaxTreeParams, GetSyntaxTreeResult, OpenFileParams, PullActionsParams, PullActionsResult,
|
||||
PullDiagnosticsParams, PullDiagnosticsResult, RenameParams, RenameResult,
|
||||
ScanProjectFolderParams, ScanProjectFolderResult, SearchPatternParams, SearchResults,
|
||||
SupportsFeatureParams, UpdateSettingsParams,
|
||||
SupportsFeatureParams, UpdateSettingsParams, UpdateSettingsResult,
|
||||
};
|
||||
|
||||
pub struct WorkspaceClient<T> {
|
||||
|
|
@ -120,7 +120,10 @@ where
|
|||
self.request("biome/is_path_ignored", params)
|
||||
}
|
||||
|
||||
fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), WorkspaceError> {
|
||||
fn update_settings(
|
||||
&self,
|
||||
params: UpdateSettingsParams,
|
||||
) -> Result<UpdateSettingsResult, WorkspaceError> {
|
||||
self.request("biome/update_settings", params)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use super::{
|
|||
ParsePatternParams, ParsePatternResult, PatternId, ProjectKey, PullActionsParams,
|
||||
PullActionsResult, PullDiagnosticsParams, PullDiagnosticsResult, RenameResult,
|
||||
ScanProjectFolderParams, ScanProjectFolderResult, SearchPatternParams, SearchResults,
|
||||
SupportsFeatureParams, UpdateSettingsParams,
|
||||
SupportsFeatureParams, UpdateSettingsParams, UpdateSettingsResult,
|
||||
};
|
||||
use crate::diagnostics::FileTooLarge;
|
||||
use crate::file_handlers::{
|
||||
|
|
@ -22,6 +22,8 @@ use crate::workspace::{
|
|||
};
|
||||
use crate::{file_handlers::Features, Workspace, WorkspaceError};
|
||||
use append_only_vec::AppendOnlyVec;
|
||||
use biome_analyze::AnalyzerPluginVec;
|
||||
use biome_configuration::plugins::{PluginConfiguration, Plugins};
|
||||
use biome_configuration::{BiomeDiagnostic, Configuration};
|
||||
use biome_dependency_graph::DependencyGraph;
|
||||
use biome_deserialize::json::deserialize_from_json_str;
|
||||
|
|
@ -38,6 +40,7 @@ use biome_json_parser::JsonParserOptions;
|
|||
use biome_json_syntax::JsonFileSource;
|
||||
use biome_package::PackageType;
|
||||
use biome_parser::AnyParse;
|
||||
use biome_plugin_loader::{BiomePlugin, PluginCache, PluginDiagnostic};
|
||||
use biome_project_layout::ProjectLayout;
|
||||
use biome_rowan::NodeCache;
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
|
|
@ -62,6 +65,9 @@ pub(super) struct WorkspaceServer {
|
|||
/// Dependency graph tracking imports across source files.
|
||||
dependency_graph: Arc<DependencyGraph>,
|
||||
|
||||
/// Keeps all loaded plugins in memory, per project.
|
||||
plugin_caches: Arc<HashMap<ProjectKey, PluginCache>>,
|
||||
|
||||
/// Stores the document (text content + version number) associated with a URL
|
||||
documents: HashMap<Utf8PathBuf, Document, FxBuildHasher>,
|
||||
|
||||
|
|
@ -136,6 +142,7 @@ impl WorkspaceServer {
|
|||
projects: Default::default(),
|
||||
project_layout: Default::default(),
|
||||
dependency_graph: Default::default(),
|
||||
plugin_caches: Default::default(),
|
||||
documents: Default::default(),
|
||||
file_sources: AppendOnlyVec::default(),
|
||||
patterns: Default::default(),
|
||||
|
|
@ -519,6 +526,41 @@ impl WorkspaceServer {
|
|||
})
|
||||
}
|
||||
|
||||
fn load_plugins(
|
||||
&self,
|
||||
project_key: ProjectKey,
|
||||
base_path: &Utf8Path,
|
||||
plugins: &Plugins,
|
||||
) -> Vec<PluginDiagnostic> {
|
||||
let mut diagnostics = Vec::new();
|
||||
let plugin_cache = PluginCache::default();
|
||||
|
||||
for plugin_config in plugins.iter() {
|
||||
match plugin_config {
|
||||
PluginConfiguration::Path(plugin_path) => {
|
||||
match BiomePlugin::load(self.fs.as_ref(), plugin_path, base_path) {
|
||||
Ok(plugin) => {
|
||||
plugin_cache.insert_plugin(plugin_path.clone().into(), plugin);
|
||||
}
|
||||
Err(diagnostic) => diagnostics.push(diagnostic),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.plugin_caches.pin().insert(project_key, plugin_cache);
|
||||
|
||||
diagnostics
|
||||
}
|
||||
|
||||
fn get_analyzer_plugins_for_project(&self, project_key: ProjectKey) -> AnalyzerPluginVec {
|
||||
self.plugin_caches
|
||||
.pin()
|
||||
.get(&project_key)
|
||||
.map(|cache| cache.get_analyzer_plugins())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(super) fn update_project_layout_for_paths(&self, paths: &[BiomePath]) {
|
||||
for path in paths {
|
||||
if let Err(error) = self.update_project_layout_for_path(path) {
|
||||
|
|
@ -645,22 +687,44 @@ impl Workspace for WorkspaceServer {
|
|||
/// This function may panic if the internal settings mutex has been poisoned
|
||||
/// by another thread having previously panicked while holding the lock
|
||||
#[tracing::instrument(level = "debug", skip(self))]
|
||||
fn update_settings(&self, params: UpdateSettingsParams) -> Result<(), WorkspaceError> {
|
||||
fn update_settings(
|
||||
&self,
|
||||
params: UpdateSettingsParams,
|
||||
) -> Result<UpdateSettingsResult, WorkspaceError> {
|
||||
let mut settings = self
|
||||
.projects
|
||||
.get_settings(params.project_key)
|
||||
.ok_or_else(WorkspaceError::no_project)?;
|
||||
|
||||
let workspace_directory = params.workspace_directory.map(|p| p.to_path_buf());
|
||||
|
||||
settings.merge_with_configuration(
|
||||
params.configuration,
|
||||
params.workspace_directory.map(|p| p.to_path_buf()),
|
||||
workspace_directory.clone(),
|
||||
params.vcs_base_path.map(|p| p.to_path_buf()),
|
||||
params.gitignore_matches.as_slice(),
|
||||
)?;
|
||||
|
||||
let diagnostics = self.load_plugins(
|
||||
params.project_key,
|
||||
&workspace_directory.unwrap_or_default(),
|
||||
&settings.plugins,
|
||||
);
|
||||
let has_errors = diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic.severity() >= Severity::Error);
|
||||
if has_errors {
|
||||
// Note we also pass non-error diagnostics here. Filtering them
|
||||
// might be cleaner, but on the other hand, including them may
|
||||
// sometimes give a hint as to why an error occurred?
|
||||
return Err(WorkspaceError::plugin_errors(diagnostics));
|
||||
}
|
||||
|
||||
self.projects.set_settings(params.project_key, settings);
|
||||
|
||||
Ok(())
|
||||
Ok(UpdateSettingsResult {
|
||||
diagnostics: diagnostics.into_iter().map(Into::into).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
fn open_file(&self, params: OpenFileParams) -> Result<(), WorkspaceError> {
|
||||
|
|
@ -918,6 +982,7 @@ impl Workspace for WorkspaceServer {
|
|||
project_layout: self.project_layout.clone(),
|
||||
suppression_reason: None,
|
||||
enabled_rules,
|
||||
plugins: self.get_analyzer_plugins_for_project(project_key),
|
||||
});
|
||||
|
||||
(
|
||||
|
|
@ -998,6 +1063,7 @@ impl Workspace for WorkspaceServer {
|
|||
skip,
|
||||
suppression_reason: None,
|
||||
enabled_rules,
|
||||
plugins: self.get_analyzer_plugins_for_project(project_key),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -1137,6 +1203,7 @@ impl Workspace for WorkspaceServer {
|
|||
rule_categories,
|
||||
suppression_reason,
|
||||
enabled_rules,
|
||||
plugins: self.get_analyzer_plugins_for_project(project_key),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
source: crates/biome_service/tests/workspace.rs
|
||||
expression: result.diagnostics
|
||||
---
|
||||
[
|
||||
Diagnostic {
|
||||
category: Some(
|
||||
Category {
|
||||
name: "plugin",
|
||||
link: None,
|
||||
},
|
||||
),
|
||||
severity: Information,
|
||||
description: "Prefer object spread instead of `Object.assign()`",
|
||||
message: "Prefer object spread instead of `Object.assign()`",
|
||||
advices: Advices {
|
||||
advices: [],
|
||||
},
|
||||
verbose_advices: Advices {
|
||||
advices: [],
|
||||
},
|
||||
location: Location {
|
||||
path: Some(
|
||||
File(
|
||||
"/project/a.ts",
|
||||
),
|
||||
),
|
||||
span: Some(
|
||||
24..38,
|
||||
),
|
||||
source_code: None,
|
||||
},
|
||||
tags: DiagnosticTags(
|
||||
BitFlags<DiagnosticTag> {
|
||||
bits: 0b0,
|
||||
},
|
||||
),
|
||||
source: None,
|
||||
},
|
||||
]
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
mod test {
|
||||
use biome_analyze::RuleCategories;
|
||||
use biome_configuration::analyzer::{RuleGroup, RuleSelector};
|
||||
use biome_configuration::plugins::{PluginConfiguration, Plugins};
|
||||
use biome_configuration::{Configuration, FilesConfiguration};
|
||||
use biome_fs::{BiomePath, MemoryFileSystem};
|
||||
use biome_js_syntax::{JsFileSource, TextSize};
|
||||
|
|
@ -9,11 +10,12 @@ mod test {
|
|||
use biome_service::projects::ProjectKey;
|
||||
use biome_service::workspace::{
|
||||
server, CloseFileParams, CloseProjectParams, FileContent, FileGuard, GetFileContentParams,
|
||||
GetSyntaxTreeParams, OpenFileParams, OpenProjectParams, ScanProjectFolderParams,
|
||||
UpdateSettingsParams,
|
||||
GetSyntaxTreeParams, OpenFileParams, OpenProjectParams, PullDiagnosticsParams,
|
||||
ScanProjectFolderParams, UpdateSettingsParams,
|
||||
};
|
||||
use biome_service::{Workspace, WorkspaceError};
|
||||
use camino::Utf8PathBuf;
|
||||
use insta::assert_debug_snapshot;
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
fn create_server() -> (Box<dyn Workspace>, ProjectKey) {
|
||||
|
|
@ -433,4 +435,66 @@ type User {
|
|||
})
|
||||
.is_err_and(|error| matches!(error, WorkspaceError::FileIgnored(_))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plugins_are_loaded_and_used_during_analysis() {
|
||||
const PLUGIN_CONTENT: &[u8] = br#"
|
||||
`Object.assign($args)` where {
|
||||
register_diagnostic(
|
||||
span = $args,
|
||||
message = "Prefer object spread instead of `Object.assign()`"
|
||||
)
|
||||
}
|
||||
"#;
|
||||
|
||||
const FILE_CONTENT: &[u8] = b"const a = Object.assign({ foo: 'bar' });";
|
||||
|
||||
let mut fs = MemoryFileSystem::default();
|
||||
fs.insert(Utf8PathBuf::from("/project/plugin.grit"), PLUGIN_CONTENT);
|
||||
fs.insert(Utf8PathBuf::from("/project/a.ts"), FILE_CONTENT);
|
||||
|
||||
let workspace = server(Box::new(fs));
|
||||
let project_key = workspace
|
||||
.open_project(OpenProjectParams {
|
||||
path: Utf8PathBuf::from("/project").into(),
|
||||
open_uninitialized: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
workspace
|
||||
.update_settings(UpdateSettingsParams {
|
||||
project_key,
|
||||
configuration: Configuration {
|
||||
plugins: Some(Plugins(vec![PluginConfiguration::Path(
|
||||
"./plugin.grit".to_string(),
|
||||
)])),
|
||||
..Default::default()
|
||||
},
|
||||
vcs_base_path: None,
|
||||
gitignore_matches: Vec::new(),
|
||||
workspace_directory: Some(BiomePath::new("/project")),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
workspace
|
||||
.scan_project_folder(ScanProjectFolderParams {
|
||||
project_key,
|
||||
path: None,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let result = workspace
|
||||
.pull_diagnostics(PullDiagnosticsParams {
|
||||
project_key,
|
||||
path: BiomePath::new("/project/a.ts"),
|
||||
categories: RuleCategories::default(),
|
||||
max_diagnostics: 10,
|
||||
only: Vec::new(),
|
||||
skip: Vec::new(),
|
||||
enabled_rules: Vec::new(),
|
||||
})
|
||||
.unwrap();
|
||||
assert_debug_snapshot!(result.diagnostics);
|
||||
assert_eq!(result.errors, 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,10 +50,16 @@ impl Workspace {
|
|||
}
|
||||
|
||||
#[wasm_bindgen(js_name = updateSettings)]
|
||||
pub fn update_settings(&self, params: IUpdateSettingsParams) -> Result<(), Error> {
|
||||
pub fn update_settings(
|
||||
&self,
|
||||
params: IUpdateSettingsParams,
|
||||
) -> Result<IUpdateSettingsResult, Error> {
|
||||
let params: UpdateSettingsParams =
|
||||
serde_wasm_bindgen::from_value(params.into()).map_err(into_error)?;
|
||||
self.inner.update_settings(params).map_err(into_error)
|
||||
let result = self.inner.update_settings(params).map_err(into_error)?;
|
||||
to_value(&result)
|
||||
.map(IUpdateSettingsResult::from)
|
||||
.map_err(into_error)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = openProject)]
|
||||
|
|
|
|||
305
packages/@biomejs/backend-jsonrpc/src/workspace.ts
generated
305
packages/@biomejs/backend-jsonrpc/src/workspace.ts
generated
|
|
@ -3013,157 +3013,8 @@ export type RestrictedModifier =
|
|||
| "protected"
|
||||
| "readonly"
|
||||
| "static";
|
||||
export interface OpenProjectParams {
|
||||
/**
|
||||
* Whether the folder should be opened as a project, even if no `biome.json` can be found.
|
||||
*/
|
||||
openUninitialized: boolean;
|
||||
/**
|
||||
* The path to open
|
||||
*/
|
||||
path: BiomePath;
|
||||
}
|
||||
export interface OpenFileParams {
|
||||
content: FileContent;
|
||||
documentFileSource?: DocumentFileSource;
|
||||
path: BiomePath;
|
||||
/**
|
||||
* Set to `true` to persist the node cache used during parsing, in order to speed up subsequent reparsing if the document has been edited.
|
||||
|
||||
This should only be enabled if reparsing is to be expected, such as when the file is opened through the LSP Proxy.
|
||||
*/
|
||||
persistNodeCache?: boolean;
|
||||
projectKey: ProjectKey;
|
||||
version: number;
|
||||
}
|
||||
export type FileContent =
|
||||
| { content: string; type: "fromClient" }
|
||||
| { type: "fromServer" };
|
||||
export type DocumentFileSource =
|
||||
| "Unknown"
|
||||
| { Js: JsFileSource }
|
||||
| { Json: JsonFileSource }
|
||||
| { Css: CssFileSource }
|
||||
| { Graphql: GraphqlFileSource }
|
||||
| { Html: HtmlFileSource }
|
||||
| { Grit: GritFileSource };
|
||||
export interface JsFileSource {
|
||||
/**
|
||||
* Used to mark if the source is being used for an Astro, Svelte or Vue file
|
||||
*/
|
||||
embedding_kind: EmbeddingKind;
|
||||
language: Language;
|
||||
module_kind: ModuleKind;
|
||||
variant: LanguageVariant;
|
||||
version: LanguageVersion;
|
||||
}
|
||||
export interface JsonFileSource {
|
||||
allowComments: boolean;
|
||||
allowTrailingCommas: boolean;
|
||||
variant: JsonFileVariant;
|
||||
}
|
||||
export interface CssFileSource {
|
||||
variant: CssVariant;
|
||||
}
|
||||
export interface GraphqlFileSource {
|
||||
variant: GraphqlVariant;
|
||||
}
|
||||
export interface HtmlFileSource {
|
||||
variant: HtmlVariant;
|
||||
}
|
||||
export interface GritFileSource {
|
||||
variant: GritVariant;
|
||||
}
|
||||
export type EmbeddingKind = "Astro" | "Vue" | "Svelte" | "None";
|
||||
export type Language =
|
||||
| "javaScript"
|
||||
| { typeScript: { definition_file: boolean } };
|
||||
/**
|
||||
* Is the source file an ECMAScript Module or Script. Changes the parsing semantic.
|
||||
*/
|
||||
export type ModuleKind = "script" | "module";
|
||||
export type LanguageVariant = "standard" | "standardRestricted" | "jsx";
|
||||
/**
|
||||
* Enum of the different ECMAScript standard versions. The versions are ordered in increasing order; The newest version comes last.
|
||||
|
||||
Defaults to the latest stable ECMAScript standard.
|
||||
*/
|
||||
export type LanguageVersion = "eS2022" | "eSNext";
|
||||
/**
|
||||
* It represents the extension of the file
|
||||
*/
|
||||
export type JsonFileVariant = "standard" | "jsonc";
|
||||
/**
|
||||
* The style of CSS contained in the file.
|
||||
|
||||
Currently, Biome only supports plain CSS, and aims to be compatible with the latest Recommendation level standards.
|
||||
*/
|
||||
export type CssVariant = "standard";
|
||||
/**
|
||||
* The style of GraphQL contained in the file.
|
||||
*/
|
||||
export type GraphqlVariant = "standard";
|
||||
export type HtmlVariant = "Standard" | "Astro";
|
||||
export type GritVariant = "Standard";
|
||||
export interface ChangeFileParams {
|
||||
content: string;
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
version: number;
|
||||
}
|
||||
export interface CloseFileParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetSyntaxTreeParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetSyntaxTreeResult {
|
||||
ast: string;
|
||||
cst: string;
|
||||
}
|
||||
export interface CheckFileSizeParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface CheckFileSizeResult {
|
||||
fileSize: number;
|
||||
limit: number;
|
||||
}
|
||||
export interface GetFileContentParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetControlFlowGraphParams {
|
||||
cursor: TextSize;
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export type TextSize = number;
|
||||
export interface GetFormatterIRParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface PullDiagnosticsParams {
|
||||
categories: RuleCategories;
|
||||
/**
|
||||
* Rules to apply on top of the configuration
|
||||
*/
|
||||
enabledRules?: RuleCode[];
|
||||
maxDiagnostics: number;
|
||||
only?: RuleCode[];
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
skip?: RuleCode[];
|
||||
}
|
||||
export type RuleCategories = RuleCategory[];
|
||||
export type RuleCode = string;
|
||||
export type RuleCategory = "syntax" | "lint" | "action" | "transformation";
|
||||
export interface PullDiagnosticsResult {
|
||||
export interface UpdateSettingsResult {
|
||||
diagnostics: Diagnostic[];
|
||||
errors: number;
|
||||
skippedDiagnostics: number;
|
||||
}
|
||||
/**
|
||||
* Serializable representation for a [Diagnostic](super::Diagnostic).
|
||||
|
|
@ -3611,6 +3462,7 @@ export interface TextEdit {
|
|||
ops: CompressedOp[];
|
||||
}
|
||||
export type Backtrace = BacktraceFrame[];
|
||||
export type TextSize = number;
|
||||
/**
|
||||
* Enumeration of all the supported markup elements
|
||||
*/
|
||||
|
|
@ -3650,6 +3502,157 @@ export interface BacktraceSymbol {
|
|||
lineno?: number;
|
||||
name?: string;
|
||||
}
|
||||
export interface OpenProjectParams {
|
||||
/**
|
||||
* Whether the folder should be opened as a project, even if no `biome.json` can be found.
|
||||
*/
|
||||
openUninitialized: boolean;
|
||||
/**
|
||||
* The path to open
|
||||
*/
|
||||
path: BiomePath;
|
||||
}
|
||||
export interface OpenFileParams {
|
||||
content: FileContent;
|
||||
documentFileSource?: DocumentFileSource;
|
||||
path: BiomePath;
|
||||
/**
|
||||
* Set to `true` to persist the node cache used during parsing, in order to speed up subsequent reparsing if the document has been edited.
|
||||
|
||||
This should only be enabled if reparsing is to be expected, such as when the file is opened through the LSP Proxy.
|
||||
*/
|
||||
persistNodeCache?: boolean;
|
||||
projectKey: ProjectKey;
|
||||
version: number;
|
||||
}
|
||||
export type FileContent =
|
||||
| { content: string; type: "fromClient" }
|
||||
| { type: "fromServer" };
|
||||
export type DocumentFileSource =
|
||||
| "Unknown"
|
||||
| { Js: JsFileSource }
|
||||
| { Json: JsonFileSource }
|
||||
| { Css: CssFileSource }
|
||||
| { Graphql: GraphqlFileSource }
|
||||
| { Html: HtmlFileSource }
|
||||
| { Grit: GritFileSource };
|
||||
export interface JsFileSource {
|
||||
/**
|
||||
* Used to mark if the source is being used for an Astro, Svelte or Vue file
|
||||
*/
|
||||
embedding_kind: EmbeddingKind;
|
||||
language: Language;
|
||||
module_kind: ModuleKind;
|
||||
variant: LanguageVariant;
|
||||
version: LanguageVersion;
|
||||
}
|
||||
export interface JsonFileSource {
|
||||
allowComments: boolean;
|
||||
allowTrailingCommas: boolean;
|
||||
variant: JsonFileVariant;
|
||||
}
|
||||
export interface CssFileSource {
|
||||
variant: CssVariant;
|
||||
}
|
||||
export interface GraphqlFileSource {
|
||||
variant: GraphqlVariant;
|
||||
}
|
||||
export interface HtmlFileSource {
|
||||
variant: HtmlVariant;
|
||||
}
|
||||
export interface GritFileSource {
|
||||
variant: GritVariant;
|
||||
}
|
||||
export type EmbeddingKind = "Astro" | "Vue" | "Svelte" | "None";
|
||||
export type Language =
|
||||
| "javaScript"
|
||||
| { typeScript: { definition_file: boolean } };
|
||||
/**
|
||||
* Is the source file an ECMAScript Module or Script. Changes the parsing semantic.
|
||||
*/
|
||||
export type ModuleKind = "script" | "module";
|
||||
export type LanguageVariant = "standard" | "standardRestricted" | "jsx";
|
||||
/**
|
||||
* Enum of the different ECMAScript standard versions. The versions are ordered in increasing order; The newest version comes last.
|
||||
|
||||
Defaults to the latest stable ECMAScript standard.
|
||||
*/
|
||||
export type LanguageVersion = "eS2022" | "eSNext";
|
||||
/**
|
||||
* It represents the extension of the file
|
||||
*/
|
||||
export type JsonFileVariant = "standard" | "jsonc";
|
||||
/**
|
||||
* The style of CSS contained in the file.
|
||||
|
||||
Currently, Biome only supports plain CSS, and aims to be compatible with the latest Recommendation level standards.
|
||||
*/
|
||||
export type CssVariant = "standard";
|
||||
/**
|
||||
* The style of GraphQL contained in the file.
|
||||
*/
|
||||
export type GraphqlVariant = "standard";
|
||||
export type HtmlVariant = "Standard" | "Astro";
|
||||
export type GritVariant = "Standard";
|
||||
export interface ChangeFileParams {
|
||||
content: string;
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
version: number;
|
||||
}
|
||||
export interface CloseFileParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetSyntaxTreeParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetSyntaxTreeResult {
|
||||
ast: string;
|
||||
cst: string;
|
||||
}
|
||||
export interface CheckFileSizeParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface CheckFileSizeResult {
|
||||
fileSize: number;
|
||||
limit: number;
|
||||
}
|
||||
export interface GetFileContentParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetControlFlowGraphParams {
|
||||
cursor: TextSize;
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface GetFormatterIRParams {
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
}
|
||||
export interface PullDiagnosticsParams {
|
||||
categories: RuleCategories;
|
||||
/**
|
||||
* Rules to apply on top of the configuration
|
||||
*/
|
||||
enabledRules?: RuleCode[];
|
||||
maxDiagnostics: number;
|
||||
only?: RuleCode[];
|
||||
path: BiomePath;
|
||||
projectKey: ProjectKey;
|
||||
skip?: RuleCode[];
|
||||
}
|
||||
export type RuleCategories = RuleCategory[];
|
||||
export type RuleCode = string;
|
||||
export type RuleCategory = "syntax" | "lint" | "action" | "transformation";
|
||||
export interface PullDiagnosticsResult {
|
||||
diagnostics: Diagnostic[];
|
||||
errors: number;
|
||||
skippedDiagnostics: number;
|
||||
}
|
||||
export interface PullActionsParams {
|
||||
enabledRules?: RuleCode[];
|
||||
only?: RuleCode[];
|
||||
|
|
@ -3846,7 +3849,7 @@ export type RuleDomain = "react" | "test" | "solid" | "next";
|
|||
export type RuleDomainValue = "all" | "none" | "recommended";
|
||||
export interface Workspace {
|
||||
fileFeatures(params: SupportsFeatureParams): Promise<FileFeaturesResult>;
|
||||
updateSettings(params: UpdateSettingsParams): Promise<void>;
|
||||
updateSettings(params: UpdateSettingsParams): Promise<UpdateSettingsResult>;
|
||||
openProject(params: OpenProjectParams): Promise<ProjectKey>;
|
||||
openFile(params: OpenFileParams): Promise<void>;
|
||||
changeFile(params: ChangeFileParams): Promise<void>;
|
||||
|
|
|
|||
|
|
@ -198,7 +198,7 @@ impl Analyze {
|
|||
root,
|
||||
filter,
|
||||
&options,
|
||||
Vec::new(),
|
||||
&[],
|
||||
Default::default(),
|
||||
|event| {
|
||||
black_box(event.diagnostic());
|
||||
|
|
@ -216,7 +216,7 @@ impl Analyze {
|
|||
..AnalysisFilter::default()
|
||||
};
|
||||
let options = AnalyzerOptions::default();
|
||||
biome_css_analyze::analyze(root, filter, &options, Vec::new(), |event| {
|
||||
biome_css_analyze::analyze(root, filter, &options, &[], |event| {
|
||||
black_box(event.diagnostic());
|
||||
black_box(event.actions());
|
||||
ControlFlow::<Never>::Continue(())
|
||||
|
|
|
|||
|
|
@ -430,7 +430,7 @@ fn assert_lint(
|
|||
let services =
|
||||
JsAnalyzerServices::from((Default::default(), Default::default(), file_source));
|
||||
|
||||
biome_js_analyze::analyze(&root, filter, &options, vec![], services, |signal| {
|
||||
biome_js_analyze::analyze(&root, filter, &options, &[], services, |signal| {
|
||||
if let Some(mut diag) = signal.diagnostic() {
|
||||
for action in signal.actions() {
|
||||
if !action.is_suppression() {
|
||||
|
|
@ -522,7 +522,7 @@ fn assert_lint(
|
|||
test,
|
||||
);
|
||||
|
||||
biome_css_analyze::analyze(&root, filter, &options, Vec::new(), |signal| {
|
||||
biome_css_analyze::analyze(&root, filter, &options, &[], |signal| {
|
||||
if let Some(mut diag) = signal.diagnostic() {
|
||||
for action in signal.actions() {
|
||||
if !action.is_suppression() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue