Skip to content

Commit 8fc595a

Browse files
authored
Support markdown in extension mappings (#23218)
1 parent 7856d89 commit 8fc595a

7 files changed

Lines changed: 117 additions & 104 deletions

File tree

crates/ruff/src/commands/analyze_graph.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use ruff_graph::{Direction, ImportMap, ModuleDb, ModuleImports};
1010
use ruff_linter::package::PackageRoot;
1111
use ruff_linter::source_kind::SourceKind;
1212
use ruff_linter::{warn_user, warn_user_once};
13-
use ruff_python_ast::{PySourceType, SourceType};
13+
use ruff_python_ast::SourceType;
1414
use ruff_workspace::resolver::{ResolvedFile, match_exclusion, python_files_in_path};
1515
use rustc_hash::{FxBuildHasher, FxHashMap};
1616
use std::io::Write;
@@ -137,19 +137,16 @@ pub(crate) fn analyze_graph(
137137
}
138138

139139
// Ignore non-Python files.
140-
let source_type = match settings.analyze.extension.get(path) {
141-
None => match SourceType::from(&path) {
142-
SourceType::Python(source_type) => source_type,
143-
SourceType::Toml(_) => {
144-
debug!("Ignoring TOML file: {}", path.display());
145-
continue;
146-
}
147-
SourceType::Markdown => {
148-
debug!("Ignoring Markdown file: {}", path.display());
149-
continue;
150-
}
151-
},
152-
Some(language) => PySourceType::from(language),
140+
let source_type = match settings.analyze.extension.get_source_type(path) {
141+
SourceType::Python(source_type) => source_type,
142+
SourceType::Toml(_) => {
143+
debug!("Ignoring TOML file: {}", path.display());
144+
continue;
145+
}
146+
SourceType::Markdown => {
147+
debug!("Ignoring Markdown file: {}", path.display());
148+
continue;
149+
}
153150
};
154151

155152
// Convert to system paths.

crates/ruff/src/commands/format.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,7 @@ pub(crate) fn format(
123123
let path = resolved_file.path();
124124
let settings = resolver.resolve(path);
125125

126-
let source_type = match settings.formatter.extension.get(path) {
127-
None => SourceType::from(path),
128-
Some(language) => SourceType::Python(PySourceType::from(language)),
129-
};
126+
let source_type = settings.formatter.extension.get_source_type(path);
130127
if source_type.is_toml() {
131128
// Ignore TOML files.
132129
return None;

crates/ruff/src/commands/format_stdin.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use anyhow::Result;
55
use log::error;
66

77
use ruff_linter::source_kind::{SourceError, SourceKind};
8-
use ruff_python_ast::{PySourceType, SourceType};
8+
use ruff_python_ast::SourceType;
99
use ruff_workspace::FormatterSettings;
1010
use ruff_workspace::resolver::{PyprojectConfig, Resolver, match_exclusion, python_file_at_path};
1111

@@ -51,18 +51,15 @@ pub(crate) fn format_stdin(
5151
let path = cli.stdin_filename.as_deref();
5252
let settings = &resolver.base_settings().formatter;
5353

54-
let source_type = match path.and_then(|path| settings.extension.get(path)) {
55-
None => match path.map(SourceType::from).unwrap_or_default() {
56-
source_type @ (SourceType::Python(_) | SourceType::Markdown) => source_type,
57-
SourceType::Toml(_) => {
58-
if mode.is_write() {
59-
parrot_stdin()?;
60-
}
61-
return Ok(ExitStatus::Success);
62-
}
63-
},
64-
Some(language) => SourceType::Python(PySourceType::from(language)),
65-
};
54+
let source_type = path
55+
.map(|path| settings.extension.get_source_type(path))
56+
.unwrap_or_default();
57+
if source_type.is_toml() {
58+
if mode.is_write() {
59+
parrot_stdin()?;
60+
}
61+
return Ok(ExitStatus::Success);
62+
}
6663

6764
// Format the file.
6865
match format_source_code(path, cli.range, settings, source_type, mode) {

crates/ruff/src/diagnostics.rs

Lines changed: 50 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use ruff_linter::settings::{LinterSettings, flags};
2020
use ruff_linter::source_kind::{SourceError, SourceKind};
2121
use ruff_linter::{IOError, Violation, fs};
2222
use ruff_notebook::{NotebookError, NotebookIndex};
23-
use ruff_python_ast::{PySourceType, SourceType, TomlSourceType};
23+
use ruff_python_ast::{SourceType, TomlSourceType};
2424
use ruff_source_file::SourceFileBuilder;
2525
use ruff_text_size::TextRange;
2626
use ruff_workspace::Settings;
@@ -208,35 +208,31 @@ pub(crate) fn lint_path(
208208

209209
debug!("Checking: {}", path.display());
210210

211-
let source_type = match settings.extension.get(path).map(PySourceType::from) {
212-
Some(source_type) => source_type,
213-
None => match SourceType::from(path) {
214-
SourceType::Toml(TomlSourceType::Pyproject) => {
215-
let diagnostics = if settings
216-
.rules
217-
.iter_enabled()
218-
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
219-
{
220-
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
221-
Ok(contents) => contents,
222-
Err(err) => {
223-
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
224-
}
225-
};
226-
let source_file =
227-
SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
228-
lint_pyproject_toml(&source_file, settings)
229-
} else {
230-
vec![]
211+
let source_type = match settings.extension.get_source_type(path) {
212+
SourceType::Toml(TomlSourceType::Pyproject) => {
213+
let diagnostics = if settings
214+
.rules
215+
.iter_enabled()
216+
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
217+
{
218+
let contents = match std::fs::read_to_string(path).map_err(SourceError::from) {
219+
Ok(contents) => contents,
220+
Err(err) => {
221+
return Ok(Diagnostics::from_source_error(&err, Some(path), settings));
222+
}
231223
};
232-
return Ok(Diagnostics {
233-
inner: diagnostics,
234-
..Diagnostics::default()
235-
});
236-
}
237-
SourceType::Toml(_) | SourceType::Markdown => return Ok(Diagnostics::default()),
238-
SourceType::Python(source_type) => source_type,
239-
},
224+
let source_file = SourceFileBuilder::new(path.to_string_lossy(), contents).finish();
225+
lint_pyproject_toml(&source_file, settings)
226+
} else {
227+
vec![]
228+
};
229+
return Ok(Diagnostics {
230+
inner: diagnostics,
231+
..Diagnostics::default()
232+
});
233+
}
234+
SourceType::Toml(_) | SourceType::Markdown => return Ok(Diagnostics::default()),
235+
SourceType::Python(source_type) => source_type,
240236
};
241237

242238
// Extract the sources from the file.
@@ -356,42 +352,37 @@ pub(crate) fn lint_stdin(
356352
fix_mode: flags::FixMode,
357353
) -> Result<Diagnostics> {
358354
let (source_type, py_source_type) = match path
359-
.and_then(|path| settings.linter.extension.get(path))
355+
.map(|path| settings.linter.extension.get_source_type(path))
356+
.unwrap_or_default()
360357
{
361-
None => match path.map(SourceType::from).unwrap_or_default() {
362-
SourceType::Toml(source_type) if source_type.is_pyproject() => {
363-
if !settings
364-
.linter
365-
.rules
366-
.iter_enabled()
367-
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
368-
{
369-
return Ok(Diagnostics::default());
370-
}
358+
SourceType::Toml(source_type) if source_type.is_pyproject() => {
359+
if !settings
360+
.linter
361+
.rules
362+
.iter_enabled()
363+
.any(|rule_code| rule_code.lint_source().is_pyproject_toml())
364+
{
365+
return Ok(Diagnostics::default());
366+
}
371367

372-
let path = path.unwrap();
373-
let source_file =
374-
SourceFileBuilder::new(path.to_string_lossy(), contents.clone()).finish();
368+
let path = path.unwrap();
369+
let source_file =
370+
SourceFileBuilder::new(path.to_string_lossy(), contents.clone()).finish();
375371

376-
match fix_mode {
377-
flags::FixMode::Diff | flags::FixMode::Generate => {}
378-
flags::FixMode::Apply => write!(&mut io::stdout().lock(), "{contents}")?,
379-
}
380-
381-
return Ok(Diagnostics {
382-
inner: lint_pyproject_toml(&source_file, &settings.linter),
383-
fixed: FixMap::from_iter([(fs::relativize_path(path), FixTable::default())]),
384-
notebook_indexes: FxHashMap::default(),
385-
});
372+
match fix_mode {
373+
flags::FixMode::Diff | flags::FixMode::Generate => {}
374+
flags::FixMode::Apply => write!(&mut io::stdout().lock(), "{contents}")?,
386375
}
387376

388-
SourceType::Toml(_) | SourceType::Markdown => return Ok(Diagnostics::default()),
389-
source_type @ SourceType::Python(py_source_type) => (source_type, py_source_type),
390-
},
391-
Some(language) => {
392-
let py_source_type = PySourceType::from(language);
393-
(SourceType::Python(py_source_type), py_source_type)
377+
return Ok(Diagnostics {
378+
inner: lint_pyproject_toml(&source_file, &settings.linter),
379+
fixed: FixMap::from_iter([(fs::relativize_path(path), FixTable::default())]),
380+
notebook_indexes: FxHashMap::default(),
381+
});
394382
}
383+
384+
SourceType::Toml(_) | SourceType::Markdown => return Ok(Diagnostics::default()),
385+
source_type @ SourceType::Python(py_source_type) => (source_type, py_source_type),
395386
};
396387

397388
// Extract the sources from the file.

crates/ruff_linter/src/settings/types.rs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use strum_macros::EnumIter;
1616

1717
use ruff_cache::{CacheKey, CacheKeyHasher};
1818
use ruff_macros::CacheKey;
19-
use ruff_python_ast::{self as ast, PySourceType};
19+
use ruff_python_ast::{self as ast, PySourceType, SourceType};
2020

2121
use crate::Applicability;
2222
use crate::registry::RuleSet;
@@ -428,6 +428,7 @@ pub enum Language {
428428
Python,
429429
Pyi,
430430
Ipynb,
431+
Markdown,
431432
}
432433

433434
impl FromStr for Language {
@@ -438,19 +439,21 @@ impl FromStr for Language {
438439
"python" => Ok(Self::Python),
439440
"pyi" => Ok(Self::Pyi),
440441
"ipynb" => Ok(Self::Ipynb),
442+
"md" => Ok(Self::Markdown),
441443
_ => {
442444
bail!("Unrecognized language: `{s}`. Expected one of `python`, `pyi`, or `ipynb`.")
443445
}
444446
}
445447
}
446448
}
447449

448-
impl From<Language> for PySourceType {
450+
impl From<Language> for SourceType {
449451
fn from(value: Language) -> Self {
450452
match value {
451-
Language::Python => Self::Python,
452-
Language::Ipynb => Self::Ipynb,
453-
Language::Pyi => Self::Stub,
453+
Language::Python => Self::Python(PySourceType::Python),
454+
Language::Ipynb => Self::Python(PySourceType::Ipynb),
455+
Language::Pyi => Self::Python(PySourceType::Stub),
456+
Language::Markdown => Self::Markdown,
454457
}
455458
}
456459
}
@@ -505,6 +508,22 @@ impl ExtensionMapping {
505508
pub fn get_extension(&self, ext: &str) -> Option<Language> {
506509
self.0.get(ext).copied()
507510
}
511+
512+
/// Return a mapped [`SourceType`] for a given path.
513+
pub fn get_source_type(&self, path: &Path) -> SourceType {
514+
match self.get(path) {
515+
None => SourceType::from(path),
516+
Some(language) => SourceType::from(language),
517+
}
518+
}
519+
520+
/// Return a mapped [`SourceType`] for a given file extension.
521+
pub fn get_source_type_by_extension(&self, ext: &str) -> SourceType {
522+
match self.get_extension(ext) {
523+
None => SourceType::from_extension(ext),
524+
Some(language) => SourceType::from(language),
525+
}
526+
}
508527
}
509528

510529
impl From<FxHashMap<String, Language>> for ExtensionMapping {

crates/ruff_markdown/src/lib.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::{path::Path, sync::LazyLock};
22

33
use regex::Regex;
4-
use ruff_python_ast::PySourceType;
4+
use ruff_python_ast::{PySourceType, SourceType};
55
use ruff_python_formatter::format_module_source;
66
use ruff_python_trivia::textwrap::{dedent, indent};
77
use ruff_source_file::{Line, UniversalNewlines};
@@ -91,9 +91,10 @@ pub fn format_code_blocks(
9191

9292
// Maybe python, try formatting it
9393
let language = language.to_ascii_lowercase();
94-
let py_source_type = match settings.extension.get_extension(&language) {
95-
None => PySourceType::from_extension(&language),
96-
Some(language) => PySourceType::from(language),
94+
let SourceType::Python(py_source_type) =
95+
settings.extension.get_source_type_by_extension(&language)
96+
else {
97+
break;
9798
};
9899

99100
let end = code_line.start();

crates/ruff_python_ast/src/lib.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ pub enum SourceType {
4747
Markdown,
4848
}
4949

50+
impl SourceType {
51+
pub fn from_extension(ext: &str) -> Self {
52+
match ext {
53+
"toml" => Self::Toml(TomlSourceType::Unrecognized),
54+
"md" | "qmd" => Self::Markdown,
55+
_ => Self::Python(PySourceType::from_extension(ext)),
56+
}
57+
}
58+
}
59+
5060
impl Default for SourceType {
5161
fn default() -> Self {
5262
Self::Python(PySourceType::Python)
@@ -59,11 +69,12 @@ impl<P: AsRef<Path>> From<P> for SourceType {
5969
Some(filename) if filename == "pyproject.toml" => Self::Toml(TomlSourceType::Pyproject),
6070
Some(filename) if filename == "Pipfile" => Self::Toml(TomlSourceType::Pipfile),
6171
Some(filename) if filename == "poetry.lock" => Self::Toml(TomlSourceType::Poetry),
62-
_ => match path.as_ref().extension() {
63-
Some(ext) if ext == "toml" => Self::Toml(TomlSourceType::Unrecognized),
64-
Some(ext) if ext == "md" || ext == "qmd" => Self::Markdown,
65-
_ => Self::Python(PySourceType::from(path)),
66-
},
72+
_ => Self::from_extension(
73+
path.as_ref()
74+
.extension()
75+
.and_then(OsStr::to_str)
76+
.unwrap_or(""),
77+
),
6778
}
6879
}
6980
}

0 commit comments

Comments
 (0)