aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-11 10:44:05 +0000
committerFuwn <[email protected]>2026-02-11 10:44:05 +0000
commitb15244a37c6bb15f1b3fa6daa5b5432ee7408ef4 (patch)
tree765db0f99dc54dc84f94c818842171c3bf7c7953
parentperf(engine): Write formatted output directly into strings.Builder (diff)
downloadiku-b15244a37c6bb15f1b3fa6daa5b5432ee7408ef4.tar.xz
iku-b15244a37c6bb15f1b3fa6daa5b5432ee7408ef4.zip
fix(adapter): Suppress blank lines before continuation keywords (else/catch/finally)
-rw-r--r--adapter_ecmascript.go53
-rw-r--r--adapter_ecmascript_test.go85
-rw-r--r--engine/engine.go2
-rw-r--r--engine/event.go1
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