Skip to content

Emit config diagnostics#2289

Open
sgavriil01 wants to merge 4 commits into
facebook:mainfrom
sgavriil01:emit-config-diagnostics
Open

Emit config diagnostics#2289
sgavriil01 wants to merge 4 commits into
facebook:mainfrom
sgavriil01:emit-config-diagnostics

Conversation

@sgavriil01

Copy link
Copy Markdown
Contributor

Summary

This PR adds LSP diagnostics for config file (pyrefly.toml/pyproject.toml) parsing errors. When a config file has invalid syntax, diagnostics now appear in VS Code's Problems panel and clear automatically when the file is fixed.

Changes:

  • Modified ConfigError to include file path information
  • Fixed bug in workspace.rs where config errors were being discarded
  • Added publish_config_diagnostics() to emit LSP diagnostics for config files
  • Added caching to ensure diagnostics persist across publish cycles

Fixes #2078

Test Plan

  • Added integration test test_config_file_diagnostics() that creates invalid TOML and verifies proper handling
  • Manually tested by creating syntax errors in pyrefly.toml - diagnostics appear in Problems panel with correct line/column positions
  • Verified diagnostics clear when config is fixed
  • Ran ./test.py --no-test --no-conformance - formatting and linting pass
@meta-cla meta-cla Bot added the cla signed label Feb 2, 2026
Emit diagnostics for config file parse errors (facebook#2078)

Config errors (pyrefly.toml/pyproject.toml) now appear as LSP diagnostics
in the Problems panel and clear when fixed.
@sgavriil01 sgavriil01 force-pushed the emit-config-diagnostics branch from c487ef4 to 5393991 Compare February 2, 2026 14:55
@github-actions

This comment has been minimized.

@connernilsen connernilsen self-assigned this Feb 4, 2026
@connernilsen connernilsen self-requested a review February 4, 2026 19:50
@connernilsen connernilsen added configuration language-server Issues specific to our IDE integration rather than type checking and removed needs-triage labels Feb 6, 2026
@connernilsen

Copy link
Copy Markdown
Contributor

Hey @sgavriil01, thanks for making this! It seems like something we definitely need. I'll review early next week

@connernilsen connernilsen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hey, thanks for doing this! It's looking great so far and I love the motivation. I left a few comments, mostly small things, but I do think there needs to be a different approach for how we figure out which files to update for, which I left in the else branch when we're getting the diagnostics to publish.

Let me know if you have any questions or disagree, I'm totally down to discuss things or give more thoughts if you want :)

Comment thread crates/pyrefly_config/src/finder.rs Outdated
Comment thread pyrefly/lib/lsp/non_wasm/server.rs Outdated
Comment thread pyrefly/lib/lsp/non_wasm/server.rs Outdated
});
*self.cached_config_errors.lock() = config_errors.clone();
} else {
// If get_config_errors() returned empty (consumed by mem::take),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Those errors might be empty because all errors have been fixed. If that's the case, will we not end up clearing diagnostics here?

What we might need to do instead to make sure this is working is to keep a map in the config loader of all config paths to errors that have been loaded since the last time they were taken. Then we can use that to determine if a config has been changed since the last time we published errors. If it's not in the map, then we don't update diagnostics from the previous result. If it is, then completely overwrite with new errors.

Comment thread pyrefly/lib/lsp/non_wasm/server.rs Outdated
Comment thread pyrefly/lib/lsp/non_wasm/server.rs Outdated
…efactor ConfigError API

- Replace std::collections::HashSet with SmallSet for deduplication
- Add source: DiagnosticSource parameter to publish_config_diagnostics
- Consolidate ConfigError::error/warn methods to accept Option<PathBuf>
- Update all callsites to use the new unified API
- Add span field to ConfigError for line/column information
- Extract span from toml::de::Error in from_file()
- Update config_error_to_diagnostic to use ConfigError.span()
- Remove extract_line_col_from_toml_error() regex function
- Add error_with_span() constructor for TOML errors
@sgavriil01

Copy link
Copy Markdown
Contributor Author

Thanks for the review! I've addressed the first 4 points:

  1. Using SmallSet for deduplication
  2. Passing DiagnosticSource as a parameter instead of hardcoding it
  3. Simplified the ConfigError API
  4. Using TOML's built-in error information instead of regex parsing

For point 5 about the caching strategy - could you clarify the intended behavior? Should the ConfigFinder track which config files were recently loaded and only publish diagnostics for those, rather than maintaining a separate cache in the Server?

@github-actions

Copy link
Copy Markdown

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity for more than 2 weeks.

If you are still working on this this pull request, please add a comment or push new commits to keep it active. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

@github-actions github-actions Bot added the stale label Feb 26, 2026
@connernilsen

Copy link
Copy Markdown
Contributor

Hey @sgavriil01, sorry for the long delay. I've had to shift my focus internally for a bit to focus on a bug that's been causing a lot of problems. I think it should be resolved soon, so I'll come back and check out your changes here hopefully this week :)

@github-actions github-actions Bot removed the stale label Mar 11, 2026

@connernilsen connernilsen left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Alright, thanks for making the previous changes. And sorry about the wait. I was pulled into a few internal issues over the past few weeks that took up all of my time.

For point 5 about the caching strategy - could you clarify the intended behavior? Should the ConfigFinder track which config files were recently loaded and only publish diagnostics for those, rather than maintaining a separate cache in the Server?

What I'm thinking is, we have our ConfigFinder implementation that handles loading and caching configs. There are times where a config is re-loaded or we clear the config finder's cache, but it kind of knows the state of all configs at all times, so let's use that as our source of truth.

Your changes above removing anyhow::Error from ConfigError make it so we can add #[derive(Clone)] to ConfigError, so now we don't need to take errors from the config finder, we can just clone them when needed. Those errors will also automatically be cleared when they're not longer useful, so we can just:

  1. clear all config errors that currently exist
  2. clone all errors from the config finder
  3. process the errors to turn them into diagnostics
  4. push those diagnostics to the IDE

Then all we need to do is make sure we know which configs we've pushed diagnostics to, and for that, we can continue to use the config_files_with_diagnostics you created, but not have to manage complicated state in the server with cached_config_errors.

Sorry, I changed a few details from my first comment mentioning the caching changes, so let me know if this makes more sense.

Comment on lines +1224 to +1237
let error_str = toml_err.to_string();
let line = error_str
.split("line ")
.nth(1)
.and_then(|s| s.split(&[',', ' ', '\n'][..]).next())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(1);
let column = error_str
.split("column ")
.nth(1)
.and_then(|s| s.split(&[',', ' ', '\n'][..]).next())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(1);
(line, column)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instead of trying to parse this, can we convert the span to a line, col pair? You can probably do something like that with

Comment on lines +1221 to +1237
toml_err.span().map(|_span| {
// The span is in bytes, but toml errors also expose line/column in their Display
// For now, we parse the display string which includes "at line X, column Y"
let error_str = toml_err.to_string();
let line = error_str
.split("line ")
.nth(1)
.and_then(|s| s.split(&[',', ' ', '\n'][..]).next())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(1);
let column = error_str
.split("column ")
.nth(1)
.and_then(|s| s.split(&[',', ' ', '\n'][..]).next())
.and_then(|s| s.parse::<usize>().ok())
.unwrap_or(1);
(line, column)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Instead of trying to parse this, can we convert the span to a line, col pair? You can probably do something like this

  use ruff_source_file::LineIndex;

  let index = LineIndex::from_source_text(source);
  let line_col = index.line_column(text_size_offset, source);
  // line_col.line and line_col.column are both 0-indexed
@github-actions

github-actions Bot commented Apr 2, 2026

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity for more than 2 weeks.

If you are still working on this this pull request, please add a comment or push new commits to keep it active. Otherwise, please unassign yourself and allow someone else to take over.

Thank you for your contributions!

@github-actions github-actions Bot added the stale label Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla signed configuration language-server Issues specific to our IDE integration rather than type checking stale

2 participants