From c19f3d9913aee271bcab55b3d6f9ad78acc946a7 Mon Sep 17 00:00:00 2001 From: Gears Date: Wed, 12 Nov 2025 19:22:18 +0000 Subject: [PATCH] Warn for detatched doc comments --- CHANGELOG.md | 29 +++++++++++++++++++ compiler-core/src/parse.rs | 13 +++++++++ ...tests__warnings__detached_doc_comment.snap | 23 +++++++++++++++ compiler-core/src/type_/tests/warnings.rs | 13 +++++++++ compiler-core/src/warning.rs | 29 +++++++++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__detached_doc_comment.snap diff --git a/CHANGELOG.md b/CHANGELOG.md index f441ed5d1de..0f3a5a988f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,35 @@ containing scientific notation or trailing zeros (i.e. `100` and `1e2`). ([ptdewey](https://github.com/ptdewey)) +- The compiler now emits a warning when a doc comment is not attached to a + definition due to a regular comment in between. For example, in the following + code: + + ```gleam + /// This documentation is not attached + // This is not a doc comment + /// This is actual documentation + pub fn wibble() { + todo + } + ``` + + Will now produce the following warning: + + ```txt + warning: Detached doc comment + ┌─ src/main.gleam:1:4 + │ + 1 │ /// This documentation is not attached + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This is not attached to a definition + + This doc comment is followed by a regular comment so it is not attached to + any definition. + Hint: Move the comment above the doc comment + ``` + + ([Surya Rose](https://github.com/GearsDatapacks)) + ### Build tool - The help text displayed by `gleam dev --help`, `gleam test --help`, and diff --git a/compiler-core/src/parse.rs b/compiler-core/src/parse.rs index 016b62b8a24..658b27644f5 100644 --- a/compiler-core/src/parse.rs +++ b/compiler-core/src/parse.rs @@ -173,6 +173,14 @@ pub fn parse_module( }); } + for detached in parser.detached_doc_comments { + warnings.emit(Warning::DetachedDocComment { + path: path.clone(), + src: src.clone(), + location: detached, + }); + } + Ok(parsed) } @@ -218,6 +226,7 @@ pub struct Parser> { tok1: Option, extra: ModuleExtra, doc_comments: VecDeque<(u32, EcoString)>, + detached_doc_comments: Vec, } impl Parser where @@ -232,6 +241,7 @@ where tok1: None, extra: ModuleExtra::new(), doc_comments: VecDeque::new(), + detached_doc_comments: Vec::new(), }; parser.advance(); parser.advance(); @@ -4061,9 +4071,12 @@ functions are declared separately from types."; if *start >= until { break; } + if self.extra.has_comment_between(*start, until) { // We ignore doc comments that come before a regular comment. + let location = SrcSpan::new(*start, start + line.len() as u32); _ = self.doc_comments.pop_front(); + self.detached_doc_comments.push(location); continue; } diff --git a/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__detached_doc_comment.snap b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__detached_doc_comment.snap new file mode 100644 index 00000000000..7bb8e393675 --- /dev/null +++ b/compiler-core/src/type_/tests/snapshots/gleam_core__type___tests__warnings__detached_doc_comment.snap @@ -0,0 +1,23 @@ +--- +source: compiler-core/src/type_/tests/warnings.rs +expression: "\n/// This comment is detached\n//\n\n/// This is actual documentation\npub const pi = 3.14\n" +--- +----- SOURCE CODE + +/// This comment is detached +// + +/// This is actual documentation +pub const pi = 3.14 + + +----- WARNING +warning: Detached doc comment + ┌─ test/path:2:4 + │ +2 │ /// This comment is detached + │ ^^^^^^^^^^^^^^^^^^^^^^^^^ This is not attached to a definition + +This doc comment is followed by a regular comment so it is not attached to +any definition. +Hint: Move the comment above the doc comment diff --git a/compiler-core/src/type_/tests/warnings.rs b/compiler-core/src/type_/tests/warnings.rs index 2038b2ccdb1..69cba489564 100644 --- a/compiler-core/src/type_/tests/warnings.rs +++ b/compiler-core/src/type_/tests/warnings.rs @@ -4612,3 +4612,16 @@ pub type Wobble "#, ); } + +#[test] +fn detached_doc_comment() { + assert_warning!( + " +/// This comment is detached +// + +/// This is actual documentation +pub const pi = 3.14 +" + ); +} diff --git a/compiler-core/src/warning.rs b/compiler-core/src/warning.rs index fecb63636f4..e133cd1b73c 100644 --- a/compiler-core/src/warning.rs +++ b/compiler-core/src/warning.rs @@ -174,6 +174,12 @@ pub enum Warning { path: Utf8PathBuf, name: EcoString, }, + + DetachedDocComment { + path: Utf8PathBuf, + src: EcoString, + location: SrcSpan, + }, } #[derive(Debug, Clone, Eq, PartialEq, Copy)] @@ -424,6 +430,29 @@ To have a clause without a guard, remove this.", } } + Warning::DetachedDocComment { + path, + src, + location, + } => Diagnostic { + title: "Detached doc comment".into(), + text: wrap( + "This doc comment is followed by a regular \ +comment so it is not attached to any definition.", + ), + level: diagnostic::Level::Warning, + location: Some(Location { + path: path.to_path_buf(), + src: src.clone(), + label: diagnostic::Label { + text: Some("This is not attached to a definition".into()), + span: *location, + }, + extra_labels: Vec::new(), + }), + hint: Some("Move the comment above the doc comment".into()), + }, + Warning::Type { path, warning, src } => match warning { type_::Warning::Todo { kind,