From ba0406a38c072c6705ff3abf7dc687fe23bd5be1 Mon Sep 17 00:00:00 2001 From: Kirill Mokevnin Date: Sat, 29 Aug 2026 10:21:22 -0400 Subject: [PATCH 1/3] fix: detect go package clause after leading comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `GO_PACKAGE_RE` is anchored at the start of the snippet, so a go block that opens with a comment — a file-name comment or a `//go:build` constraint — is treated as having no package clause. mdsf then prepends its temporary package and hands gofmt two package clauses, which fails with «expected declaration, found 'package'». The failure is quiet: mdsf prints `error running gofmt` and exits zero, so the block is not formatted and not reported as unformatted either. Anchor the pattern per line. Co-Authored-By: Claude Opus 5 (1M context) --- mdsf/src/parser/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mdsf/src/parser/mod.rs b/mdsf/src/parser/mod.rs index 41fcc286c..c82c19ada 100644 --- a/mdsf/src/parser/mod.rs +++ b/mdsf/src/parser/mod.rs @@ -41,7 +41,7 @@ pub fn parse_go_codeblock(lines: &mut Enumerate) -> (bool, String, usize) // TODO: check for multiline comments pub static GO_PACKAGE_RE: std::sync::LazyLock = - std::sync::LazyLock::new(|| Regex::new(r"^\s*package\s+\w").unwrap()); + std::sync::LazyLock::new(|| Regex::new(r"(?m)^\s*package\s+\w").unwrap()); #[inline] pub fn remove_go_package(snippet: String) -> String { @@ -81,6 +81,8 @@ mod test_go_package_re { "\n package \tmdsf", "\n package\tmdsf", "\n \tpackage\t\n\nmdsf\n", + "// mdsf\npackage mdsf", + "//go:build integration\n\npackage mdsf", ] { assert!(GO_PACKAGE_RE.is_match(s), "'{s}' did not match"); } From 8f98b6e82dee4743b30914a3da433ad7f0688352 Mon Sep 17 00:00:00 2001 From: Kirill Mokevnin Date: Sat, 29 Aug 2026 16:43:37 -0400 Subject: [PATCH 2/3] fix: skip leading comments when detecting go package clause A package clause is the first token of a Go file that is neither whitespace nor a comment. Match that rule directly instead of widening the anchored pattern to every line, so a block comment containing a line that starts with `package` is not mistaken for a package clause. Co-Authored-By: Claude Opus 5 (1M context) --- mdsf/src/parser/mod.rs | 60 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/mdsf/src/parser/mod.rs b/mdsf/src/parser/mod.rs index c82c19ada..16ce0c36d 100644 --- a/mdsf/src/parser/mod.rs +++ b/mdsf/src/parser/mod.rs @@ -32,16 +32,38 @@ pub fn parse_generic_codeblock(lines: &mut Enumerate) -> (bool, String, u pub fn parse_go_codeblock(lines: &mut Enumerate) -> (bool, String, usize) { let (is_snippet, mut code_snippet, snippet_lines) = parse_generic_codeblock(lines); - if is_snippet && !GO_PACKAGE_RE.is_match(&code_snippet) { + if is_snippet && !has_go_package(&code_snippet) { code_snippet.insert_str(0, GO_TEMPORARY_PACKAGE_NAME); } (is_snippet, code_snippet, snippet_lines) } -// TODO: check for multiline comments pub static GO_PACKAGE_RE: std::sync::LazyLock = - std::sync::LazyLock::new(|| Regex::new(r"(?m)^\s*package\s+\w").unwrap()); + std::sync::LazyLock::new(|| Regex::new(r"^\s*package\s+\w").unwrap()); + +/// A Go package clause is the first token of a file that is neither a comment +/// nor whitespace, so leading comments are skipped before looking for it. +#[inline] +pub fn has_go_package(snippet: &str) -> bool { + let mut rest = snippet.trim_start(); + + loop { + if let Some(after) = rest.strip_prefix("//") { + rest = after + .split_once('\n') + .map_or("", |(_, tail)| tail) + .trim_start(); + } else if let Some(after) = rest.strip_prefix("/*") { + rest = after + .split_once("*/") + .map_or("", |(_, tail)| tail) + .trim_start(); + } else { + return GO_PACKAGE_RE.is_match(rest); + } + } +} #[inline] pub fn remove_go_package(snippet: String) -> String { @@ -81,8 +103,6 @@ mod test_go_package_re { "\n package \tmdsf", "\n package\tmdsf", "\n \tpackage\t\n\nmdsf\n", - "// mdsf\npackage mdsf", - "//go:build integration\n\npackage mdsf", ] { assert!(GO_PACKAGE_RE.is_match(s), "'{s}' did not match"); } @@ -101,3 +121,33 @@ mod test_go_package_re { } } } + +#[cfg(test)] +mod test_has_go_package { + use crate::parser::has_go_package; + + #[test] + fn it_should_match() { + for s in [ + "package mdsf", + "// mdsf.go\npackage mdsf", + "//go:build integration\n\npackage mdsf", + "// mdsf.go\n// second line\npackage mdsf", + "/*\nI like my\npackage manager\n*/\npackage mdsf", + ] { + assert!(has_go_package(s), "'{s}' did not match"); + } + } + + #[test] + fn it_should_not_match() { + for s in [ + "const foo = 1", + "// package mdsf", + "// mdsf.go\nconst foo = 1", + "/*\nI like my\npackage manager\n*/\n\nconst foo = 1", + ] { + assert!(!has_go_package(s), "'{s}' matched"); + } + } +} From 1bd432c7e34ee801b531377ce6cb2fd753f9b4e1 Mon Sep 17 00:00:00 2001 From: mokevnin Date: Sat, 29 Aug 2026 20:45:21 +0000 Subject: [PATCH 3/3] chore: updated generated code --- mdsf/src/filetype/generated_file_types.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mdsf/src/filetype/generated_file_types.rs b/mdsf/src/filetype/generated_file_types.rs index a864c41cf..b40ae2ef6 100644 --- a/mdsf/src/filetype/generated_file_types.rs +++ b/mdsf/src/filetype/generated_file_types.rs @@ -336,6 +336,7 @@ pub fn language_to_ext(language: &str) -> Option<&'static str> { "fortran free form" => Some(".f90"), "fortran" => Some(".f"), "foxpro" => Some(".prg"), + "fpp" => Some(".fpp"), "freebasic" => Some(".bi"), "freemarker" => Some(".ftl"), "frege" => Some(".fr"), @@ -851,6 +852,7 @@ pub fn language_to_ext(language: &str) -> Option<&'static str> { "qsharp" => Some(".qs"), "qt script" => Some(".qs"), "quakec" => Some(".qc"), + "quartus simulation ip" => Some(".sip"), "quickbasic" => Some(".bas"), "quint" => Some(".qnt"), "r" => Some(".r"), @@ -888,6 +890,7 @@ pub fn language_to_ext(language: &str) -> Option<&'static str> { "restructuredtext" => Some(".rst"), "rexx" => Some(".rexx"), "rez" => Some(".r"), + "rhai" => Some(".rhai"), "rhtml" => Some(".erb"), "rich text format" => Some(".rtf"), "ring" => Some(".ring"), @@ -946,6 +949,7 @@ pub fn language_to_ext(language: &str) -> Option<&'static str> { "shen" => Some(".shen"), "sieve" => Some(".sieve"), "simple file verification" => Some(".sfv"), + "sip" => Some(".sip"), "slang" => Some(".slang"), "slash" => Some(".sl"), "slice" => Some(".ice"), @@ -1085,6 +1089,8 @@ pub fn language_to_ext(language: &str) -> Option<&'static str> { "vento" => Some(".vto"), "verilog" => Some(".v"), "verse" => Some(".verse"), + "vespa schema definition" => Some(".sd"), + "vespa" => Some(".sd"), "vhdl" => Some(".vhdl"), "vhs" => Some(".tape"), "vim help file" => Some(".txt"), @@ -1520,6 +1526,7 @@ mod test_language_to_ext { ("fortran free form", ".f90"), ("fortran", ".f"), ("foxpro", ".prg"), + ("fpp", ".fpp"), ("freebasic", ".bi"), ("freemarker", ".ftl"), ("frege", ".fr"), @@ -2035,6 +2042,7 @@ mod test_language_to_ext { ("qsharp", ".qs"), ("qt script", ".qs"), ("quakec", ".qc"), + ("quartus simulation ip", ".sip"), ("quickbasic", ".bas"), ("quint", ".qnt"), ("r", ".r"), @@ -2072,6 +2080,7 @@ mod test_language_to_ext { ("restructuredtext", ".rst"), ("rexx", ".rexx"), ("rez", ".r"), + ("rhai", ".rhai"), ("rhtml", ".erb"), ("rich text format", ".rtf"), ("ring", ".ring"), @@ -2130,6 +2139,7 @@ mod test_language_to_ext { ("shen", ".shen"), ("sieve", ".sieve"), ("simple file verification", ".sfv"), + ("sip", ".sip"), ("slang", ".slang"), ("slash", ".sl"), ("slice", ".ice"), @@ -2269,6 +2279,8 @@ mod test_language_to_ext { ("vento", ".vto"), ("verilog", ".v"), ("verse", ".verse"), + ("vespa schema definition", ".sd"), + ("vespa", ".sd"), ("vhdl", ".vhdl"), ("vhs", ".tape"), ("vim help file", ".txt"),