@@ -1545,11 +1545,51 @@ fn update_requirement(old: &mut Requirement, new: &Requirement, has_source: bool
15451545
15461546/// Removes all occurrences of dependencies with the given name from the given `deps` array.
15471547fn remove_dependency ( name : & PackageName , deps : & mut Array ) -> Vec < Requirement > {
1548- // Remove matching dependencies.
1548+ // Remove in reverse to preserve indices. Before each removal, transfer the item's
1549+ // prefix (which may contain end-of-line comments belonging to the previous line) to
1550+ // the next item or array trailing so comments are not lost.
1551+ //
1552+ // For example, in:
1553+ // ```toml
1554+ // dependencies = [
1555+ // "numpy>=2.4.3", # essential comment
1556+ // "requests>=2.32.5",
1557+ // ]
1558+ // ```
1559+ //
1560+ // The comment `# essential comment` is stored by `toml_edit` in the prefix of
1561+ // `requests`. When `requests` is removed, we transfer it so it remains on the
1562+ // `numpy` line.
15491563 let removed = find_dependencies ( name, None , deps)
15501564 . into_iter ( )
1551- . rev ( ) // Reverse to preserve indices as we remove them.
1565+ . rev ( )
15521566 . filter_map ( |( i, _) | {
1567+ if let Some ( prefix) = deps
1568+ . get ( i)
1569+ . and_then ( |item| item. decor ( ) . prefix ( ) . and_then ( |s| s. as_str ( ) ) )
1570+ . filter ( |s| !s. is_empty ( ) )
1571+ {
1572+ let prefix = prefix. to_string ( ) ;
1573+ if let Some ( next) = deps. get ( i + 1 )
1574+ && let Some ( existing) = next. decor ( ) . prefix ( ) . and_then ( |s| s. as_str ( ) )
1575+ {
1576+ // Transfer removed item's prefix to the next item's prefix.
1577+ let existing = existing. to_string ( ) ;
1578+ deps. get_mut ( i + 1 )
1579+ . unwrap ( )
1580+ . decor_mut ( )
1581+ . set_prefix ( format ! ( "{prefix}{existing}" ) ) ;
1582+ } else if let Some ( next) = deps. get_mut ( i + 1 ) {
1583+ // Next item exists but has no prefix; use ours directly.
1584+ next. decor_mut ( ) . set_prefix ( & prefix) ;
1585+ } else if let Some ( existing) = deps. trailing ( ) . as_str ( ) {
1586+ // No next item; move comments to the array trailing.
1587+ deps. set_trailing ( format ! ( "{prefix}{existing}" ) ) ;
1588+ } else {
1589+ deps. set_trailing ( & prefix) ;
1590+ }
1591+ }
1592+
15531593 deps. remove ( i)
15541594 . as_str ( )
15551595 . and_then ( |req| Requirement :: from_str ( req) . ok ( ) )
@@ -1750,9 +1790,11 @@ fn split_specifiers(req: &str) -> (&str, &str) {
17501790
17511791#[ cfg( test) ]
17521792mod test {
1753- use super :: { AddBoundsKind , reformat_array_multiline, split_specifiers} ;
1793+ use super :: { AddBoundsKind , reformat_array_multiline, remove_dependency, split_specifiers} ;
1794+ use insta:: assert_snapshot;
17541795 use std:: str:: FromStr ;
17551796 use toml_edit:: DocumentMut ;
1797+ use uv_normalize:: PackageName ;
17561798 use uv_pep440:: Version ;
17571799
17581800 #[ test]
@@ -1927,4 +1969,191 @@ dependencies = [
19271969 assert_eq ! ( actual, expected, "{version}" ) ;
19281970 }
19291971 }
1972+
1973+ #[ test]
1974+ fn remove_preserves_end_of_line_comment_on_previous_item ( ) {
1975+ let toml = r#"
1976+ [project]
1977+ dependencies = [
1978+ "numpy>=2.4.3", # this comment is clearly essential
1979+ "requests>=2.32.5",
1980+ ]
1981+ "# ;
1982+ let mut doc: DocumentMut = toml. parse ( ) . unwrap ( ) ;
1983+ let deps = doc[ "project" ] [ "dependencies" ]
1984+ . as_array_mut ( )
1985+ . expect ( "dependencies array" ) ;
1986+
1987+ let name = PackageName :: from_str ( "requests" ) . unwrap ( ) ;
1988+ remove_dependency ( & name, deps) ;
1989+
1990+ assert_snapshot ! (
1991+ doc. to_string( ) ,
1992+ @r#"
1993+ [project]
1994+ dependencies = [
1995+ "numpy>=2.4.3", # this comment is clearly essential
1996+ ]
1997+ "#
1998+ ) ;
1999+ }
2000+
2001+ #[ test]
2002+ fn remove_preserves_end_of_line_comment_on_previous_item_middle ( ) {
2003+ let toml = r#"
2004+ [project]
2005+ dependencies = [
2006+ "numpy>=2.4.3", # numpy comment
2007+ "requests>=2.32.5",
2008+ "flask>=3.0.0",
2009+ ]
2010+ "# ;
2011+ let mut doc: DocumentMut = toml. parse ( ) . unwrap ( ) ;
2012+ let deps = doc[ "project" ] [ "dependencies" ]
2013+ . as_array_mut ( )
2014+ . expect ( "dependencies array" ) ;
2015+
2016+ let name = PackageName :: from_str ( "requests" ) . unwrap ( ) ;
2017+ remove_dependency ( & name, deps) ;
2018+
2019+ assert_snapshot ! (
2020+ doc. to_string( ) ,
2021+ @r#"
2022+ [project]
2023+ dependencies = [
2024+ "numpy>=2.4.3", # numpy comment
2025+ "flask>=3.0.0",
2026+ ]
2027+ "#
2028+ ) ;
2029+ }
2030+
2031+ #[ test]
2032+ fn remove_preserves_own_line_comment_above_removed_item ( ) {
2033+ let toml = r#"
2034+ [project]
2035+ dependencies = [
2036+ "numpy>=2.4.3",
2037+ # This is a comment about requests
2038+ "requests>=2.32.5",
2039+ ]
2040+ "# ;
2041+ let mut doc: DocumentMut = toml. parse ( ) . unwrap ( ) ;
2042+ let deps = doc[ "project" ] [ "dependencies" ]
2043+ . as_array_mut ( )
2044+ . expect ( "dependencies array" ) ;
2045+
2046+ let name = PackageName :: from_str ( "requests" ) . unwrap ( ) ;
2047+ remove_dependency ( & name, deps) ;
2048+
2049+ assert_snapshot ! (
2050+ doc. to_string( ) ,
2051+ @r#"
2052+ [project]
2053+ dependencies = [
2054+ "numpy>=2.4.3",
2055+ # This is a comment about requests
2056+ ]
2057+ "#
2058+ ) ;
2059+ }
2060+
2061+ #[ test]
2062+ fn remove_item_with_trailing_comment_last ( ) {
2063+ // When the removed item itself has an end-of-line comment and is the last item,
2064+ // toml_edit stores the comment in the array trailing. The comment is preserved
2065+ // (as an own-line comment in the trailing section) but moves position since it
2066+ // can no longer be on the removed item's line.
2067+ let toml = r#"
2068+ [project]
2069+ dependencies = [
2070+ "requests>=2.32.5",
2071+ "numpy>=2.4.3", # comment on numpy
2072+ ]
2073+ "# ;
2074+ let mut doc: DocumentMut = toml. parse ( ) . unwrap ( ) ;
2075+ let deps = doc[ "project" ] [ "dependencies" ]
2076+ . as_array_mut ( )
2077+ . expect ( "dependencies array" ) ;
2078+
2079+ let name = PackageName :: from_str ( "numpy" ) . unwrap ( ) ;
2080+ remove_dependency ( & name, deps) ;
2081+
2082+ assert_snapshot ! (
2083+ doc. to_string( ) ,
2084+ @r#"
2085+ [project]
2086+ dependencies = [
2087+ "requests>=2.32.5",
2088+ # comment on numpy
2089+ ]
2090+ "#
2091+ ) ;
2092+ }
2093+
2094+ #[ test]
2095+ fn remove_item_with_trailing_comment_middle ( ) {
2096+ // When the removed item has an end-of-line comment and is in the middle,
2097+ // toml_edit stores the comment in the next item's prefix. After removal,
2098+ // reformat_array_multiline repositions it as an own-line comment.
2099+ let toml = r#"
2100+ [project]
2101+ dependencies = [
2102+ "requests>=2.32.5",
2103+ "numpy>=2.4.3", # comment on numpy
2104+ "flask>=3.0.0",
2105+ ]
2106+ "# ;
2107+ let mut doc: DocumentMut = toml. parse ( ) . unwrap ( ) ;
2108+ let deps = doc[ "project" ] [ "dependencies" ]
2109+ . as_array_mut ( )
2110+ . expect ( "dependencies array" ) ;
2111+
2112+ let name = PackageName :: from_str ( "numpy" ) . unwrap ( ) ;
2113+ remove_dependency ( & name, deps) ;
2114+
2115+ assert_snapshot ! (
2116+ doc. to_string( ) ,
2117+ @r#"
2118+ [project]
2119+ dependencies = [
2120+ "requests>=2.32.5",
2121+ # comment on numpy
2122+ "flask>=3.0.0",
2123+ ]
2124+ "#
2125+ ) ;
2126+ }
2127+
2128+ #[ test]
2129+ fn remove_multiple_adjacent_matches_preserves_comment_order ( ) {
2130+ let toml = r#"
2131+ [project]
2132+ dependencies = [
2133+ "iniconfig>=2.0.0", # comment on iniconfig
2134+ "typing-extensions>=4.0.0 ; python_version < '3.11'", # comment on first typing-extensions
2135+ "typing-extensions>=4.0.0 ; python_version >= '3.11'",
2136+ "sniffio>=1.3.0",
2137+ ]
2138+ "# ;
2139+ let mut doc: DocumentMut = toml. parse ( ) . unwrap ( ) ;
2140+ let deps = doc[ "project" ] [ "dependencies" ]
2141+ . as_array_mut ( )
2142+ . expect ( "dependencies array" ) ;
2143+
2144+ let name = PackageName :: from_str ( "typing-extensions" ) . unwrap ( ) ;
2145+ remove_dependency ( & name, deps) ;
2146+
2147+ assert_snapshot ! (
2148+ doc. to_string( ) ,
2149+ @r#"
2150+ [project]
2151+ dependencies = [
2152+ "iniconfig>=2.0.0", # comment on iniconfig
2153+ # comment on first typing-extensions
2154+ "sniffio>=1.3.0",
2155+ ]
2156+ "#
2157+ ) ;
2158+ }
19302159}
0 commit comments