From b15244a37c6bb15f1b3fa6daa5b5432ee7408ef4 Mon Sep 17 00:00:00 2001 From: Fuwn Date: Wed, 11 Feb 2026 10:44:05 +0000 Subject: fix(adapter): Suppress blank lines before continuation keywords (else/catch/finally) --- adapter_ecmascript.go | 53 ++++++++++++++++------------- adapter_ecmascript_test.go | 85 ++++++++++++++++++++++++---------------------- engine/engine.go | 2 +- engine/event.go | 1 + 4 files changed, 75 insertions(+), 66 deletions(-) diff --git a/adapter_ecmascript.go b/adapter_ecmascript.go index 9e0b66d..29f99b7 100644 --- a/adapter_ecmascript.go +++ b/adapter_ecmascript.go @@ -97,12 +97,13 @@ func (a *EcmaScriptAdapter) Analyze(source []byte) ([]byte, []engine.LineEvent, continue } - statementType, isScoped := classifyEcmaScriptStatement(trimmedContent) + statementType, isScoped, isContinuation := classifyEcmaScriptStatement(trimmedContent) if statementType != "" { event.HasASTInfo = true event.StatementType = statementType event.IsScoped = isScoped + event.IsContinuation = isContinuation event.IsTopLevel = ecmaScriptLineIsTopLevel(currentLine) event.IsStartLine = true } else { @@ -117,7 +118,7 @@ func (a *EcmaScriptAdapter) Analyze(source []byte) ([]byte, []engine.LineEvent, return source, events, nil } -func classifyEcmaScriptStatement(trimmedLine string) (string, bool) { +func classifyEcmaScriptStatement(trimmedLine string) (string, bool, bool) { classified := trimmedLine if strings.HasPrefix(classified, "export default ") { @@ -136,50 +137,54 @@ func classifyEcmaScriptStatement(trimmedLine string) (string, bool) { switch { case ecmaScriptStatementHasPrefix(classified, "function"): - return "function", true + return "function", true, false case ecmaScriptStatementHasPrefix(classified, "class"): - return "class", true + return "class", true, false case ecmaScriptStatementHasPrefix(classified, "if"): - return "if", true + return "if", true, false case ecmaScriptStatementHasPrefix(classified, "else"): - return "if", true + return "if", true, true case ecmaScriptStatementHasPrefix(classified, "for"): - return "for", true + return "for", true, false case ecmaScriptStatementHasPrefix(classified, "while"): - return "while", true + return "while", true, false case ecmaScriptStatementHasPrefix(classified, "do"): - return "do", true + return "do", true, false case ecmaScriptStatementHasPrefix(classified, "switch"): - return "switch", true + return "switch", true, false case ecmaScriptStatementHasPrefix(classified, "try"): - return "try", true + return "try", true, false + case ecmaScriptStatementHasPrefix(classified, "catch"): + return "try", true, true + case ecmaScriptStatementHasPrefix(classified, "finally"): + return "try", true, true case ecmaScriptStatementHasPrefix(classified, "interface"): - return "interface", true + return "interface", true, false case ecmaScriptStatementHasPrefix(classified, "enum"): - return "enum", true + return "enum", true, false case ecmaScriptStatementHasPrefix(classified, "namespace"): - return "namespace", true + return "namespace", true, false case ecmaScriptStatementHasPrefix(classified, "const"): - return "const", false + return "const", false, false case ecmaScriptStatementHasPrefix(classified, "let"): - return "let", false + return "let", false, false case ecmaScriptStatementHasPrefix(classified, "var"): - return "var", false + return "var", false, false case ecmaScriptStatementHasPrefix(classified, "import"): - return "import", false + return "import", false, false case ecmaScriptStatementHasPrefix(classified, "type"): - return "type", false + return "type", false, false case ecmaScriptStatementHasPrefix(classified, "return"): - return "return", false + return "return", false, false case ecmaScriptStatementHasPrefix(classified, "throw"): - return "throw", false + return "throw", false, false case ecmaScriptStatementHasPrefix(classified, "await"): - return "await", false + return "await", false, false case ecmaScriptStatementHasPrefix(classified, "yield"): - return "yield", false + return "yield", false, false } - return "", false + return "", false, false } func ecmaScriptStatementHasPrefix(line string, keyword string) bool { diff --git a/adapter_ecmascript_test.go b/adapter_ecmascript_test.go index ee739ac..30bc165 100644 --- a/adapter_ecmascript_test.go +++ b/adapter_ecmascript_test.go @@ -411,52 +411,55 @@ func TestEcmaScriptAdapter(t *testing.T) { func TestClassifyEcmaScriptStatement(t *testing.T) { cases := []struct { - input string - expectedType string - expectedScope bool + input string + expectedType string + expectedScope bool + expectedContinuation bool }{ - {"function foo() {", "function", true}, - {"async function foo() {", "function", true}, - {"export function foo() {", "function", true}, - {"export default function() {", "function", true}, - {"class Foo {", "class", true}, - {"export class Foo {", "class", true}, - {"if (x) {", "if", true}, - {"else if (y) {", "if", true}, - {"else {", "if", true}, - {"for (const x of items) {", "for", true}, - {"while (true) {", "while", true}, - {"do {", "do", true}, - {"switch (x) {", "switch", true}, - {"try {", "try", true}, - {"interface Foo {", "interface", true}, - {"enum Direction {", "enum", true}, - {"namespace Foo {", "namespace", true}, - {"const x = 1;", "const", false}, - {"let x = 1;", "let", false}, - {"var x = 1;", "var", false}, - {"import { foo } from 'bar';", "import", false}, - {"type Foo = string;", "type", false}, - {"return x;", "return", false}, - {"return;", "return", false}, - {"throw new Error();", "throw", false}, - {"await fetch(url);", "await", false}, - {"yield value;", "yield", false}, - {"export const x = 1;", "const", false}, - {"export default class Foo {", "class", true}, - {"declare const x: number;", "const", false}, - {"declare function foo(): void;", "function", true}, - {"foo();", "", false}, - {"x = 1;", "", false}, - {"", "", false}, + {"function foo() {", "function", true, false}, + {"async function foo() {", "function", true, false}, + {"export function foo() {", "function", true, false}, + {"export default function() {", "function", true, false}, + {"class Foo {", "class", true, false}, + {"export class Foo {", "class", true, false}, + {"if (x) {", "if", true, false}, + {"else if (y) {", "if", true, true}, + {"else {", "if", true, true}, + {"for (const x of items) {", "for", true, false}, + {"while (true) {", "while", true, false}, + {"do {", "do", true, false}, + {"switch (x) {", "switch", true, false}, + {"try {", "try", true, false}, + {"catch (e) {", "try", true, true}, + {"finally {", "try", true, true}, + {"interface Foo {", "interface", true, false}, + {"enum Direction {", "enum", true, false}, + {"namespace Foo {", "namespace", true, false}, + {"const x = 1;", "const", false, false}, + {"let x = 1;", "let", false, false}, + {"var x = 1;", "var", false, false}, + {"import { foo } from 'bar';", "import", false, false}, + {"type Foo = string;", "type", false, false}, + {"return x;", "return", false, false}, + {"return;", "return", false, false}, + {"throw new Error();", "throw", false, false}, + {"await fetch(url);", "await", false, false}, + {"yield value;", "yield", false, false}, + {"export const x = 1;", "const", false, false}, + {"export default class Foo {", "class", true, false}, + {"declare const x: number;", "const", false, false}, + {"declare function foo(): void;", "function", true, false}, + {"foo();", "", false, false}, + {"x = 1;", "", false, false}, + {"", "", false, false}, } for _, testCase := range cases { - statementType, isScoped := classifyEcmaScriptStatement(testCase.input) + statementType, isScoped, isContinuation := classifyEcmaScriptStatement(testCase.input) - if statementType != testCase.expectedType || isScoped != testCase.expectedScope { - t.Errorf("classifyEcmaScriptStatement(%q) = (%q, %v), want (%q, %v)", - testCase.input, statementType, isScoped, testCase.expectedType, testCase.expectedScope) + if statementType != testCase.expectedType || isScoped != testCase.expectedScope || isContinuation != testCase.expectedContinuation { + t.Errorf("classifyEcmaScriptStatement(%q) = (%q, %v, %v), want (%q, %v, %v)", + testCase.input, statementType, isScoped, isContinuation, testCase.expectedType, testCase.expectedScope, testCase.expectedContinuation) } } } diff --git a/engine/engine.go b/engine/engine.go index 2ce21f3..0b37dd4 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -49,7 +49,7 @@ func (e *Engine) format(events []LineEvent, resultBuilder *strings.Builder) { currentIsTopLevel := event.HasASTInfo && event.IsTopLevel currentIsScoped := event.HasASTInfo && event.IsScoped - if hasWrittenContent && !previousWasOpenBrace && !event.IsClosingBrace && !event.IsCaseLabel { + if hasWrittenContent && !previousWasOpenBrace && !event.IsClosingBrace && !event.IsCaseLabel && !event.IsContinuation { if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType { if !(e.CommentMode == CommentsFollow && previousWasComment) { needsBlankLine = true diff --git a/engine/event.go b/engine/event.go index e9253e8..18f9eb2 100644 --- a/engine/event.go +++ b/engine/event.go @@ -13,6 +13,7 @@ type LineEvent struct { IsClosingBrace bool IsOpeningBrace bool IsCaseLabel bool + IsContinuation bool IsCommentOnly bool IsBlank bool InRawString bool -- cgit v1.2.3