Repository: astral-sh/ruff
File: crates/ruff_linter/src/rules/flake8_implicit_str_concat/rules/collection_literal.rs (Rust)

Code before:
///     "In 1971, astronaut Alan Shepard played golf on the moon.",
/// )
/// ```
///
/// If the concatenation is intentional, wrap it in parentheses to make it
/// explicit:
/// ```python
/// facts = (
///     "Lobsters have blue blood.",
///     "The liver is the only human organ that can fully regenerate itself.",
///     (
///         "Clarinets are made almost entirely out of wood from the mpingo tree."
///         "In 1971, astronaut Alan Shepard played golf on the moon."
///     ),
/// )
/// ```
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.14.9")]
pub(crate) struct ImplicitStringConcatenationInCollectionLiteral;

impl Violation for ImplicitStringConcatenationInCollectionLiteral {
    const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always;

    #[derive_message_formats]
    fn message(&self) -> String {
        "Unparenthesized implicit string concatenation in collection; did you forget a comma?"
            .to_string()
    }

    fn fix_title(&self) -> Option<String> {
        Some("Wrap implicitly concatenated strings in parentheses".to_string())
    }
}

/// ISC004
pub(crate) fn implicit_string_concatenation_in_collection_literal(
    checker: &Checker,
    expr: &Expr,
    elements: &[Expr],
) {
    for element in elements {
        let Ok(string_like) = StringLike::try_from(element) else {
            continue;
        };
        if !string_like.is_implicit_concatenated() {
            continue;
        }
        if parenthesized_range(
            string_like.as_expression_ref(),
            expr.into(),
            checker.tokens(),

Review comment (refactor):
I don't feel strongly about this at all, but the `did you forget a comma?` after the semi-colon feels slightly too long/out of place to me. A couple of other options would be dropping it entirely or moving it to a `help` (or `info`) sub-diagnostic. That would look something like this:

```
ISC004 [*] Unparenthesized implicit string concatenation in collection
 --> ISC004.py:4:5
  |
2 |       "Lobsters have blue blood.",
3 |       "The liver is the only human organ that can fully regenerate itself.",
4 | /     "Clarinets are made almost entirely out of wood from the mpingo tree."
5 | |     "In 1971, astronaut Alan Shepard played golf on the moon.",
  | |______________________________________________________________^
6 |   )
  |
help: Did you forget a comma?
help: Wrap implicitly concatenated strings in parentheses
1 | facts = (
2 |     "Lobsters have blue blood.",
3 |     "The liver is the only human organ that can fully regenerate itself.",
  -     "Clarinets are made almost entirely out of wood from the mpingo tree."
  -     "In 1971, astronaut Alan Shepard played golf on the moon.",
4 +     ("Clarinets are made almost entirely out of wood from the mpingo tree."
5 +     "In 1971, astronaut Alan Shepard played golf on the moon."),
6 | )
7 | 
8 | facts = [
```

But again, I don't feel strongly, and we can leave it here in the main message if you prefer. That has the benefit of showing up in the `--output-format=concise` diagnostic anyway.

I also wonder if `Ambiguous` might be a better first word in the diagnostic than `Unparenthesized` since that's the problem parenthesizing is trying to fix, but that's an even smaller nit/suggestion.

Code after:
///     "In 1971, astronaut Alan Shepard played golf on the moon.",
/// )
/// ```
///
/// If the concatenation is intentional, wrap it in parentheses to make it
/// explicit:
/// ```python
/// facts = (
///     "Lobsters have blue blood.",
///     "The liver is the only human organ that can fully regenerate itself.",
///     (
///         "Clarinets are made almost entirely out of wood from the mpingo tree."
///         "In 1971, astronaut Alan Shepard played golf on the moon."
///     ),
/// )
/// ```
///
/// ## Fix safety
/// The fix is safe in that it does not change the semantics of your code.
/// However, the issue is that you may often want to change semantics
/// by adding a missing comma.
#[derive(ViolationMetadata)]
#[violation_metadata(preview_since = "0.14.10")]
pub(crate) struct ImplicitStringConcatenationInCollectionLiteral;

impl Violation for ImplicitStringConcatenationInCollectionLiteral {
    const FIX_AVAILABILITY: FixAvailability = FixAvailability::Always;

    #[derive_message_formats]
    fn message(&self) -> String {
        "Unparenthesized implicit string concatenation in collection".to_string()
    }

    fn fix_title(&self) -> Option<String> {
        Some("Wrap implicitly concatenated strings in parentheses".to_string())
    }
}

/// ISC004
pub(crate) fn implicit_string_concatenation_in_collection_literal(
    checker: &Checker,
    expr: &Expr,
    elements: &[Expr],
) {
    for element in elements {
        let Ok(string_like) = StringLike::try_from(element) else {
            continue;
        };
        if !string_like.is_implicit_concatenated() {
            continue;
        }