Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid printing continuations within import identifiers #7744

Merged
merged 1 commit into from Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -2,6 +2,13 @@
from a import aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa, aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
from a import aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa as dfgsdfgsd, aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa as sdkjflsdjlahlfd

# Continuations.
import foo\
.bar

from foo\
.bar import baz

# At the top-level, force one empty line after an import, but allow up to two empty
# lines.
import os
Expand Down
3 changes: 2 additions & 1 deletion crates/ruff_python_formatter/src/other/alias.rs
@@ -1,6 +1,7 @@
use ruff_formatter::write;
use ruff_python_ast::Alias;

use crate::other::identifier::DotDelimitedIdentifier;
use crate::prelude::*;

#[derive(Default)]
Expand All @@ -13,7 +14,7 @@ impl FormatNodeRule<Alias> for FormatAlias {
name,
asname,
} = item;
name.format().fmt(f)?;
DotDelimitedIdentifier::new(name).fmt(f)?;
if let Some(asname) = asname {
write!(f, [space(), token("as"), space(), asname.format()])?;
}
Expand Down
40 changes: 40 additions & 0 deletions crates/ruff_python_formatter/src/other/identifier.rs
Expand Up @@ -27,3 +27,43 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Identifier {
FormatOwnedWithRule::new(self, FormatIdentifier)
}
}

/// A formatter for a dot-delimited identifier, as seen in import statements:
/// ```python
/// import foo.bar
/// ```
///
/// Dot-delimited identifiers can contain newlines via continuations (backslashes) after the
/// dot-delimited segment, as in:
/// ```python
/// import foo\
/// .bar
/// ```
///
/// While identifiers can typically be formatted via verbatim source code slices, dot-delimited
/// identifiers with newlines must be formatted via `text`. This struct implements both the fast
/// and slow paths for such identifiers.
#[derive(Debug)]
pub(crate) struct DotDelimitedIdentifier<'a>(&'a Identifier);

impl<'a> DotDelimitedIdentifier<'a> {
pub(crate) fn new(identifier: &'a Identifier) -> Self {
Self(identifier)
}
}

impl Format<PyFormatContext<'_>> for DotDelimitedIdentifier<'_> {
fn fmt(&self, f: &mut PyFormatter) -> FormatResult<()> {
// An import identifier can contain newlines by inserting continuations (backslashes) after
// a dot-delimited segment, as in:
// ```python
// import foo\
// .bar
// ```
if memchr::memchr(b'\\', f.context().source()[self.0.range()].as_bytes()).is_some() {
text(self.0.as_str(), Some(self.0.start())).fmt(f)
} else {
source_text_slice(self.0.range()).fmt(f)
}
}
}
Expand Up @@ -6,6 +6,7 @@ use ruff_text_size::Ranged;
use crate::builders::{parenthesize_if_expands, PyFormatterExtensions, TrailingComma};
use crate::comments::{SourceComment, SuppressionKind};
use crate::expression::parentheses::parenthesized;
use crate::other::identifier::DotDelimitedIdentifier;
use crate::prelude::*;

#[derive(Default)]
Expand All @@ -31,7 +32,7 @@ impl FormatNodeRule<StmtImportFrom> for FormatStmtImportFrom {
}
Ok(())
}),
module.as_ref().map(AsFormat::format),
module.as_ref().map(DotDelimitedIdentifier::new),
space(),
token("import"),
space(),
Expand Down
Expand Up @@ -8,6 +8,13 @@ from a import aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjf
from a import aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa, aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa
from a import aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa as dfgsdfgsd, aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa as sdkjflsdjlahlfd

# Continuations.
import foo\
.bar

from foo\
.bar import baz

# At the top-level, force one empty line after an import, but allow up to two empty
# lines.
import os
Expand Down Expand Up @@ -98,6 +105,11 @@ from a import (
aksjdhflsakhdflkjsadlfajkslhfdkjsaldajlahflashdfljahlfksajlhfajfjfsaahflakjslhdfkjalhdskjfa as sdkjflsdjlahlfd,
)

# Continuations.
import foo.bar

from foo.bar import baz

# At the top-level, force one empty line after an import, but allow up to two empty
# lines.
import os
Expand Down