[ty] Add respect-type-ignore-comments configuration option (#22137)

This commit is contained in:
Micha Reiser
2025-12-23 08:36:51 +01:00
committed by GitHub
parent 4745d15fff
commit d6a7c9b4ed
19 changed files with 323 additions and 14 deletions

View File

@@ -10,8 +10,8 @@ use ruff_python_ast::PythonVersion;
use ty_module_resolver::{SearchPathSettings, SearchPaths};
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{
Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, SysPrefixPathOrigin, default_lint_registry,
AnalysisSettings, Db, Program, ProgramSettings, PythonEnvironment, PythonPlatform,
PythonVersionSource, PythonVersionWithSource, SysPrefixPathOrigin, default_lint_registry,
};
static EMPTY_VENDORED: std::sync::LazyLock<VendoredFileSystem> = std::sync::LazyLock::new(|| {
@@ -27,6 +27,7 @@ pub struct ModuleDb {
files: Files,
system: OsSystem,
rule_selection: Arc<RuleSelection>,
analysis_settings: Arc<AnalysisSettings>,
}
impl ModuleDb {
@@ -110,6 +111,10 @@ impl Db for ModuleDb {
fn verbose(&self) -> bool {
false
}
fn analysis_settings(&self) -> &AnalysisSettings {
&self.analysis_settings
}
}
#[salsa::db]

View File

@@ -28,6 +28,34 @@ division-by-zero = "ignore"
---
## `analysis`
### `respect-type-ignore-comments`
Whether ty should respect `type: ignore` comments.
When set to `false`, `type: ignore` comments are treated like any other normal
comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).
Setting this option can be useful when using ty alongside other type checkers or when
you prefer using `ty: ignore` over `type: ignore`.
Defaults to `true`.
**Default value**: `true`
**Type**: `bool`
**Example usage**:
```toml title="pyproject.toml"
[tool.ty.analysis]
# Disable support for `type: ignore` comments
respect-type-ignore-comments = false
```
---
## `environment`
### `extra-paths`

View File

@@ -0,0 +1,43 @@
use insta_cmd::assert_cmd_snapshot;
use crate::CliTest;
/// ty ignores `type: ignore` comments when setting `respect-type-ignore-comments=false`
#[test]
fn respect_type_ignore_comments_is_turned_off() -> anyhow::Result<()> {
let case = CliTest::with_file(
"test.py",
r#"
y = a + 5 # type: ignore
"#,
)?;
// Assert that there's an `unresolved-reference` diagnostic (error).
assert_cmd_snapshot!(case.command(), @r"
success: true
exit_code: 0
----- stdout -----
All checks passed!
----- stderr -----
");
assert_cmd_snapshot!(case.command().arg("--config").arg("analysis.respect-type-ignore-comments=false"), @r"
success: false
exit_code: 1
----- stdout -----
error[unresolved-reference]: Name `a` used when not defined
--> test.py:2:5
|
2 | y = a + 5 # type: ignore
| ^
|
info: rule `unresolved-reference` is enabled by default
Found 1 diagnostic
----- stderr -----
");
Ok(())
}

View File

@@ -133,7 +133,7 @@ fn cli_config_args_invalid_option() -> anyhow::Result<()> {
|
1 | bad-option=true
| ^^^^^^^^^^
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `overrides`
unknown field `bad-option`, expected one of `environment`, `src`, `rules`, `terminal`, `analysis`, `overrides`
Usage: ty <COMMAND>

View File

@@ -1,3 +1,4 @@
mod analysis_options;
mod config_option;
mod exit_code;
mod file_selection;

View File

@@ -16,7 +16,7 @@ use ruff_db::vendored::VendoredFileSystem;
use salsa::{Database, Event, Setter};
use ty_module_resolver::SearchPaths;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{Db as SemanticDb, Program};
use ty_python_semantic::{AnalysisSettings, Db as SemanticDb, Program};
mod changes;
@@ -470,6 +470,10 @@ impl SemanticDb for ProjectDatabase {
ty_python_semantic::default_lint_registry()
}
fn analysis_settings(&self) -> &AnalysisSettings {
self.project().settings(self).analysis()
}
fn verbose(&self) -> bool {
self.project().verbose(self)
}
@@ -533,7 +537,9 @@ pub(crate) mod tests {
use ruff_db::vendored::VendoredFileSystem;
use ty_module_resolver::SearchPathSettings;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{Program, ProgramSettings, PythonPlatform, PythonVersionWithSource};
use ty_python_semantic::{
AnalysisSettings, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource,
};
use crate::db::Db;
use crate::{Project, ProjectMetadata};
@@ -655,6 +661,10 @@ pub(crate) mod tests {
ty_python_semantic::default_lint_registry()
}
fn analysis_settings(&self) -> &AnalysisSettings {
self.project().settings(self).analysis()
}
fn verbose(&self) -> bool {
false
}

View File

@@ -31,7 +31,7 @@ use ty_combine::Combine;
use ty_module_resolver::{SearchPathSettings, SearchPathSettingsError, SearchPaths};
use ty_python_semantic::lint::{Level, LintSource, RuleSelection};
use ty_python_semantic::{
MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform,
AnalysisSettings, MisconfigurationMode, ProgramSettings, PythonEnvironment, PythonPlatform,
PythonVersionFileSource, PythonVersionSource, PythonVersionWithSource, SitePackagesPaths,
SysPrefixPathOrigin,
};
@@ -87,6 +87,10 @@ pub struct Options {
#[option_group]
pub terminal: Option<TerminalOptions>,
#[serde(skip_serializing_if = "Option::is_none")]
#[option_group]
pub analysis: Option<AnalysisOptions>,
/// Override configurations for specific file patterns.
///
/// Each override specifies include/exclude patterns and rule configurations
@@ -435,6 +439,8 @@ impl Options {
color: colored::control::SHOULD_COLORIZE.should_colorize(),
})?;
let analysis = self.analysis.or_default().to_settings();
let overrides = self
.to_overrides_settings(db, project_root, &mut diagnostics)
.map_err(|err| ToSettingsError {
@@ -447,6 +453,7 @@ impl Options {
rules: Arc::new(rules),
terminal,
src,
analysis,
overrides,
};
@@ -1254,6 +1261,55 @@ pub struct TerminalOptions {
pub error_on_warning: Option<bool>,
}
#[derive(
Debug,
Default,
Clone,
Eq,
PartialEq,
Combine,
Serialize,
Deserialize,
OptionsMetadata,
get_size2::GetSize,
)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct AnalysisOptions {
/// Whether ty should respect `type: ignore` comments.
///
/// When set to `false`, `type: ignore` comments are treated like any other normal
/// comment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).
///
/// Setting this option can be useful when using ty alongside other type checkers or when
/// you prefer using `ty: ignore` over `type: ignore`.
///
/// Defaults to `true`.
#[option(
default = r#"true"#,
value_type = "bool",
example = r#"
# Disable support for `type: ignore` comments
respect-type-ignore-comments = false
"#
)]
respect_type_ignore_comments: Option<bool>,
}
impl AnalysisOptions {
fn to_settings(&self) -> AnalysisSettings {
let AnalysisSettings {
respect_type_ignore_comments: respect_type_ignore_default,
} = AnalysisSettings::default();
AnalysisSettings {
respect_type_ignore_comments: self
.respect_type_ignore_comments
.unwrap_or(respect_type_ignore_default),
}
}
}
/// Configuration override that applies to specific files based on glob patterns.
///
/// An override allows you to apply different rule configurations to specific

View File

@@ -2,6 +2,7 @@ use std::sync::Arc;
use ruff_db::files::File;
use ty_combine::Combine;
use ty_python_semantic::AnalysisSettings;
use ty_python_semantic::lint::RuleSelection;
use crate::metadata::options::{InnerOverrideOptions, OutputFormat};
@@ -25,6 +26,7 @@ pub struct Settings {
pub(super) rules: Arc<RuleSelection>,
pub(super) terminal: TerminalSettings,
pub(super) src: SrcSettings,
pub(super) analysis: AnalysisSettings,
/// Settings for configuration overrides that apply to specific file patterns.
///
@@ -54,6 +56,10 @@ impl Settings {
pub fn overrides(&self) -> &[Override] {
&self.overrides
}
pub fn analysis(&self) -> &AnalysisSettings {
&self.analysis
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default, get_size2::GetSize)]

View File

@@ -213,3 +213,31 @@ including module docstrings.
a = 10 / 0 # error: [division-by-zero]
b = a / 0 # error: [division-by-zero]
```
## `respect-type-ignore-comments=false`
ty ignore `type-ignore` comments if `respect-type-ignore-comments` is set to false.
```toml
[analysis]
respect-type-ignore-comments = false
```
`type: ignore` comments can't be used to suppress an error:
```py
# error: [unresolved-reference]
a = b + 10 # type: ignore
```
ty doesn't report or remove unused `type: ignore` comments:
```py
a = 10 + 5 # type: ignore
```
ty doesn't report invalid `type: ignore` comments:
```py
a = 10 + 4 # type: ignoreee
```

View File

@@ -1,3 +1,4 @@
use crate::AnalysisSettings;
use crate::lint::{LintRegistry, RuleSelection};
use ruff_db::files::File;
use ty_module_resolver::Db as ModuleResolverDb;
@@ -13,6 +14,8 @@ pub trait Db: ModuleResolverDb {
fn lint_registry(&self) -> &LintRegistry;
fn analysis_settings(&self) -> &AnalysisSettings;
/// Whether ty is running with logging verbosity INFO or higher (`-v` or more).
fn verbose(&self) -> bool;
}
@@ -23,8 +26,8 @@ pub(crate) mod tests {
use crate::program::Program;
use crate::{
ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
default_lint_registry,
AnalysisSettings, ProgramSettings, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, default_lint_registry,
};
use ty_module_resolver::SearchPathSettings;
@@ -52,6 +55,7 @@ pub(crate) mod tests {
vendored: VendoredFileSystem,
events: Events,
rule_selection: Arc<RuleSelection>,
analysis_settings: Arc<AnalysisSettings>,
}
impl TestDb {
@@ -71,6 +75,7 @@ pub(crate) mod tests {
events,
files: Files::default(),
rule_selection: Arc::new(RuleSelection::from_registry(default_lint_registry())),
analysis_settings: AnalysisSettings::default().into(),
}
}
@@ -133,6 +138,10 @@ pub(crate) mod tests {
default_lint_registry()
}
fn analysis_settings(&self) -> &AnalysisSettings {
&self.analysis_settings
}
fn verbose(&self) -> bool {
false
}

View File

@@ -73,3 +73,23 @@ pub fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&IGNORE_COMMENT_UNKNOWN_RULE);
registry.register_lint(&INVALID_IGNORE_COMMENT);
}
#[derive(Debug, Clone, PartialEq, Eq, get_size2::GetSize)]
pub struct AnalysisSettings {
/// Whether errors can be suppressed with `type: ignore` comments.
///
/// If set to false, ty won't:
///
/// * allow suppressing errors with `type: ignore` comments
/// * report unused `type: ignore` comments
/// * report invalid `type: ignore` comments
pub respect_type_ignore_comments: bool,
}
impl Default for AnalysisSettings {
fn default() -> Self {
Self {
respect_type_ignore_comments: true,
}
}
}

View File

@@ -101,6 +101,8 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions {
let parsed = parsed_module(db, file).load(db);
let source = source_text(db, file);
let respect_type_ignore = db.analysis_settings().respect_type_ignore_comments;
let mut builder = SuppressionsBuilder::new(&source, db.lint_registry());
let mut line_start = TextSize::default();
@@ -116,6 +118,9 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions {
for comment in parser {
match comment {
Ok(comment) => {
if comment.kind().is_type_ignore() && !respect_type_ignore {
continue;
}
builder.add_comment(comment, TextRange::new(line_start, token.end()));
}
Err(error) => match error.kind {
@@ -127,6 +132,10 @@ pub(crate) fn suppressions(db: &dyn Db, file: File) -> Suppressions {
| ParseErrorKind::CodesMissingComma(kind)
| ParseErrorKind::InvalidCode(kind)
| ParseErrorKind::CodesMissingClosingBracket(kind) => {
if kind.is_type_ignore() && !respect_type_ignore {
continue;
}
builder.add_invalid_comment(kind, error);
}
},

View File

@@ -1,3 +1,5 @@
use std::sync::Arc;
use anyhow::{Context, anyhow};
use ruff_db::Db;
use ruff_db::files::{File, Files, system_path_to_file};
@@ -9,8 +11,8 @@ use ty_module_resolver::SearchPathSettings;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::pull_types::pull_types;
use ty_python_semantic::{
Program, ProgramSettings, PythonPlatform, PythonVersionSource, PythonVersionWithSource,
default_lint_registry,
AnalysisSettings, Program, ProgramSettings, PythonPlatform, PythonVersionSource,
PythonVersionWithSource, default_lint_registry,
};
use test_case::test_case;
@@ -179,6 +181,7 @@ pub struct CorpusDb {
rule_selection: RuleSelection,
system: TestSystem,
vendored: VendoredFileSystem,
analysis_settings: Arc<AnalysisSettings>,
}
impl CorpusDb {
@@ -190,6 +193,7 @@ impl CorpusDb {
vendored: ty_vendored::file_system().clone(),
rule_selection: RuleSelection::from_registry(default_lint_registry()),
files: Files::default(),
analysis_settings: Arc::new(AnalysisSettings::default()),
};
Program::from_settings(
@@ -263,6 +267,10 @@ impl ty_python_semantic::Db for CorpusDb {
fn verbose(&self) -> bool {
false
}
fn analysis_settings(&self) -> &AnalysisSettings {
&self.analysis_settings
}
}
#[salsa::db]

View File

@@ -150,6 +150,9 @@ Settings: Settings {
},
},
},
analysis: AnalysisSettings {
respect_type_ignore_comments: true,
},
overrides: [],
}

View File

@@ -25,6 +25,8 @@ pub(crate) struct MarkdownTestConfig {
pub(crate) log: Option<Log>,
pub(crate) analysis: Option<Analysis>,
/// The [`ruff_db::system::System`] to use for tests.
///
/// Defaults to the case-sensitive [`ruff_db::system::InMemorySystem`].
@@ -103,6 +105,13 @@ pub(crate) struct Environment {
pub python: Option<SystemPathBuf>,
}
#[derive(Deserialize, Default, Debug, Clone)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub(crate) struct Analysis {
/// Whether ty should support `type: ignore` comments.
pub(crate) respect_type_ignore_comments: Option<bool>,
}
#[derive(Deserialize, Debug, Clone)]
#[serde(untagged)]
pub(crate) enum Log {

View File

@@ -8,12 +8,15 @@ use ruff_db::system::{
};
use ruff_db::vendored::VendoredFileSystem;
use ruff_notebook::{Notebook, NotebookError};
use salsa::Setter as _;
use std::borrow::Cow;
use std::sync::Arc;
use tempfile::TempDir;
use ty_module_resolver::SearchPaths;
use ty_python_semantic::lint::{LintRegistry, RuleSelection};
use ty_python_semantic::{Db as SemanticDb, Program, default_lint_registry};
use ty_python_semantic::{AnalysisSettings, Db as SemanticDb, Program, default_lint_registry};
use crate::config::Analysis;
#[salsa::db]
#[derive(Clone)]
@@ -23,13 +26,14 @@ pub(crate) struct Db {
system: MdtestSystem,
vendored: VendoredFileSystem,
rule_selection: Arc<RuleSelection>,
settings: Option<Settings>,
}
impl Db {
pub(crate) fn setup() -> Self {
let rule_selection = RuleSelection::all(default_lint_registry(), Severity::Info);
Self {
let mut db = Self {
system: MdtestSystem::in_memory(),
storage: salsa::Storage::new(Some(Box::new({
move |event| {
@@ -39,6 +43,35 @@ impl Db {
vendored: ty_vendored::file_system().clone(),
files: Files::default(),
rule_selection: Arc::new(rule_selection),
settings: None,
};
db.settings = Some(Settings::new(&db));
db
}
fn settings(&self) -> Settings {
self.settings.unwrap()
}
pub(crate) fn update_analysis_options(&mut self, options: Option<&Analysis>) {
let analysis = if let Some(options) = options {
let AnalysisSettings {
respect_type_ignore_comments: respect_type_ignore_comments_default,
} = AnalysisSettings::default();
AnalysisSettings {
respect_type_ignore_comments: options
.respect_type_ignore_comments
.unwrap_or(respect_type_ignore_comments_default),
}
} else {
AnalysisSettings::default()
};
let settings = self.settings();
if settings.analysis(self) != &analysis {
settings.set_analysis(self).to(analysis);
}
}
@@ -100,6 +133,10 @@ impl SemanticDb for Db {
fn verbose(&self) -> bool {
false
}
fn analysis_settings(&self) -> &AnalysisSettings {
self.settings().analysis(self)
}
}
#[salsa::db]
@@ -112,6 +149,13 @@ impl DbWithWritableSystem for Db {
}
}
#[salsa::input(debug)]
struct Settings {
#[default]
#[returns(ref)]
analysis: AnalysisSettings,
}
#[derive(Debug, Clone)]
pub(crate) struct MdtestSystem(Arc<MdtestSystemInner>);

View File

@@ -469,6 +469,7 @@ fn run_test(
};
Program::init_or_update(db, settings);
db.update_analysis_options(configuration.analysis.as_ref());
// When snapshot testing is enabled, this is populated with
// all diagnostics. Otherwise it remains empty.

View File

@@ -19,8 +19,8 @@ use ty_module_resolver::{Db as ModuleResolverDb, SearchPathSettings};
use ty_python_semantic::lint::LintRegistry;
use ty_python_semantic::types::check_types;
use ty_python_semantic::{
Db as SemanticDb, Program, ProgramSettings, PythonPlatform, PythonVersionWithSource,
default_lint_registry, lint::RuleSelection,
AnalysisSettings, Db as SemanticDb, Program, ProgramSettings, PythonPlatform,
PythonVersionWithSource, default_lint_registry, lint::RuleSelection,
};
/// Database that can be used for testing.
@@ -34,6 +34,7 @@ struct TestDb {
system: TestSystem,
vendored: VendoredFileSystem,
rule_selection: Arc<RuleSelection>,
analysis_settings: Arc<AnalysisSettings>,
}
impl TestDb {
@@ -48,6 +49,7 @@ impl TestDb {
vendored: ty_vendored::file_system().clone(),
files: Files::default(),
rule_selection: RuleSelection::from_registry(default_lint_registry()).into(),
analysis_settings: AnalysisSettings::default().into(),
}
}
}
@@ -98,6 +100,10 @@ impl SemanticDb for TestDb {
&self.rule_selection
}
fn analysis_settings(&self) -> &AnalysisSettings {
&self.analysis_settings
}
fn lint_registry(&self) -> &LintRegistry {
default_lint_registry()
}

23
ty.schema.json generated
View File

@@ -3,6 +3,16 @@
"title": "Options",
"type": "object",
"properties": {
"analysis": {
"anyOf": [
{
"$ref": "#/definitions/AnalysisOptions"
},
{
"type": "null"
}
]
},
"environment": {
"description": "Configures the type checking environment.",
"anyOf": [
@@ -59,6 +69,19 @@
},
"additionalProperties": false,
"definitions": {
"AnalysisOptions": {
"type": "object",
"properties": {
"respect-type-ignore-comments": {
"description": "Whether ty should respect `type: ignore` comments.\n\nWhen set to `false`, `type: ignore` comments are treated like any other normal\ncomment and can't be used to suppress ty errors (you have to use `ty: ignore` instead).\n\nSetting this option can be useful when using ty alongside other type checkers or when\nyou prefer using `ty: ignore` over `type: ignore`.\n\nDefaults to `true`.",
"type": [
"boolean",
"null"
]
}
},
"additionalProperties": false
},
"Array_of_string": {
"type": "array",
"items": {