Add linting tools to GN and fix lint errors.

Change-Id: Ife65a275556b0a555a9e3d40d7bae4806a6a6964
Reviewed-on: https://gn-review.googlesource.com/c/gn/+/23720
Reviewed-by: Takuto Ikuta <tikuta@google.com>
Commit-Queue: Matt Stark <msta@google.com>
diff --git a/src/gn/starlark/Cargo.toml b/src/gn/starlark/Cargo.toml
index f98fdc8..57abfff 100644
--- a/src/gn/starlark/Cargo.toml
+++ b/src/gn/starlark/Cargo.toml
@@ -56,7 +56,9 @@
 borrow_as_ptr = "warn"
 cast_lossless = "warn"
 cloned_instead_of_copied = "warn"
-elidable_lifetime_names = "warn"
+# Several starlark macros expand to things that break this lint.
+# It's a pretty minor thing with no real consequences, so we just allow it.
+elidable_lifetime_names = "allow"
 explicit_into_iter_loop = "warn"
 explicit_iter_loop = "warn"
 flat_map_option = "warn"
diff --git a/src/gn/starlark/crates/types/src/errors.rs b/src/gn/starlark/crates/types/src/errors.rs
index 24fb1db..e8b2f07 100644
--- a/src/gn/starlark/crates/types/src/errors.rs
+++ b/src/gn/starlark/crates/types/src/errors.rs
@@ -26,7 +26,7 @@
 
 impl From<Error> for starlark::Error {
     fn from(err: Error) -> Self {
-        starlark::Error::new_other(err)
+        Self::new_other(err)
     }
 }
 
diff --git a/src/gn/starlark/crates/types/src/file.rs b/src/gn/starlark/crates/types/src/file.rs
index a4221e2..81b1dd6 100644
--- a/src/gn/starlark/crates/types/src/file.rs
+++ b/src/gn/starlark/crates/types/src/file.rs
@@ -9,7 +9,8 @@
     environment::{Methods, MethodsBuilder, MethodsStatic},
     starlark_simple_value,
     values::{
-        Freeze, FreezeResult, Freezer, ProvidesStaticType, StarlarkValue, Trace, Tracer, ValueLike,
+        Freeze, FreezeResult, Freezer, ProvidesStaticType, StarlarkValue, Trace, Tracer,
+        ValueLike as _,
     },
 };
 use starlark_derive::{starlark_module, starlark_value, NoSerialize};
@@ -68,7 +69,7 @@
     /// section of a ninja file.
     pub fn ninja_escaped_path(&self) -> Cow<'static, str> {
         let s = self.0;
-        if s.contains(|c| c == ' ' || c == '$' || c == ':') {
+        if s.contains([' ', '$', ':']) {
             let mut result = String::with_capacity(s.len());
             for c in s.chars() {
                 if c == '$' || c == ' ' || c == ':' {
@@ -109,7 +110,7 @@
 }
 
 impl Freeze for File {
-    type Frozen = File;
+    type Frozen = Self;
 
     fn freeze(self, _freezer: &Freezer) -> FreezeResult<Self::Frozen> {
         Ok(self)
@@ -127,23 +128,23 @@
     }
 
     fn write_hash(&self, hasher: &mut StarlarkHasher) -> starlark::Result<()> {
-        use std::hash::Hash;
+        use std::hash::Hash as _;
         self.hash(hasher);
         Ok(())
     }
 
     fn equals(&self, other: starlark::values::Value<'v>) -> starlark::Result<bool> {
-        Ok(other.downcast_ref::<File>().is_some_and(|o| self == o))
+        Ok(other.downcast_ref::<Self>().is_some_and(|o| self == o))
     }
 
     fn collect_repr(&self, collector: &mut String) {
-        use std::fmt::Write;
-        write!(collector, "{:?}", self).unwrap();
+        use std::fmt::Write as _;
+        write!(collector, "{self:?}").unwrap();
     }
 
     fn collect_str(&self, collector: &mut String) {
-        use std::fmt::Write;
-        write!(collector, "{}", self).unwrap();
+        use std::fmt::Write as _;
+        write!(collector, "{self}").unwrap();
     }
 }
 
@@ -187,6 +188,14 @@
     }
 }
 
+#[allow(dead_code)]
+fn dummy_to_force_cxx_linking() {
+    // The C++ code depends on the cxx crate being linked for the rust::Str type.
+    // Because the rust code doesn't depend on the cxx crate, we need a dummy
+    // function that uses some part of cxx to ensure it links.
+    drop(cxx::UniquePtr::<cxx::CxxString>::null());
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -236,11 +245,3 @@
         a.eq("repr(generated_file)", "'File(\"foo/bar/baz.txt\")'");
     }
 }
