fix(pp): // line comment containing /* no longer eats subsequent lines

stripBlockComments scanned each line for a /* block-comment opener
while tracking string literals but had no notion of // or && line
comments. A line like `// see app/api/*.prg` would open a block
comment from /*.prg that ran until EOF or the next */, silently
dropping every FUNCTION declaration in between. The compiled file
ended up with an empty symbols slice, and callers in other files
panicked at runtime with "no function symbol for call".

Hit while writing app/lib/text.prg in solmade — its `// build's
\`app/api/*.prg\` glob doesn't pick it up` line dropped all three
of QueryParamRaw / UrlDecodeBytes / IsAllDigits.

Fix: detect // and && line-comment markers before the /* check.
When one is seen, copy the rest of the line through verbatim (the
lexer and #command machinery still need it) and stop scanning so
the embedded /* can't open a block comment.

Two regression tests cover both markers. Full mandatory test suite
(go test ./..., FiveSql2 43/43, compat 56/56, std.ch 17/17, FRB 7/7,
pgserver 11/11) still passes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CharlesKWON
2026-05-29 08:47:55 +09:00
parent 675eaa4def
commit a8f6e53785
2 changed files with 49 additions and 0 deletions

View File

@@ -659,6 +659,10 @@ func (pp *Preprocessor) applyRules(line string) string {
// stripBlockComments removes /* ... */ comments from a line.
// If a /* is found without closing */, sets inBlock to true.
//
// `//` and `&&` line-comment markers are detected first so a `/*`
// substring inside one of them (e.g. `// see app/api/*.prg`) doesn't
// start a runaway block comment that eats subsequent lines.
func stripBlockComments(line string, inBlock *bool) string {
var out strings.Builder
i := 0
@@ -679,6 +683,20 @@ func stripBlockComments(line string, inBlock *bool) string {
i++
continue
}
// // line comment — copy the rest of the line through verbatim
// (the lexer/`#command` machinery still needs to see it) but
// don't scan it for `/*` so an embedded `/*` substring (e.g.
// `// see app/api/*.prg`) can't open a runaway block comment
// that eats subsequent lines.
if i+1 < len(line) && line[i] == '/' && line[i+1] == '/' {
out.WriteString(line[i:])
return out.String()
}
// && Harbour-style line comment — same rule.
if i+1 < len(line) && line[i] == '&' && line[i+1] == '&' {
out.WriteString(line[i:])
return out.String()
}
// Block comment start
if i+1 < len(line) && line[i] == '/' && line[i+1] == '*' {
// Find closing */

View File

@@ -262,3 +262,34 @@ func TestMissingInclude(t *testing.T) {
t.Error("code after missing include should continue")
}
}
// TestSlashStarInsideLineComment locks in that a `/*` substring inside
// a `//` line comment does NOT open a runaway block comment. Before
// the fix, a comment like "// see app/api/*.prg" would start a block
// comment from `/*.prg` that ate every subsequent line until EOF or
// the next `*/`, silently dropping FUNCTION declarations.
func TestSlashStarInsideLineComment(t *testing.T) {
p := New()
src := `// see app/api/*.prg for the glob
FUNCTION Foo()
RETURN 1
`
result, _ := p.Process("test.prg", src)
if !strings.Contains(result, "FUNCTION Foo") {
t.Errorf("FUNCTION Foo() should survive a // line comment that contains /*; got:\n%s", result)
}
}
// TestDoubleAmpInsideLineComment — same protection for Harbour's `&&`
// line comment marker.
func TestDoubleAmpInsideLineComment(t *testing.T) {
p := New()
src := `&& glob /*.prg
FUNCTION Foo()
RETURN 1
`
result, _ := p.Process("test.prg", src)
if !strings.Contains(result, "FUNCTION Foo") {
t.Errorf("FUNCTION Foo() should survive a && line comment that contains /*; got:\n%s", result)
}
}