@@ -1390,10 +1390,13 @@ func (ctx *parseContext) mapForStmt(stmt *ast.ForStmt) *tree.ForLoop {
13901390 // Go's AST normalizes `for ; cond; {}` to Init=nil, Post=nil, same as `for cond {}`.
13911391 // We detect semicolons by looking at the source text between for keyword and body.
13921392 is3Clause := stmt .Init != nil || stmt .Post != nil
1393+ bodyStart := int (stmt .Body .Lbrace ) - ctx .file .Base ()
13931394 if ! is3Clause {
1394- // Check for semicolons in the source between cursor and the body brace
1395- bodyStart := int (stmt .Body .Lbrace ) - ctx .file .Base ()
1396- if ctx .findNextBefore (';' , bodyStart ) >= 0 {
1395+ // `for ; cond; {}` has no Init/Post in the AST but is still
1396+ // syntactically 3-clause. Detect by scanning for `;` in the
1397+ // header — but skip rune/string literals so a `';'` inside the
1398+ // condition (e.g. `for tok != ';'`) isn't mistaken for one.
1399+ if ctx .findNextPositionOf (';' , bodyStart ) >= 0 {
13971400 is3Clause = true
13981401 }
13991402 }
@@ -1402,7 +1405,7 @@ func (ctx *parseContext) mapForStmt(stmt *ast.ForStmt) *tree.ForLoop {
14021405 // 3-clause for: for [init]; [cond]; [post] {}
14031406 if stmt .Init != nil {
14041407 init := ctx .mapStmt (stmt .Init )
1405- semicolonOffset := ctx .findNext (';' )
1408+ semicolonOffset := ctx .findNextPositionOf (';' , bodyStart )
14061409 var after tree.Space
14071410 if semicolonOffset >= 0 {
14081411 after = ctx .prefix (ctx .file .Pos (semicolonOffset ))
@@ -1412,7 +1415,7 @@ func (ctx *parseContext) mapForStmt(stmt *ast.ForStmt) *tree.ForLoop {
14121415 control .Init = & initRP
14131416 } else {
14141417 // No init but semicolons present: `for ; cond; post {}`
1415- semicolonOffset := ctx .findNext (';' )
1418+ semicolonOffset := ctx .findNextPositionOf (';' , bodyStart )
14161419 var after tree.Space
14171420 if semicolonOffset >= 0 {
14181421 after = ctx .prefix (ctx .file .Pos (semicolonOffset ))
@@ -1424,7 +1427,7 @@ func (ctx *parseContext) mapForStmt(stmt *ast.ForStmt) *tree.ForLoop {
14241427
14251428 if stmt .Cond != nil {
14261429 cond := ctx .mapExpr (stmt .Cond )
1427- semicolonOffset := ctx .findNext (';' )
1430+ semicolonOffset := ctx .findNextPositionOf (';' , bodyStart )
14281431 after := tree .EmptySpace
14291432 if semicolonOffset >= 0 {
14301433 after = ctx .prefix (ctx .file .Pos (semicolonOffset ))
@@ -1433,7 +1436,7 @@ func (ctx *parseContext) mapForStmt(stmt *ast.ForStmt) *tree.ForLoop {
14331436 condRP := tree.RightPadded [tree.Expression ]{Element : cond , After : after }
14341437 control .Condition = & condRP
14351438 } else {
1436- semicolonOffset := ctx .findNext (';' )
1439+ semicolonOffset := ctx .findNextPositionOf (';' , bodyStart )
14371440 after := tree .EmptySpace
14381441 if semicolonOffset >= 0 {
14391442 after = ctx .prefix (ctx .file .Pos (semicolonOffset ))
@@ -2877,6 +2880,65 @@ func (ctx *parseContext) findNextBefore(ch byte, before int) int {
28772880 return - 1
28782881}
28792882
2883+ // findNextPositionOf is like findNextBefore but skips over Go rune
2884+ // literals ('...'), interpreted string literals ("..."), raw string literals
2885+ // (`...`), and `//` / `/* */` comments while scanning. A `before` of 0 means
2886+ // scan to end of src. Used for syntactic markers like `;` in a `for` header
2887+ // that can otherwise hide inside a `';'` rune literal or a `/* ; */` comment.
2888+ func (ctx * parseContext ) findNextPositionOf (ch byte , before int ) int {
2889+ end := len (ctx .src )
2890+ if before > 0 && before < end {
2891+ end = before
2892+ }
2893+ i := ctx .cursor
2894+ for i < end {
2895+ b := ctx .src [i ]
2896+ switch {
2897+ case b == '\'' || b == '"' :
2898+ quote := b
2899+ i ++
2900+ for i < end {
2901+ c := ctx .src [i ]
2902+ if c == '\\' && i + 1 < end {
2903+ i += 2
2904+ continue
2905+ }
2906+ i ++
2907+ if c == quote {
2908+ break
2909+ }
2910+ }
2911+ case b == '`' :
2912+ i ++
2913+ for i < end && ctx .src [i ] != '`' {
2914+ i ++
2915+ }
2916+ if i < end {
2917+ i ++
2918+ }
2919+ case b == '/' && i + 1 < end && ctx .src [i + 1 ] == '/' :
2920+ i += 2
2921+ for i < end && ctx .src [i ] != '\n' {
2922+ i ++
2923+ }
2924+ case b == '/' && i + 1 < end && ctx .src [i + 1 ] == '*' :
2925+ i += 2
2926+ for i + 1 < end && ! (ctx .src [i ] == '*' && ctx .src [i + 1 ] == '/' ) {
2927+ i ++
2928+ }
2929+ if i + 1 < end {
2930+ i += 2
2931+ }
2932+ default :
2933+ if b == ch {
2934+ return i
2935+ }
2936+ i ++
2937+ }
2938+ }
2939+ return - 1
2940+ }
2941+
28802942// findNextString finds the next occurrence of s from the current cursor.
28812943func (ctx * parseContext ) findNextString (s string ) int {
28822944 idx := strings .Index (string (ctx .src [ctx .cursor :]), s )
0 commit comments