mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-24 21:43:52 +00:00
[flake8-use-pathlib
] Catch redundant joins in PTH201
and avoid syntax errors (#15177)
Some checks are pending
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
Some checks are pending
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / benchmarks (push) Blocked by required conditions
## Summary Resolves #10453, resolves #15165. ## Test Plan `cargo nextest run` and `cargo insta test`.
This commit is contained in:
parent
d3492178e1
commit
901b7dd8f8
4 changed files with 437 additions and 66 deletions
|
@ -1,15 +1,70 @@
|
|||
from pathlib import Path, PurePath
|
||||
from pathlib import Path as pth
|
||||
|
||||
|
||||
# match
|
||||
_ = Path(".")
|
||||
_ = pth(".")
|
||||
_ = PurePath(".")
|
||||
_ = Path("")
|
||||
|
||||
Path('', )
|
||||
|
||||
Path(
|
||||
'',
|
||||
)
|
||||
|
||||
Path( # Comment before argument
|
||||
'',
|
||||
)
|
||||
|
||||
Path(
|
||||
'', # EOL comment
|
||||
)
|
||||
|
||||
Path(
|
||||
'' # Comment in the middle of implicitly concatenated string
|
||||
".",
|
||||
)
|
||||
|
||||
Path(
|
||||
'' # Comment before comma
|
||||
,
|
||||
)
|
||||
|
||||
Path(
|
||||
'',
|
||||
) / "bare"
|
||||
|
||||
Path( # Comment before argument
|
||||
'',
|
||||
) / ("parenthesized")
|
||||
|
||||
Path(
|
||||
'', # EOL comment
|
||||
) / ( ("double parenthesized" ) )
|
||||
|
||||
( Path(
|
||||
'' # Comment in the middle of implicitly concatenated string
|
||||
".",
|
||||
) )/ (("parenthesized path call")
|
||||
# Comment between closing parentheses
|
||||
)
|
||||
|
||||
Path(
|
||||
'' # Comment before comma
|
||||
,
|
||||
) / "multiple" / (
|
||||
"frag" # Comment
|
||||
'ment'
|
||||
)
|
||||
|
||||
|
||||
# no match
|
||||
_ = Path()
|
||||
print(".")
|
||||
Path("file.txt")
|
||||
Path(".", "folder")
|
||||
PurePath(".", "folder")
|
||||
|
||||
Path()
|
||||
|
|
|
@ -983,7 +983,7 @@ pub(crate) fn expression(expr: &Expr, checker: &mut Checker) {
|
|||
flake8_use_pathlib::rules::replaceable_by_pathlib(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::PathConstructorCurrentDirectory) {
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, expr, func);
|
||||
flake8_use_pathlib::rules::path_constructor_current_directory(checker, call);
|
||||
}
|
||||
if checker.enabled(Rule::OsSepSplit) {
|
||||
flake8_use_pathlib::rules::os_sep_split(checker, call);
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix};
|
||||
use std::ops::Range;
|
||||
|
||||
use ruff_diagnostics::{AlwaysFixableViolation, Applicability, Diagnostic, Edit, Fix};
|
||||
use ruff_macros::{derive_message_formats, ViolationMetadata};
|
||||
use ruff_python_ast::{self as ast, Expr, ExprCall};
|
||||
use ruff_python_ast::parenthesize::parenthesized_range;
|
||||
use ruff_python_ast::{AstNode, Expr, ExprBinOp, ExprCall, Operator};
|
||||
use ruff_python_semantic::SemanticModel;
|
||||
use ruff_python_trivia::CommentRanges;
|
||||
use ruff_text_size::{Ranged, TextRange};
|
||||
|
||||
use crate::checkers::ast::Checker;
|
||||
use crate::fix::edits::{remove_argument, Parentheses};
|
||||
|
||||
/// ## What it does
|
||||
/// Checks for `pathlib.Path` objects that are initialized with the current
|
||||
|
@ -43,7 +50,17 @@ impl AlwaysFixableViolation for PathConstructorCurrentDirectory {
|
|||
}
|
||||
|
||||
/// PTH201
|
||||
pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &Expr, func: &Expr) {
|
||||
pub(crate) fn path_constructor_current_directory(checker: &mut Checker, call: &ExprCall) {
|
||||
let applicability = |range| {
|
||||
if checker.comment_ranges().intersects(range) {
|
||||
Applicability::Unsafe
|
||||
} else {
|
||||
Applicability::Safe
|
||||
}
|
||||
};
|
||||
|
||||
let (func, arguments) = (&call.func, &call.arguments);
|
||||
|
||||
if !checker
|
||||
.semantic()
|
||||
.resolve_qualified_name(func)
|
||||
|
@ -54,21 +71,75 @@ pub(crate) fn path_constructor_current_directory(checker: &mut Checker, expr: &E
|
|||
return;
|
||||
}
|
||||
|
||||
let Expr::Call(ExprCall { arguments, .. }) = expr else {
|
||||
return;
|
||||
};
|
||||
|
||||
if !arguments.keywords.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let [Expr::StringLiteral(ast::ExprStringLiteral { value, range })] = &*arguments.args else {
|
||||
let [Expr::StringLiteral(arg)] = &*arguments.args else {
|
||||
return;
|
||||
};
|
||||
|
||||
if matches!(value.to_str(), "" | ".") {
|
||||
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, *range);
|
||||
diagnostic.set_fix(Fix::safe_edit(Edit::range_deletion(*range)));
|
||||
checker.diagnostics.push(diagnostic);
|
||||
if !matches!(arg.value.to_str(), "" | ".") {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut diagnostic = Diagnostic::new(PathConstructorCurrentDirectory, arg.range());
|
||||
|
||||
match parent_and_next_path_fragment_range(
|
||||
checker.semantic(),
|
||||
checker.comment_ranges(),
|
||||
checker.source(),
|
||||
) {
|
||||
Some((parent_range, next_fragment_range)) => {
|
||||
let next_fragment_expr = checker.locator().slice(next_fragment_range);
|
||||
let call_expr = checker.locator().slice(call.range());
|
||||
|
||||
let relative_argument_range: Range<usize> = {
|
||||
let range = arg.range() - call.start();
|
||||
range.start().into()..range.end().into()
|
||||
};
|
||||
|
||||
let mut new_call_expr = call_expr.to_string();
|
||||
new_call_expr.replace_range(relative_argument_range, next_fragment_expr);
|
||||
|
||||
let edit = Edit::range_replacement(new_call_expr, parent_range);
|
||||
|
||||
diagnostic.set_fix(Fix::applicable_edit(edit, applicability(parent_range)));
|
||||
}
|
||||
None => diagnostic.try_set_fix(|| {
|
||||
let edit = remove_argument(arg, arguments, Parentheses::Preserve, checker.source())?;
|
||||
Ok(Fix::applicable_edit(edit, applicability(call.range())))
|
||||
}),
|
||||
};
|
||||
|
||||
checker.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
||||
fn parent_and_next_path_fragment_range(
|
||||
semantic: &SemanticModel,
|
||||
comment_ranges: &CommentRanges,
|
||||
source: &str,
|
||||
) -> Option<(TextRange, TextRange)> {
|
||||
let parent = semantic.current_expression_parent()?;
|
||||
|
||||
let Expr::BinOp(parent @ ExprBinOp { op, right, .. }) = parent else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let range = right.range();
|
||||
|
||||
if !matches!(op, Operator::Div) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
parent.range(),
|
||||
parenthesized_range(
|
||||
right.into(),
|
||||
parent.as_any_node_ref(),
|
||||
comment_ranges,
|
||||
source,
|
||||
)
|
||||
.unwrap_or(range),
|
||||
))
|
||||
}
|
||||
|
|
|
@ -2,84 +2,329 @@
|
|||
source: crates/ruff_linter/src/rules/flake8_use_pathlib/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
PTH201.py:5:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
PTH201.py:6:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
4 | # match
|
||||
5 | _ = Path(".")
|
||||
5 | # match
|
||||
6 | _ = Path(".")
|
||||
| ^^^ PTH201
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
2 2 | from pathlib import Path as pth
|
||||
3 3 |
|
||||
4 4 | # match
|
||||
5 |-_ = Path(".")
|
||||
5 |+_ = Path()
|
||||
6 6 | _ = pth(".")
|
||||
7 7 | _ = PurePath(".")
|
||||
8 8 | _ = Path("")
|
||||
|
||||
PTH201.py:6:9: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
4 | # match
|
||||
5 | _ = Path(".")
|
||||
6 | _ = pth(".")
|
||||
| ^^^ PTH201
|
||||
7 | _ = PurePath(".")
|
||||
8 | _ = Path("")
|
||||
7 | _ = pth(".")
|
||||
8 | _ = PurePath(".")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
3 3 |
|
||||
4 4 | # match
|
||||
5 5 | _ = Path(".")
|
||||
6 |-_ = pth(".")
|
||||
6 |+_ = pth()
|
||||
7 7 | _ = PurePath(".")
|
||||
8 8 | _ = Path("")
|
||||
9 9 |
|
||||
4 4 |
|
||||
5 5 | # match
|
||||
6 |-_ = Path(".")
|
||||
6 |+_ = Path()
|
||||
7 7 | _ = pth(".")
|
||||
8 8 | _ = PurePath(".")
|
||||
9 9 | _ = Path("")
|
||||
|
||||
PTH201.py:7:14: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
PTH201.py:7:9: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
5 | _ = Path(".")
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
5 | # match
|
||||
6 | _ = Path(".")
|
||||
7 | _ = pth(".")
|
||||
| ^^^ PTH201
|
||||
8 | _ = Path("")
|
||||
8 | _ = PurePath(".")
|
||||
9 | _ = Path("")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
4 4 | # match
|
||||
5 5 | _ = Path(".")
|
||||
6 6 | _ = pth(".")
|
||||
7 |-_ = PurePath(".")
|
||||
7 |+_ = PurePath()
|
||||
8 8 | _ = Path("")
|
||||
9 9 |
|
||||
10 10 | # no match
|
||||
4 4 |
|
||||
5 5 | # match
|
||||
6 6 | _ = Path(".")
|
||||
7 |-_ = pth(".")
|
||||
7 |+_ = pth()
|
||||
8 8 | _ = PurePath(".")
|
||||
9 9 | _ = Path("")
|
||||
10 10 |
|
||||
|
||||
PTH201.py:8:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
PTH201.py:8:14: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
6 | _ = pth(".")
|
||||
7 | _ = PurePath(".")
|
||||
8 | _ = Path("")
|
||||
6 | _ = Path(".")
|
||||
7 | _ = pth(".")
|
||||
8 | _ = PurePath(".")
|
||||
| ^^^ PTH201
|
||||
9 | _ = Path("")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | # match
|
||||
6 6 | _ = Path(".")
|
||||
7 7 | _ = pth(".")
|
||||
8 |-_ = PurePath(".")
|
||||
8 |+_ = PurePath()
|
||||
9 9 | _ = Path("")
|
||||
10 10 |
|
||||
11 11 | Path('', )
|
||||
|
||||
PTH201.py:9:10: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
7 | _ = pth(".")
|
||||
8 | _ = PurePath(".")
|
||||
9 | _ = Path("")
|
||||
| ^^ PTH201
|
||||
9 |
|
||||
10 | # no match
|
||||
10 |
|
||||
11 | Path('', )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
5 5 | _ = Path(".")
|
||||
6 6 | _ = pth(".")
|
||||
7 7 | _ = PurePath(".")
|
||||
8 |-_ = Path("")
|
||||
8 |+_ = Path()
|
||||
9 9 |
|
||||
10 10 | # no match
|
||||
11 11 | _ = Path()
|
||||
6 6 | _ = Path(".")
|
||||
7 7 | _ = pth(".")
|
||||
8 8 | _ = PurePath(".")
|
||||
9 |-_ = Path("")
|
||||
9 |+_ = Path()
|
||||
10 10 |
|
||||
11 11 | Path('', )
|
||||
12 12 |
|
||||
|
||||
PTH201.py:11:6: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
9 | _ = Path("")
|
||||
10 |
|
||||
11 | Path('', )
|
||||
| ^^ PTH201
|
||||
12 |
|
||||
13 | Path(
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
8 8 | _ = PurePath(".")
|
||||
9 9 | _ = Path("")
|
||||
10 10 |
|
||||
11 |-Path('', )
|
||||
11 |+Path()
|
||||
12 12 |
|
||||
13 13 | Path(
|
||||
14 14 | '',
|
||||
|
||||
PTH201.py:14:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
13 | Path(
|
||||
14 | '',
|
||||
| ^^ PTH201
|
||||
15 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
10 10 |
|
||||
11 11 | Path('', )
|
||||
12 12 |
|
||||
13 |-Path(
|
||||
14 |- '',
|
||||
15 |-)
|
||||
13 |+Path()
|
||||
16 14 |
|
||||
17 15 | Path( # Comment before argument
|
||||
18 16 | '',
|
||||
|
||||
PTH201.py:18:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
17 | Path( # Comment before argument
|
||||
18 | '',
|
||||
| ^^ PTH201
|
||||
19 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
14 14 | '',
|
||||
15 15 | )
|
||||
16 16 |
|
||||
17 |-Path( # Comment before argument
|
||||
18 |- '',
|
||||
19 |-)
|
||||
17 |+Path()
|
||||
20 18 |
|
||||
21 19 | Path(
|
||||
22 20 | '', # EOL comment
|
||||
|
||||
PTH201.py:22:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
21 | Path(
|
||||
22 | '', # EOL comment
|
||||
| ^^ PTH201
|
||||
23 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
18 18 | '',
|
||||
19 19 | )
|
||||
20 20 |
|
||||
21 |-Path(
|
||||
22 |- '', # EOL comment
|
||||
23 |-)
|
||||
21 |+Path()
|
||||
24 22 |
|
||||
25 23 | Path(
|
||||
26 24 | '' # Comment in the middle of implicitly concatenated string
|
||||
|
||||
PTH201.py:26:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
25 | Path(
|
||||
26 | '' # Comment in the middle of implicitly concatenated string
|
||||
| _____^
|
||||
27 | | ".",
|
||||
| |_______^ PTH201
|
||||
28 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
22 22 | '', # EOL comment
|
||||
23 23 | )
|
||||
24 24 |
|
||||
25 |-Path(
|
||||
26 |- '' # Comment in the middle of implicitly concatenated string
|
||||
27 |- ".",
|
||||
28 |-)
|
||||
25 |+Path()
|
||||
29 26 |
|
||||
30 27 | Path(
|
||||
31 28 | '' # Comment before comma
|
||||
|
||||
PTH201.py:31:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
30 | Path(
|
||||
31 | '' # Comment before comma
|
||||
| ^^ PTH201
|
||||
32 | ,
|
||||
33 | )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
27 27 | ".",
|
||||
28 28 | )
|
||||
29 29 |
|
||||
30 |-Path(
|
||||
31 |- '' # Comment before comma
|
||||
32 |- ,
|
||||
33 |-)
|
||||
30 |+Path()
|
||||
34 31 |
|
||||
35 32 | Path(
|
||||
36 33 | '',
|
||||
|
||||
PTH201.py:36:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
35 | Path(
|
||||
36 | '',
|
||||
| ^^ PTH201
|
||||
37 | ) / "bare"
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Safe fix
|
||||
33 33 | )
|
||||
34 34 |
|
||||
35 35 | Path(
|
||||
36 |- '',
|
||||
37 |-) / "bare"
|
||||
36 |+ "bare",
|
||||
37 |+)
|
||||
38 38 |
|
||||
39 39 | Path( # Comment before argument
|
||||
40 40 | '',
|
||||
|
||||
PTH201.py:40:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
39 | Path( # Comment before argument
|
||||
40 | '',
|
||||
| ^^ PTH201
|
||||
41 | ) / ("parenthesized")
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
37 37 | ) / "bare"
|
||||
38 38 |
|
||||
39 39 | Path( # Comment before argument
|
||||
40 |- '',
|
||||
41 |-) / ("parenthesized")
|
||||
40 |+ ("parenthesized"),
|
||||
41 |+)
|
||||
42 42 |
|
||||
43 43 | Path(
|
||||
44 44 | '', # EOL comment
|
||||
|
||||
PTH201.py:44:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
43 | Path(
|
||||
44 | '', # EOL comment
|
||||
| ^^ PTH201
|
||||
45 | ) / ( ("double parenthesized" ) )
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
41 41 | ) / ("parenthesized")
|
||||
42 42 |
|
||||
43 43 | Path(
|
||||
44 |- '', # EOL comment
|
||||
45 |-) / ( ("double parenthesized" ) )
|
||||
44 |+ ( ("double parenthesized" ) ), # EOL comment
|
||||
45 |+)
|
||||
46 46 |
|
||||
47 47 | ( Path(
|
||||
48 48 | '' # Comment in the middle of implicitly concatenated string
|
||||
|
||||
PTH201.py:48:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
47 | ( Path(
|
||||
48 | '' # Comment in the middle of implicitly concatenated string
|
||||
| _____^
|
||||
49 | | ".",
|
||||
| |_______^ PTH201
|
||||
50 | ) )/ (("parenthesized path call")
|
||||
51 | # Comment between closing parentheses
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
44 44 | '', # EOL comment
|
||||
45 45 | ) / ( ("double parenthesized" ) )
|
||||
46 46 |
|
||||
47 |-( Path(
|
||||
48 |- '' # Comment in the middle of implicitly concatenated string
|
||||
49 |- ".",
|
||||
50 |-) )/ (("parenthesized path call")
|
||||
47 |+Path(
|
||||
48 |+ (("parenthesized path call")
|
||||
51 49 | # Comment between closing parentheses
|
||||
50 |+),
|
||||
52 51 | )
|
||||
53 52 |
|
||||
54 53 | Path(
|
||||
|
||||
PTH201.py:55:5: PTH201 [*] Do not pass the current directory explicitly to `Path`
|
||||
|
|
||||
54 | Path(
|
||||
55 | '' # Comment before comma
|
||||
| ^^ PTH201
|
||||
56 | ,
|
||||
57 | ) / "multiple" / (
|
||||
|
|
||||
= help: Remove the current directory argument
|
||||
|
||||
ℹ Unsafe fix
|
||||
52 52 | )
|
||||
53 53 |
|
||||
54 54 | Path(
|
||||
55 |- '' # Comment before comma
|
||||
55 |+ "multiple" # Comment before comma
|
||||
56 56 | ,
|
||||
57 |-) / "multiple" / (
|
||||
57 |+) / (
|
||||
58 58 | "frag" # Comment
|
||||
59 59 | 'ment'
|
||||
60 60 | )
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue