mirror of
https://github.com/astral-sh/ruff.git
synced 2025-10-01 06:11:21 +00:00
ERA001
: Ignore script-comments with multiple end-tags (#13283)
This commit is contained in:
parent
312bd86e48
commit
ac720cd705
3 changed files with 211 additions and 27 deletions
|
@ -57,7 +57,18 @@ dictionary = {
|
||||||
# ]
|
# ]
|
||||||
# ///
|
# ///
|
||||||
|
|
||||||
# Script tag without a closing tag (OK)
|
# Script tag with multiple closing tags (OK)
|
||||||
|
# /// script
|
||||||
|
# [tool.uv]
|
||||||
|
# extra-index-url = ["https://pypi.org/simple", """\
|
||||||
|
# https://example.com/
|
||||||
|
# ///
|
||||||
|
# """
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
print(1)
|
||||||
|
|
||||||
|
# Script tag without a closing tag (Error)
|
||||||
|
|
||||||
# /// script
|
# /// script
|
||||||
# requires-python = ">=3.11"
|
# requires-python = ">=3.11"
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
|
use crate::settings::LinterSettings;
|
||||||
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
use ruff_diagnostics::{Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
use ruff_macros::{derive_message_formats, violation};
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
use ruff_python_trivia::CommentRanges;
|
use ruff_python_trivia::CommentRanges;
|
||||||
use ruff_source_file::Locator;
|
use ruff_source_file::{Locator, UniversalNewlineIterator};
|
||||||
|
use ruff_text_size::TextRange;
|
||||||
use crate::settings::LinterSettings;
|
|
||||||
|
|
||||||
use super::super::detection::comment_contains_code;
|
use super::super::detection::comment_contains_code;
|
||||||
|
|
||||||
|
@ -50,28 +50,16 @@ pub(crate) fn commented_out_code(
|
||||||
comment_ranges: &CommentRanges,
|
comment_ranges: &CommentRanges,
|
||||||
settings: &LinterSettings,
|
settings: &LinterSettings,
|
||||||
) {
|
) {
|
||||||
// Skip comments within `/// script` tags.
|
let mut comments = comment_ranges.into_iter().peekable();
|
||||||
let mut in_script_tag = false;
|
|
||||||
|
|
||||||
// Iterate over all comments in the document.
|
// Iterate over all comments in the document.
|
||||||
for range in comment_ranges {
|
while let Some(range) = comments.next() {
|
||||||
let line = locator.lines(range);
|
let line = locator.line(range.start());
|
||||||
|
|
||||||
// Detect `/// script` tags.
|
|
||||||
if in_script_tag {
|
|
||||||
if is_script_tag_end(line) {
|
|
||||||
in_script_tag = false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if is_script_tag_start(line) {
|
if is_script_tag_start(line) {
|
||||||
in_script_tag = true;
|
if skip_script_comments(range, &mut comments, locator) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip comments within `/// script` tags.
|
|
||||||
if in_script_tag {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Verify that the comment is on its own line, and that it contains code.
|
// Verify that the comment is on its own line, and that it contains code.
|
||||||
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
if is_own_line_comment(line) && comment_contains_code(line, &settings.task_tags[..]) {
|
||||||
|
@ -84,6 +72,88 @@ pub(crate) fn commented_out_code(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses the rest of a [PEP 723](https://peps.python.org/pep-0723/)
|
||||||
|
/// script comment and moves `comments` past the script comment's end unless
|
||||||
|
/// the script comment is invalid.
|
||||||
|
///
|
||||||
|
/// Returns `true` if it is a valid script comment.
|
||||||
|
fn skip_script_comments<I>(
|
||||||
|
script_start: TextRange,
|
||||||
|
comments: &mut std::iter::Peekable<I>,
|
||||||
|
locator: &Locator,
|
||||||
|
) -> bool
|
||||||
|
where
|
||||||
|
I: Iterator<Item = TextRange>,
|
||||||
|
{
|
||||||
|
let line_end = locator.full_line_end(script_start.end());
|
||||||
|
let rest = locator.after(line_end);
|
||||||
|
let mut end_offset = None;
|
||||||
|
let mut lines = UniversalNewlineIterator::with_offset(rest, line_end).peekable();
|
||||||
|
|
||||||
|
while let Some(line) = lines.next() {
|
||||||
|
let Some(content) = script_line_content(&line) else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
if content == "///" {
|
||||||
|
// > Precedence for an ending line # /// is given when the next line is not a valid
|
||||||
|
// > embedded content line as described above.
|
||||||
|
// > For example, the following is a single fully valid block:
|
||||||
|
// > ```python
|
||||||
|
// > # /// some-toml
|
||||||
|
// > # embedded-csharp = """
|
||||||
|
// > # /// <summary>
|
||||||
|
// > # /// text
|
||||||
|
// > # ///
|
||||||
|
// > # /// </summary>
|
||||||
|
// > # public class MyClass { }
|
||||||
|
// > # """
|
||||||
|
// > # ///
|
||||||
|
// ````
|
||||||
|
if lines.next().is_some_and(|line| is_valid_script_line(&line)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
end_offset = Some(line.full_end());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// > Unclosed blocks MUST be ignored.
|
||||||
|
let Some(end_offset) = end_offset else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Skip over all script-comments.
|
||||||
|
while let Some(comment) = comments.peek() {
|
||||||
|
if comment.start() >= end_offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
comments.next();
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn script_line_content(line: &str) -> Option<&str> {
|
||||||
|
let Some(rest) = line.strip_prefix('#') else {
|
||||||
|
// Not a comment
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An empty line
|
||||||
|
if rest.is_empty() {
|
||||||
|
return Some("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// > If there are characters after the # then the first character MUST be a space.
|
||||||
|
rest.strip_prefix(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_valid_script_line(line: &str) -> bool {
|
||||||
|
script_line_content(line).is_some()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns `true` if line contains an own-line comment.
|
/// Returns `true` if line contains an own-line comment.
|
||||||
fn is_own_line_comment(line: &str) -> bool {
|
fn is_own_line_comment(line: &str) -> bool {
|
||||||
for char in line.chars() {
|
for char in line.chars() {
|
||||||
|
@ -104,9 +174,77 @@ fn is_script_tag_start(line: &str) -> bool {
|
||||||
line == "# /// script"
|
line == "# /// script"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if the line appears to start a script tag.
|
#[cfg(test)]
|
||||||
///
|
mod tests {
|
||||||
/// See: <https://peps.python.org/pep-0723/>
|
use crate::rules::eradicate::rules::commented_out_code::skip_script_comments;
|
||||||
fn is_script_tag_end(line: &str) -> bool {
|
use ruff_python_parser::parse_module;
|
||||||
line == "# ///"
|
use ruff_python_trivia::CommentRanges;
|
||||||
|
use ruff_source_file::Locator;
|
||||||
|
use ruff_text_size::TextSize;
|
||||||
|
#[test]
|
||||||
|
fn script_comment() {
|
||||||
|
let code = r#"
|
||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.11"
|
||||||
|
# dependencies = [
|
||||||
|
# "requests<3",
|
||||||
|
# "rich",
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
a = 10 # abc
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let parsed = parse_module(code).unwrap();
|
||||||
|
let locator = Locator::new(code);
|
||||||
|
|
||||||
|
let comments = CommentRanges::from(parsed.tokens());
|
||||||
|
let mut comments = comments.into_iter().peekable();
|
||||||
|
|
||||||
|
let script_start = code.find("# /// script").unwrap();
|
||||||
|
let script_start_range = locator.full_line_range(TextSize::try_from(script_start).unwrap());
|
||||||
|
|
||||||
|
let valid = skip_script_comments(script_start_range, &mut comments, &Locator::new(code));
|
||||||
|
|
||||||
|
assert!(valid);
|
||||||
|
|
||||||
|
let next_comment = comments.next();
|
||||||
|
|
||||||
|
assert!(next_comment.is_some());
|
||||||
|
assert_eq!(&code[next_comment.unwrap()], "# abc");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn script_comment_end_precedence() {
|
||||||
|
let code = r#"
|
||||||
|
# /// script
|
||||||
|
# [tool.uv]
|
||||||
|
# extra-index-url = ["https://pypi.org/simple", """\
|
||||||
|
# https://example.com/
|
||||||
|
# ///
|
||||||
|
# """
|
||||||
|
# ]
|
||||||
|
# ///
|
||||||
|
|
||||||
|
a = 10 # abc
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let parsed = parse_module(code).unwrap();
|
||||||
|
let locator = Locator::new(code);
|
||||||
|
|
||||||
|
let comments = CommentRanges::from(parsed.tokens());
|
||||||
|
let mut comments = comments.into_iter().peekable();
|
||||||
|
|
||||||
|
let script_start = code.find("# /// script").unwrap();
|
||||||
|
let script_start_range = locator.full_line_range(TextSize::try_from(script_start).unwrap());
|
||||||
|
|
||||||
|
let valid = skip_script_comments(script_start_range, &mut comments, &Locator::new(code));
|
||||||
|
|
||||||
|
assert!(valid);
|
||||||
|
|
||||||
|
let next_comment = comments.next();
|
||||||
|
|
||||||
|
assert!(next_comment.is_some());
|
||||||
|
assert_eq!(&code[next_comment.unwrap()], "# abc");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -321,3 +321,38 @@ ERA001.py:47:1: ERA001 Found commented-out code
|
||||||
48 47 | # ///
|
48 47 | # ///
|
||||||
49 48 |
|
49 48 |
|
||||||
50 49 | # Script tag (OK)
|
50 49 | # Script tag (OK)
|
||||||
|
|
||||||
|
ERA001.py:75:1: ERA001 Found commented-out code
|
||||||
|
|
|
||||||
|
73 | # /// script
|
||||||
|
74 | # requires-python = ">=3.11"
|
||||||
|
75 | # dependencies = [
|
||||||
|
| ^^^^^^^^^^^^^^^^^^ ERA001
|
||||||
|
76 | # "requests<3",
|
||||||
|
77 | # "rich",
|
||||||
|
|
|
||||||
|
= help: Remove commented-out code
|
||||||
|
|
||||||
|
ℹ Display-only fix
|
||||||
|
72 72 |
|
||||||
|
73 73 | # /// script
|
||||||
|
74 74 | # requires-python = ">=3.11"
|
||||||
|
75 |-# dependencies = [
|
||||||
|
76 75 | # "requests<3",
|
||||||
|
77 76 | # "rich",
|
||||||
|
78 77 | # ]
|
||||||
|
|
||||||
|
ERA001.py:78:1: ERA001 Found commented-out code
|
||||||
|
|
|
||||||
|
76 | # "requests<3",
|
||||||
|
77 | # "rich",
|
||||||
|
78 | # ]
|
||||||
|
| ^^^ ERA001
|
||||||
|
|
|
||||||
|
= help: Remove commented-out code
|
||||||
|
|
||||||
|
ℹ Display-only fix
|
||||||
|
75 75 | # dependencies = [
|
||||||
|
76 76 | # "requests<3",
|
||||||
|
77 77 | # "rich",
|
||||||
|
78 |-# ]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue