Skip to content

Commit b4e3b85

Browse files
committed
Merge Async and Gen into CoroutineKind
1 parent c586717 commit b4e3b85

File tree

2 files changed

+202
-3
lines changed

2 files changed

+202
-3
lines changed

clippy_lints/src/doc/mod.rs

+199
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,205 @@ fn check_doc<'a, Events: Iterator<Item = (pulldown_cmark::Event<'a>, Range<usize
655655
headers
656656
}
657657

658+
<<<<<<< HEAD:src/tools/clippy/clippy_lints/src/doc/mod.rs
659+
=======
660+
fn check_link_quotes(cx: &LateContext<'_>, trimmed_text: &str, range: Range<usize>, fragments: Fragments<'_>) {
661+
if trimmed_text.starts_with('\'')
662+
&& trimmed_text.ends_with('\'')
663+
&& let Some(span) = fragments.span(cx, range)
664+
{
665+
span_lint(
666+
cx,
667+
DOC_LINK_WITH_QUOTES,
668+
span,
669+
"possible intra-doc link using quotes instead of backticks",
670+
);
671+
}
672+
}
673+
674+
fn check_code(cx: &LateContext<'_>, text: &str, edition: Edition, range: Range<usize>, fragments: Fragments<'_>) {
675+
fn has_needless_main(code: String, edition: Edition) -> bool {
676+
rustc_driver::catch_fatal_errors(|| {
677+
rustc_span::create_session_globals_then(edition, || {
678+
let filename = FileName::anon_source_code(&code);
679+
680+
let fallback_bundle =
681+
rustc_errors::fallback_fluent_bundle(rustc_driver::DEFAULT_LOCALE_RESOURCES.to_vec(), false);
682+
let emitter = EmitterWriter::new(Box::new(io::sink()), fallback_bundle);
683+
let handler = Handler::with_emitter(Box::new(emitter)).disable_warnings();
684+
#[expect(clippy::arc_with_non_send_sync)] // `Lrc` is expected by with_span_handler
685+
let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
686+
let sess = ParseSess::with_span_handler(handler, sm);
687+
688+
let mut parser = match maybe_new_parser_from_source_str(&sess, filename, code) {
689+
Ok(p) => p,
690+
Err(errs) => {
691+
drop(errs);
692+
return false;
693+
},
694+
};
695+
696+
let mut relevant_main_found = false;
697+
loop {
698+
match parser.parse_item(ForceCollect::No) {
699+
Ok(Some(item)) => match &item.kind {
700+
ItemKind::Fn(box Fn {
701+
sig, body: Some(block), ..
702+
}) if item.ident.name == sym::main => {
703+
let is_async = sig.header.coro_kind.is_async();
704+
let returns_nothing = match &sig.decl.output {
705+
FnRetTy::Default(..) => true,
706+
FnRetTy::Ty(ty) if ty.kind.is_unit() => true,
707+
FnRetTy::Ty(_) => false,
708+
};
709+
710+
if returns_nothing && !is_async && !block.stmts.is_empty() {
711+
// This main function should be linted, but only if there are no other functions
712+
relevant_main_found = true;
713+
} else {
714+
// This main function should not be linted, we're done
715+
return false;
716+
}
717+
},
718+
// Tests with one of these items are ignored
719+
ItemKind::Static(..)
720+
| ItemKind::Const(..)
721+
| ItemKind::ExternCrate(..)
722+
| ItemKind::ForeignMod(..)
723+
// Another function was found; this case is ignored
724+
| ItemKind::Fn(..) => return false,
725+
_ => {},
726+
},
727+
Ok(None) => break,
728+
Err(e) => {
729+
e.cancel();
730+
return false;
731+
},
732+
}
733+
}
734+
735+
relevant_main_found
736+
})
737+
})
738+
.ok()
739+
.unwrap_or_default()
740+
}
741+
742+
let trailing_whitespace = text.len() - text.trim_end().len();
743+
744+
// Because of the global session, we need to create a new session in a different thread with
745+
// the edition we need.
746+
let text = text.to_owned();
747+
if thread::spawn(move || has_needless_main(text, edition))
748+
.join()
749+
.expect("thread::spawn failed")
750+
&& let Some(span) = fragments.span(cx, range.start..range.end - trailing_whitespace)
751+
{
752+
span_lint(cx, NEEDLESS_DOCTEST_MAIN, span, "needless `fn main` in doctest");
753+
}
754+
}
755+
756+
fn check_text(cx: &LateContext<'_>, valid_idents: &FxHashSet<String>, text: &str, span: Span) {
757+
for word in text.split(|c: char| c.is_whitespace() || c == '\'') {
758+
// Trim punctuation as in `some comment (see foo::bar).`
759+
// ^^
760+
// Or even as in `_foo bar_` which is emphasized. Also preserve `::` as a prefix/suffix.
761+
let mut word = word.trim_matches(|c: char| !c.is_alphanumeric() && c != ':');
762+
763+
// Remove leading or trailing single `:` which may be part of a sentence.
764+
if word.starts_with(':') && !word.starts_with("::") {
765+
word = word.trim_start_matches(':');
766+
}
767+
if word.ends_with(':') && !word.ends_with("::") {
768+
word = word.trim_end_matches(':');
769+
}
770+
771+
if valid_idents.contains(word) || word.chars().all(|c| c == ':') {
772+
continue;
773+
}
774+
775+
// Adjust for the current word
776+
let offset = word.as_ptr() as usize - text.as_ptr() as usize;
777+
let span = Span::new(
778+
span.lo() + BytePos::from_usize(offset),
779+
span.lo() + BytePos::from_usize(offset + word.len()),
780+
span.ctxt(),
781+
span.parent(),
782+
);
783+
784+
check_word(cx, word, span);
785+
}
786+
}
787+
788+
fn check_word(cx: &LateContext<'_>, word: &str, span: Span) {
789+
/// Checks if a string is upper-camel-case, i.e., starts with an uppercase and
790+
/// contains at least two uppercase letters (`Clippy` is ok) and one lower-case
791+
/// letter (`NASA` is ok).
792+
/// Plurals are also excluded (`IDs` is ok).
793+
fn is_camel_case(s: &str) -> bool {
794+
if s.starts_with(|c: char| c.is_ascii_digit() | c.is_ascii_lowercase()) {
795+
return false;
796+
}
797+
798+
let s = s.strip_suffix('s').unwrap_or(s);
799+
800+
s.chars().all(char::is_alphanumeric)
801+
&& s.chars().filter(|&c| c.is_uppercase()).take(2).count() > 1
802+
&& s.chars().filter(|&c| c.is_lowercase()).take(1).count() > 0
803+
}
804+
805+
fn has_underscore(s: &str) -> bool {
806+
s != "_" && !s.contains("\\_") && s.contains('_')
807+
}
808+
809+
fn has_hyphen(s: &str) -> bool {
810+
s != "-" && s.contains('-')
811+
}
812+
813+
if let Ok(url) = Url::parse(word) {
814+
// try to get around the fact that `foo::bar` parses as a valid URL
815+
if !url.cannot_be_a_base() {
816+
span_lint(
817+
cx,
818+
DOC_MARKDOWN,
819+
span,
820+
"you should put bare URLs between `<`/`>` or make a proper Markdown link",
821+
);
822+
823+
return;
824+
}
825+
}
826+
827+
// We assume that mixed-case words are not meant to be put inside backticks. (Issue #2343)
828+
if has_underscore(word) && has_hyphen(word) {
829+
return;
830+
}
831+
832+
if has_underscore(word) || word.contains("::") || is_camel_case(word) {
833+
let mut applicability = Applicability::MachineApplicable;
834+
835+
span_lint_and_then(
836+
cx,
837+
DOC_MARKDOWN,
838+
span,
839+
"item in documentation is missing backticks",
840+
|diag| {
841+
let snippet = snippet_with_applicability(cx, span, "..", &mut applicability);
842+
diag.span_suggestion_with_style(
843+
span,
844+
"try",
845+
format!("`{snippet}`"),
846+
applicability,
847+
// always show the suggestion in a separate line, since the
848+
// inline presentation adds another pair of backticks
849+
SuggestionStyle::ShowAlways,
850+
);
851+
},
852+
);
853+
}
854+
}
855+
856+
>>>>>>> d116f1718f1 (Merge Async and Gen into CoroutineKind):src/tools/clippy/clippy_lints/src/doc.rs
658857
struct FindPanicUnwrap<'a, 'tcx> {
659858
cx: &'a LateContext<'tcx>,
660859
panic_span: Option<Span>,

clippy_utils/src/ast_utils.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
188188
Closure(box ast::Closure {
189189
binder: lb,
190190
capture_clause: lc,
191-
asyncness: la,
191+
coro_kind: la,
192192
movability: lm,
193193
fn_decl: lf,
194194
body: le,
@@ -197,7 +197,7 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool {
197197
Closure(box ast::Closure {
198198
binder: rb,
199199
capture_clause: rc,
200-
asyncness: ra,
200+
coro_kind: ra,
201201
movability: rm,
202202
fn_decl: rf,
203203
body: re,
@@ -565,7 +565,7 @@ pub fn eq_fn_sig(l: &FnSig, r: &FnSig) -> bool {
565565

566566
pub fn eq_fn_header(l: &FnHeader, r: &FnHeader) -> bool {
567567
matches!(l.unsafety, Unsafe::No) == matches!(r.unsafety, Unsafe::No)
568-
&& l.asyncness.is_async() == r.asyncness.is_async()
568+
&& (l.coro_kind.is_async() == r.coro_kind.is_async() || l.coro_kind.is_gen() == r.coro_kind.is_gen())
569569
&& matches!(l.constness, Const::No) == matches!(r.constness, Const::No)
570570
&& eq_ext(&l.ext, &r.ext)
571571
}

0 commit comments

Comments
 (0)