|
1 | 1 | //! Interface for editing code snippets. These functions take statements or expressions as input,
|
2 | 2 | //! and return the modified code snippet as output.
|
| 3 | +use std::borrow::Cow; |
| 4 | + |
3 | 5 | use anyhow::{bail, Result};
|
4 | 6 | use libcst_native::{
|
5 | 7 | Codegen, CodegenState, Expression, ImportNames, NameOrAttribute, ParenthesizableWhitespace,
|
6 | 8 | SmallStatement, Statement,
|
7 | 9 | };
|
8 |
| -use ruff_python_ast::name::UnqualifiedName; |
9 | 10 | use smallvec::{smallvec, SmallVec};
|
| 11 | +use unicode_normalization::UnicodeNormalization; |
10 | 12 |
|
| 13 | +use ruff_python_ast::name::UnqualifiedName; |
11 | 14 | use ruff_python_ast::Stmt;
|
12 | 15 | use ruff_python_codegen::Stylist;
|
13 | 16 | use ruff_source_file::Locator;
|
@@ -167,39 +170,55 @@ pub(crate) fn retain_imports(
|
167 | 170 | Ok(tree.codegen_stylist(stylist))
|
168 | 171 | }
|
169 | 172 |
|
170 |
| -fn collect_segments<'a>(expr: &'a Expression, parts: &mut SmallVec<[&'a str; 8]>) { |
171 |
| - match expr { |
172 |
| - Expression::Call(expr) => { |
173 |
| - collect_segments(&expr.func, parts); |
174 |
| - } |
175 |
| - Expression::Attribute(expr) => { |
176 |
| - collect_segments(&expr.value, parts); |
177 |
| - parts.push(expr.attr.value); |
178 |
| - } |
179 |
| - Expression::Name(expr) => { |
180 |
| - parts.push(expr.value); |
| 173 | +/// Create an NFKC-normalized qualified name from a libCST node. |
| 174 | +fn qualified_name_from_name_or_attribute(module: &NameOrAttribute) -> String { |
| 175 | + fn collect_segments<'a>(expr: &'a Expression, parts: &mut SmallVec<[&'a str; 8]>) { |
| 176 | + match expr { |
| 177 | + Expression::Call(expr) => { |
| 178 | + collect_segments(&expr.func, parts); |
| 179 | + } |
| 180 | + Expression::Attribute(expr) => { |
| 181 | + collect_segments(&expr.value, parts); |
| 182 | + parts.push(expr.attr.value); |
| 183 | + } |
| 184 | + Expression::Name(expr) => { |
| 185 | + parts.push(expr.value); |
| 186 | + } |
| 187 | + _ => {} |
181 | 188 | }
|
182 |
| - _ => {} |
183 | 189 | }
|
184 |
| -} |
185 | 190 |
|
186 |
| -fn unqualified_name_from_expression<'a>(expr: &'a Expression<'a>) -> Option<UnqualifiedName<'a>> { |
187 |
| - let mut segments = smallvec![]; |
188 |
| - collect_segments(expr, &mut segments); |
189 |
| - if segments.is_empty() { |
190 |
| - None |
191 |
| - } else { |
192 |
| - Some(segments.into_iter().collect()) |
| 191 | + /// Attempt to create an [`UnqualifiedName`] from a libCST expression. |
| 192 | + /// |
| 193 | + /// Strictly speaking, the `UnqualifiedName` returned by this function may be invalid, |
| 194 | + /// since it hasn't been NFKC-normalized. In order for an `UnqualifiedName` to be |
| 195 | + /// comparable to one constructed from a `ruff_python_ast` node, it has to undergo |
| 196 | + /// NFKC normalization. As a local function, however, this is fine; |
| 197 | + /// the outer function always performs NFKC normalization before returning the |
| 198 | + /// qualified name to the caller. |
| 199 | + fn unqualified_name_from_expression<'a>( |
| 200 | + expr: &'a Expression<'a>, |
| 201 | + ) -> Option<UnqualifiedName<'a>> { |
| 202 | + let mut segments = smallvec![]; |
| 203 | + collect_segments(expr, &mut segments); |
| 204 | + if segments.is_empty() { |
| 205 | + None |
| 206 | + } else { |
| 207 | + Some(segments.into_iter().collect()) |
| 208 | + } |
193 | 209 | }
|
194 |
| -} |
195 | 210 |
|
196 |
| -fn qualified_name_from_name_or_attribute(module: &NameOrAttribute) -> String { |
197 |
| - match module { |
198 |
| - NameOrAttribute::N(name) => name.value.to_string(), |
| 211 | + let unnormalized = match module { |
| 212 | + NameOrAttribute::N(name) => Cow::Borrowed(name.value), |
199 | 213 | NameOrAttribute::A(attr) => {
|
200 | 214 | let name = attr.attr.value;
|
201 | 215 | let prefix = unqualified_name_from_expression(&attr.value);
|
202 |
| - prefix.map_or_else(|| name.to_string(), |prefix| format!("{prefix}.{name}")) |
| 216 | + prefix.map_or_else( |
| 217 | + || Cow::Borrowed(name), |
| 218 | + |prefix| Cow::Owned(format!("{prefix}.{name}")), |
| 219 | + ) |
203 | 220 | }
|
204 |
| - } |
| 221 | + }; |
| 222 | + |
| 223 | + unnormalized.nfkc().collect() |
205 | 224 | }
|
0 commit comments