Skip to content

feat: workspace support #408

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

Merged
merged 31 commits into from
Jun 1, 2025
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
14 changes: 14 additions & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ enumflags2 = "0.7.11"
ignore = "0.4.23"
indexmap = { version = "2.6.0", features = ["serde"] }
insta = "1.31.0"
oxc_resolver = "1.12.0"
pg_query = "6.1.0"
proc-macro2 = "1.0.66"
quote = "1.0.33"
Expand All @@ -38,6 +39,7 @@ schemars = { version = "0.8.22", features = ["indexmap2", "small
serde = "1.0.195"
serde_json = "1.0.114"
similar = "2.6.0"
slotmap = "1.0.7"
smallvec = { version = "1.13.2", features = ["union", "const_new", "serde"] }
strum = { version = "0.27.1", features = ["derive"] }
# this will use tokio if available, otherwise async-std
Expand Down
8 changes: 6 additions & 2 deletions crates/pgt_cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ use bpaf::Bpaf;
use pgt_configuration::{PartialConfiguration, partial_configuration};
use pgt_console::Console;
use pgt_fs::FileSystem;
use pgt_workspace::PartialConfigurationExt;
use pgt_workspace::configuration::{LoadedConfiguration, load_configuration};
use pgt_workspace::settings::PartialConfigurationExt;
use pgt_workspace::workspace::UpdateSettingsParams;
use pgt_workspace::workspace::{RegisterProjectFolderParams, UpdateSettingsParams};
use pgt_workspace::{DynRef, Workspace, WorkspaceError};
use std::ffi::OsString;
use std::path::PathBuf;
Expand Down Expand Up @@ -301,6 +301,10 @@ pub(crate) trait CommandRunner: Sized {
let (vcs_base_path, gitignore_matches) =
configuration.retrieve_gitignore_matches(fs, vcs_base_path.as_deref())?;
let paths = self.get_files_to_process(fs, &configuration)?;
workspace.register_project_folder(RegisterProjectFolderParams {
path: fs.working_directory(),
set_as_current_workspace: true,
})?;

workspace.update_settings(UpdateSettingsParams {
workspace_directory: fs.working_directory(),
Expand Down
2 changes: 1 addition & 1 deletion crates/pgt_cli/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ mod test {
fn termination_diagnostic_size() {
assert_eq!(
std::mem::size_of::<CliDiagnostic>(),
80,
96,
"you successfully decreased the size of the diagnostic!"
)
}
Expand Down
1 change: 1 addition & 0 deletions crates/pgt_configuration/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ biome_deserialize = { workspace = true, features = ["schema"] }
biome_deserialize_macros = { workspace = true }
bpaf = { workspace = true }
indexmap = { workspace = true }
oxc_resolver = { workspace = true }
pgt_analyse = { workspace = true }
pgt_analyser = { workspace = true }
pgt_console = { workspace = true }
Expand Down
53 changes: 53 additions & 0 deletions crates/pgt_configuration/src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use pgt_console::fmt::Display;
use pgt_console::{MarkupBuf, markup};
use pgt_diagnostics::adapters::ResolveError;

use pgt_diagnostics::{Advices, Diagnostic, Error, LogCategory, MessageAndDescription, Visit};
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Formatter};
Expand All @@ -21,6 +23,12 @@ pub enum ConfigurationDiagnostic {

/// Thrown when the pattern inside the `ignore` field errors
InvalidIgnorePattern(InvalidIgnorePattern),

/// Thrown when there's something wrong with the files specified inside `"extends"`
CantLoadExtendFile(CantLoadExtendFile),

/// Thrown when a configuration file can't be resolved from `node_modules`
CantResolve(CantResolve),
}

impl ConfigurationDiagnostic {
Expand Down Expand Up @@ -72,6 +80,18 @@ impl ConfigurationDiagnostic {
message: MessageAndDescription::from(markup! {{message}}.to_owned()),
})
}

pub fn cant_resolve(path: impl Display, source: oxc_resolver::ResolveError) -> Self {
Self::CantResolve(CantResolve {
message: MessageAndDescription::from(
markup! {
"Failed to resolve the configuration from "{{path}}
}
.to_owned(),
),
source: Some(Error::from(ResolveError::from(source))),
})
}
}

impl Debug for ConfigurationDiagnostic {
Expand Down Expand Up @@ -168,3 +188,36 @@ pub struct CantResolve {
#[source]
source: Option<Error>,
}

#[derive(Debug, Serialize, Deserialize, Diagnostic)]
#[diagnostic(
category = "configuration",
severity = Error,
)]
pub struct CantLoadExtendFile {
#[location(resource)]
file_path: String,
#[message]
#[description]
message: MessageAndDescription,

#[verbose_advice]
verbose_advice: ConfigurationAdvices,
}

impl CantLoadExtendFile {
pub fn new(file_path: impl Into<String>, message: impl Display) -> Self {
Self {
file_path: file_path.into(),
message: MessageAndDescription::from(markup! {{message}}.to_owned()),
verbose_advice: ConfigurationAdvices::default(),
}
}

pub fn with_verbose_advice(mut self, messsage: impl Display) -> Self {
self.verbose_advice
.messages
.push(markup! {{messsage}}.to_owned());
self
}
}
6 changes: 6 additions & 0 deletions crates/pgt_configuration/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub use analyser::{
RulePlainConfiguration, RuleSelector, RuleWithFixOptions, RuleWithOptions, Rules,
partial_linter_configuration,
};
use biome_deserialize::StringSet;
use biome_deserialize_macros::{Merge, Partial};
use bpaf::Bpaf;
use database::{
Expand Down Expand Up @@ -50,6 +51,10 @@ pub struct Configuration {
#[partial(bpaf(hide))]
pub schema: String,

/// A list of paths to other JSON files, used to extends the current configuration.
#[partial(bpaf(hide))]
pub extends: StringSet,

/// The configuration of the VCS integration
#[partial(type, bpaf(external(partial_vcs_configuration), optional, hide_usage))]
pub vcs: VcsConfiguration,
Expand Down Expand Up @@ -85,6 +90,7 @@ impl PartialConfiguration {
pub fn init() -> Self {
Self {
schema: Some(format!("https://pgtools.dev/schemas/{VERSION}/schema.json")),
extends: Some(StringSet::default()),
files: Some(PartialFilesConfiguration {
ignore: Some(Default::default()),
..Default::default()
Expand Down
1 change: 1 addition & 0 deletions crates/pgt_diagnostics/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ version = "0.0.0"
backtrace = "0.3.74"
bpaf = { workspace = true }
enumflags2 = { workspace = true }
oxc_resolver = { workspace = true }
pgt_console = { workspace = true, features = ["serde"] }
pgt_diagnostics_categories = { workspace = true, features = ["serde"] }
pgt_diagnostics_macros = { workspace = true }
Expand Down
25 changes: 25 additions & 0 deletions crates/pgt_diagnostics/src/adapters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,28 @@ impl Diagnostic for SerdeJsonError {
fmt.write_markup(markup!({ AsConsoleDisplay(&self.error) }))
}
}

#[derive(Debug)]
pub struct ResolveError {
error: oxc_resolver::ResolveError,
}

impl From<oxc_resolver::ResolveError> for ResolveError {
fn from(error: oxc_resolver::ResolveError) -> Self {
Self { error }
}
}

impl Diagnostic for ResolveError {
fn category(&self) -> Option<&'static Category> {
Some(category!("internalError/io"))
}

fn description(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(fmt, "{}", self.error)
}

fn message(&self, fmt: &mut fmt::Formatter<'_>) -> io::Result<()> {
fmt.write_markup(markup!({ AsConsoleDisplay(&self.error) }))
}
}
1 change: 1 addition & 0 deletions crates/pgt_fs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ version = "0.0.0"
crossbeam = { workspace = true }
directories = "5.0.1"
enumflags2 = { workspace = true }
oxc_resolver = { workspace = true }
parking_lot = { version = "0.12.3", features = ["arc_lock"] }
pgt_diagnostics = { workspace = true }
rayon = { workspace = true }
Expand Down
15 changes: 15 additions & 0 deletions crates/pgt_fs/src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{PathInterner, PgTPath};
pub use memory::{ErrorEntry, MemoryFileSystem};
pub use os::OsFileSystem;
use oxc_resolver::{Resolution, ResolveError};
use pgt_diagnostics::{Advices, Diagnostic, LogCategory, Visit, console};
use pgt_diagnostics::{Error, Severity};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -164,6 +165,12 @@ pub trait FileSystem: Send + Sync + RefUnwindSafe {
fn get_changed_files(&self, base: &str) -> io::Result<Vec<String>>;

fn get_staged_files(&self) -> io::Result<Vec<String>>;

fn resolve_configuration(
&self,
specifier: &str,
path: &Path,
) -> Result<Resolution, ResolveError>;
}

/// Result of the auto search
Expand Down Expand Up @@ -355,6 +362,14 @@ where
fn get_staged_files(&self) -> io::Result<Vec<String>> {
T::get_staged_files(self)
}

fn resolve_configuration(
&self,
specifier: &str,
path: &Path,
) -> Result<Resolution, ResolveError> {
T::resolve_configuration(self, specifier, path)
}
}

#[derive(Debug, Diagnostic, Deserialize, Serialize)]
Expand Down
10 changes: 10 additions & 0 deletions crates/pgt_fs/src/fs/memory.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use oxc_resolver::{Resolution, ResolveError};
use rustc_hash::FxHashMap;
use std::collections::hash_map::{Entry, IntoIter};
use std::io;
Expand Down Expand Up @@ -227,6 +228,15 @@ impl FileSystem for MemoryFileSystem {

Ok(cb())
}

fn resolve_configuration(
&self,
_specifier: &str,
_path: &Path,
) -> Result<Resolution, ResolveError> {
// not needed for the memory file system
todo!()
}
}

struct MemoryFile {
Expand Down
23 changes: 21 additions & 2 deletions crates/pgt_fs/src/fs/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ use crate::{
FileSystem, PgTPath,
fs::{TraversalContext, TraversalScope},
};
use oxc_resolver::{Resolution, ResolveError, ResolveOptions, Resolver};
use pgt_diagnostics::{DiagnosticExt, Error, Severity, adapters::IoError};
use rayon::{Scope, scope};
use std::fs::{DirEntry, FileType};
use std::panic::AssertUnwindSafe;
use std::process::Command;
use std::{
env, fs,
Expand All @@ -21,12 +23,18 @@ const MAX_SYMLINK_DEPTH: u8 = 3;
/// Implementation of [FileSystem] that directly calls through to the underlying OS
pub struct OsFileSystem {
pub working_directory: Option<PathBuf>,
pub configuration_resolver: AssertUnwindSafe<Resolver>,
}

impl OsFileSystem {
pub fn new(working_directory: PathBuf) -> Self {
Self {
working_directory: Some(working_directory),
configuration_resolver: AssertUnwindSafe(Resolver::new(ResolveOptions {
condition_names: vec!["node".to_string(), "import".to_string()],
extensions: vec![".json".to_string(), ".jsonc".to_string()],
..ResolveOptions::default()
})),
}
}
}
Expand All @@ -35,6 +43,11 @@ impl Default for OsFileSystem {
fn default() -> Self {
Self {
working_directory: env::current_dir().ok(),
configuration_resolver: AssertUnwindSafe(Resolver::new(ResolveOptions {
condition_names: vec!["node".to_string(), "import".to_string()],
extensions: vec![".json".to_string(), ".jsonc".to_string()],
..ResolveOptions::default()
})),
}
}
}
Expand Down Expand Up @@ -116,6 +129,14 @@ impl FileSystem for OsFileSystem {
.map(|l| l.to_string())
.collect())
}

fn resolve_configuration(
&self,
specifier: &str,
path: &Path,
) -> Result<Resolution, ResolveError> {
self.configuration_resolver.resolve(path, specifier)
}
}

struct OsFile {
Expand Down Expand Up @@ -387,8 +408,6 @@ fn follow_symlink(
path: &Path,
ctx: &dyn TraversalContext,
) -> Result<(PathBuf, FileType), SymlinkExpansionError> {
tracing::info!("Translating symlink: {path:?}");

let target_path = fs::read_link(path).map_err(|err| {
ctx.push_diagnostic(IoError::from(err).with_file_path(path.to_string_lossy().to_string()));
SymlinkExpansionError
Expand Down
Loading