Skip to content
Closed
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
21 changes: 21 additions & 0 deletions crates/pyrefly_config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,11 @@ pub struct ConfigFile {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub typeshed_path: Option<PathBuf>,

/// Path to baseline file for comparing type errors.
/// Errors matching the baseline are suppressed.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub baseline: Option<PathBuf>,

/// Pyrefly's configurations around interpreter querying/finding.
#[serde(flatten)]
pub interpreters: Interpreters,
Expand Down Expand Up @@ -542,6 +547,7 @@ impl Default for ConfigFile {
source_db: Default::default(),
use_ignore_files: true,
typeshed_path: None,
baseline: None,
skip_lsp_config_indexing: false,
}
}
Expand Down Expand Up @@ -1175,6 +1181,9 @@ impl ConfigFile {
if let Some(typeshed_path) = &self.typeshed_path {
self.typeshed_path = Some(typeshed_path.absolutize_from(config_root));
}
if let Some(baseline) = &self.baseline {
self.baseline = Some(baseline.absolutize_from(config_root));
}
self.python_environment
.site_package_path
.iter_mut()
Expand Down Expand Up @@ -1440,6 +1449,7 @@ mod tests {
}
}],
typeshed_path: None,
baseline: None,
skip_lsp_config_indexing: false,
}
);
Expand Down Expand Up @@ -1672,6 +1682,7 @@ mod tests {
settings: Default::default(),
}],
typeshed_path: Some(PathBuf::from(typeshed)),
baseline: Some(PathBuf::from("baseline.json")),
skip_lsp_config_indexing: false,
};

Expand Down Expand Up @@ -1730,6 +1741,7 @@ mod tests {
settings: Default::default(),
}],
typeshed_path: Some(expected_typeshed),
baseline: Some(test_path.join("baseline.json")),
skip_lsp_config_indexing: false,
};
assert_eq!(config, expected_config);
Expand Down Expand Up @@ -1757,6 +1769,15 @@ mod tests {
assert!(err.to_string().contains("missing field `matches`"));
}

#[test]
fn test_baseline_config_parsing() {
let config_str = r#"
baseline = "baseline.json"
"#;
let config = ConfigFile::parse_config(config_str).unwrap();
assert_eq!(config.baseline, Some(PathBuf::from("baseline.json")));
}

#[test]
fn test_expect_all_fields_set_in_root_config() {
let root = TempDir::new().unwrap();
Expand Down
42 changes: 36 additions & 6 deletions pyrefly/lib/commands/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ impl Timings {

impl CheckArgs {
pub fn run_once(
self,
mut self,
files_to_check: Box<dyn Includes>,
config_finder: ConfigFinder,
) -> anyhow::Result<(CommandExitStatus, Vec<Error>)> {
Expand All @@ -677,6 +677,18 @@ impl CheckArgs {
true,
);
let (loaded_handles, _, sourcedb_errors) = handles.all(holder.as_ref().config_finder());

// If CLI doesn't provide baseline, get from config
if self.output.baseline.is_none() {
if let Some(handle) = loaded_handles.first() {
let config = holder.as_ref().config_finder().python_file(
ModuleNameWithKind::guaranteed(handle.module()),
handle.path(),
);
self.output.baseline = config.baseline.clone();
}
}

self.run_inner(
timings,
transaction.as_mut(),
Expand All @@ -687,7 +699,7 @@ impl CheckArgs {
}

pub fn run_once_with_snippet(
self,
mut self,
code: String,
config_finder: ConfigFinder,
) -> anyhow::Result<(CommandExitStatus, Vec<Error>)> {
Expand All @@ -699,13 +711,18 @@ impl CheckArgs {
let holder = Forgetter::new(State::new(config_finder), true);

// Create a single handle for the virtual module
let sys_info = holder
let config = holder
.as_ref()
.config_finder()
.python_file(ModuleNameWithKind::guaranteed(module_name), &module_path)
.get_sys_info();
.python_file(ModuleNameWithKind::guaranteed(module_name), &module_path);
let sys_info = config.get_sys_info();
let handle = Handle::new(module_name, module_path.clone(), sys_info);

// If CLI doesn't provide baseline, get from config
if self.output.baseline.is_none() {
self.output.baseline = config.baseline.clone();
}

let require_levels = self.get_required_levels();
let mut transaction = Forgetter::new(
holder
Expand All @@ -730,7 +747,7 @@ impl CheckArgs {
}

pub async fn run_watch(
self,
mut self,
mut watcher: Watcher,
files_to_check: Box<dyn Includes>,
config_finder: ConfigFinder,
Expand All @@ -741,6 +758,19 @@ impl CheckArgs {
let require_levels = self.get_required_levels();
let mut handles = Handles::new(expanded_file_list);
let state = State::new(config_finder);

// If CLI doesn't provide baseline, get from config
if self.output.baseline.is_none() {
let (loaded_handles, _, _) = handles.all(state.config_finder());
if let Some(handle) = loaded_handles.first() {
let config = state.config_finder().python_file(
ModuleNameWithKind::guaranteed(handle.module()),
handle.path(),
);
self.output.baseline = config.baseline.clone();
}
}

let mut transaction = state.new_committable_transaction(require_levels.default, None);
loop {
let timings = Timings::new();
Expand Down
19 changes: 17 additions & 2 deletions website/docs/error-suppressions.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,29 @@ To generate (or re-generate) the baseline file:
pyrefly check --baseline="<path to baseline file>" --update-baseline
```

To check your project using a baseline file and report only newly-introduced errors:
To check your project using a baseline file and report only newly-introduced errors, you can either use the CLI flag:

```
pyrefly check --baseline="<path to baseline file>"
```

Or specify the baseline in your configuration file (`pyrefly.toml` or `pyproject.toml`):

```toml
# pyrefly.toml
baseline = "baseline.json"
```

```toml
# pyproject.toml
[tool.pyrefly]
baseline = "baseline.json"
```

When the baseline is specified in the configuration file, you don't need to pass the `--baseline` flag on every invocation. The CLI flag takes precedence if both are specified.

Errors are matched with the baseline by looking at file, error code, and column number.
Right now, errors suppressed by the baseline file are still shown in the IDE and the baseline file can only be passed as a command line argument, but both are expected to change in the future.
Note that errors suppressed by the baseline file are still shown in the IDE.
This feature is experimental, so please submit any feedback or requests you have on our Github repo.

## Upgrading Pyrefly (And other changes that introduce new type errors)
Expand Down
Loading