-
-#[allow(dead_code)]
-fn dummy_to_force_cxx_linking() {
-    // The C++ code depends on the cxx crate being linked for the rust::Str type.
-    // Because the rust code doesn't depend on the cxx crate, we need a dummy
-    // function that uses some part of cxx to ensure it links.
-    drop(cxx::UniquePtr::<cxx::CxxString>::null());
-}
diff --git a/src/gn/starlark/crates/types/src/label.rs b/src/gn/starlark/crates/types/src/label.rs
index e2fc540..bef5dc7 100644
--- a/src/gn/starlark/crates/types/src/label.rs
+++ b/src/gn/starlark/crates/types/src/label.rs
@@ -7,7 +7,8 @@
     environment::{Methods, MethodsBuilder, MethodsStatic},
     starlark_simple_value,
     values::{
-        Freeze, FreezeResult, Freezer, ProvidesStaticType, StarlarkValue, Trace, Tracer, ValueLike,
+        Freeze, FreezeResult, Freezer, ProvidesStaticType, StarlarkValue, Trace, Tracer,
+        ValueLike as _,
     },
 };
 use starlark_derive::{starlark_module, starlark_value, NoSerialize};
@@ -60,7 +61,7 @@
         let mut iter = s.split(':');
         // Verify that there is exactly one colon.
         match (iter.next(), iter.next(), iter.next()) {
-            (Some(package), Some(name), None) if !name.is_empty() => Ok(Label {
+            (Some(package), Some(name), None) if !name.is_empty() => Ok(Self {
                 // Safety: already checked.
                 package: unsafe { PackageRef::new_unchecked(package) }.to_owned(),
                 name: name.to_owned(),
@@ -85,7 +86,7 @@
         if name.contains(':') {
             return Err(crate::Error::ColonInRelativeLabel(s.to_owned()).into());
         }
-        Ok(Label {
+        Ok(Self {
             package: relative_to.to_owned(),
             name: name.to_owned(),
         })
@@ -133,7 +134,7 @@
 }
 
 impl Freeze for Label {
-    type Frozen = Label;
+    type Frozen = Self;
 
     fn freeze(self, _freezer: &Freezer) -> FreezeResult<Self::Frozen> {
         Ok(self)
@@ -151,22 +152,22 @@
     }
 
     fn write_hash(&self, hasher: &mut StarlarkHasher) -> starlark::Result<()> {
-        use std::hash::Hash;
+        use std::hash::Hash as _;
         self.hash(hasher);
         Ok(())
     }
 
     fn equals(&self, other: starlark::values::Value<'v>) -> starlark::Result<bool> {
-        Ok(other.downcast_ref::<Label>().is_some_and(|o| self == o))
+        Ok(other.downcast_ref::<Self>().is_some_and(|o| self == o))
     }
 
     fn collect_repr(&self, collector: &mut String) {
-        use std::fmt::Write;
+        use std::fmt::Write as _;
         write!(collector, "{:?}", self.as_ref()).unwrap();
     }
 
     fn collect_str(&self, collector: &mut String) {
-        use std::fmt::Write;
+        use std::fmt::Write as _;
         write!(collector, "{}", self.as_ref()).unwrap();
     }
 }
@@ -219,7 +220,7 @@
             PackageRef::new_for_testing("//foo/bar")
         );
         assert_eq!(lbl.name, "baz");
-        assert_eq!(format!("{:?}", lbl), "Label(\"//foo/bar:baz\")");
+        assert_eq!(format!("{lbl:?}"), "Label(\"//foo/bar:baz\")");
     }
 
     #[test]
diff --git a/src/gn/starlark/crates/types/src/label_ref.rs b/src/gn/starlark/crates/types/src/label_ref.rs
index b5223c5..e8d10b2 100644
--- a/src/gn/starlark/crates/types/src/label_ref.rs
+++ b/src/gn/starlark/crates/types/src/label_ref.rs
@@ -17,6 +17,11 @@
 }
 
 impl<'a> LabelRef<'a> {
+    /// Creates a new `LabelRef`.
+    pub fn new(package: &'a PackageRef, name: &'a str) -> Self {
+        Self { package, name }
+    }
+
     /// Returns the package of this label reference.
     pub fn package(&self) -> &PackageRef {
         self.package
@@ -26,28 +31,21 @@
     pub fn name(&self) -> &str {
         self.name
     }
+
+    /// Converts this reference into an owned `Label`.
+    pub fn to_owned(&self) -> Label {
+        Label::new(self.package.to_owned(), self.name.to_owned())
+    }
 }
 
-impl<'a> std::fmt::Display for LabelRef<'a> {
+impl std::fmt::Display for LabelRef<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "{}:{}", self.package, self.name)
     }
 }
 
-impl<'a> std::fmt::Debug for LabelRef<'a> {
+impl std::fmt::Debug for LabelRef<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         write!(f, "Label(\"{}:{}\")", self.package, self.name)
     }
 }
-
-impl<'a> LabelRef<'a> {
-    /// Creates a new `LabelRef`.
-    pub fn new(package: &'a PackageRef, name: &'a str) -> Self {
-        Self { package, name }
-    }
-
-    /// Converts this reference into an owned `Label`.
-    pub fn to_owned(&self) -> Label {
-        Label::new(self.package.to_owned(), self.name.to_owned())
-    }
-}
diff --git a/src/gn/starlark/crates/types/src/package_ref.rs b/src/gn/starlark/crates/types/src/package_ref.rs
index 0cdfacf..83034d2 100644
--- a/src/gn/starlark/crates/types/src/package_ref.rs
+++ b/src/gn/starlark/crates/types/src/package_ref.rs
@@ -26,12 +26,15 @@
 
     /// Creates a new `PackageRef` from a string slice.
     ///
-    /// Requires that the string starts with "//"
+    /// # Safety
+    ///
+    /// The caller must ensure that the string `s` is a valid package path
+    /// starting with "//".
     pub unsafe fn new_unchecked(s: &str) -> &Self {
         debug_assert!(s.starts_with("//"), "Package name must start with //");
         // Safety: PackageRef is #[repr(transparent)] wrapping str, so their memory
         // layouts are identical.
-        unsafe { &*(s as *const str as *const PackageRef) }
+        unsafe { &*(std::ptr::from_ref(s) as *const Self) }
     }
 
     /// Returns the full package name, eg. "//foo/bar"
diff --git a/src/gn/starlark/crates/types/src/util.rs b/src/gn/starlark/crates/types/src/util.rs
index b5729b4..2a6ed80 100644
--- a/src/gn/starlark/crates/types/src/util.rs
+++ b/src/gn/starlark/crates/types/src/util.rs
@@ -3,7 +3,13 @@
 // found in the LICENSE file.
 
 /// Like std::mem::transmute, but can only affect lifetime.
-pub unsafe fn extend_lifetime<'to, 'from, T: ?Sized>(val: &'from T) -> &'to T {
-    // Safety: None - this function is marked unsafe.
+///
+/// # Safety
+///
+/// The caller must ensure that the returned reference is not used after the
+/// underlying data is dropped.
+pub unsafe fn extend_lifetime<'to, T: ?Sized>(val: &T) -> &'to T {
+    // Safety: Transmuting lifetime of reference is unsafe, safety is guaranteed by
+    // the caller.
     unsafe { std::mem::transmute(val) }
 }
diff --git a/tools/run_linter.sh b/tools/run_linter.sh
new file mode 100755
index 0000000..5042f27
--- /dev/null
+++ b/tools/run_linter.sh
@@ -0,0 +1,13 @@
+#!/bin/bash -eu
+# Copyright 2026 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Resolve root directory
+cd "$(dirname "$(dirname "$0")")"
+
+if command -v cargo >/dev/null 2>&1; then
+  (cd src/gn/starlark && cargo clippy --workspace --all-targets --all-features -- -D warnings)
+else
+  echo "cargo is not installed, skipping Rust linting."
+fi