馃殌 Grammar-Aware Code Formatter: Structure through separation (supports Go, JavaScript, TypeScript, JSX, and TSX)
go formatter code-formatter javascript typescript jsx tsx
at main 156 lines 4.3 kB view raw
1package engine 2 3import "strings" 4 5type CommentMode int 6 7const ( 8 CommentsFollow CommentMode = iota 9 CommentsPrecede 10 CommentsStandalone 11) 12 13type Engine struct { 14 CommentMode CommentMode 15 GroupSingleLineScopes bool 16} 17 18func (e *Engine) format(events []LineEvent, resultBuilder *strings.Builder) { 19 hasWrittenContent := false 20 previousWasOpenBrace := false 21 previousStatementType := "" 22 previousWasComment := false 23 previousWasTopLevel := false 24 previousWasScoped := false 25 previousWasSingleLineScope := false 26 27 for eventIndex, event := range events { 28 if event.InRawString { 29 if hasWrittenContent { 30 resultBuilder.WriteByte('\n') 31 } 32 33 resultBuilder.WriteString(event.Content) 34 35 hasWrittenContent = true 36 37 continue 38 } 39 40 if event.IsBlank { 41 continue 42 } 43 44 currentStatementType := event.StatementType 45 46 if event.IsPackageDecl { 47 currentStatementType = "package" 48 } 49 50 needsBlankLine := false 51 currentIsTopLevel := event.HasASTInfo && event.IsTopLevel 52 currentIsScoped := event.HasASTInfo && event.IsScoped 53 currentIsSingleLineScope := currentIsScoped && !event.IsOpeningBrace && !event.IsClosingBrace 54 55 if hasWrittenContent && !previousWasOpenBrace && !event.IsClosingBrace && !event.IsCaseLabel && !event.IsContinuation { 56 if currentIsTopLevel && previousWasTopLevel && currentStatementType != previousStatementType { 57 if e.CommentMode != CommentsFollow || !previousWasComment { 58 needsBlankLine = true 59 } 60 } else if event.HasASTInfo && (currentIsScoped || previousWasScoped) { 61 if e.GroupSingleLineScopes && currentIsSingleLineScope && previousWasSingleLineScope && currentStatementType == previousStatementType { 62 needsBlankLine = false 63 } else if e.CommentMode != CommentsFollow || !previousWasComment { 64 needsBlankLine = true 65 } 66 } else if currentStatementType != "" && previousStatementType != "" && currentStatementType != previousStatementType { 67 if e.CommentMode != CommentsFollow || !previousWasComment { 68 needsBlankLine = true 69 } 70 } 71 72 if e.CommentMode == CommentsFollow && event.IsCommentOnly && !previousWasComment { 73 nextIndex := e.findNextNonComment(events, eventIndex+1) 74 75 if nextIndex >= 0 { 76 nextNonCommentEvent := events[nextIndex] 77 78 if nextNonCommentEvent.HasASTInfo { 79 nextIsTopLevel := nextNonCommentEvent.IsTopLevel 80 nextIsScoped := nextNonCommentEvent.IsScoped 81 82 if nextIsTopLevel && previousWasTopLevel && nextNonCommentEvent.StatementType != previousStatementType { 83 needsBlankLine = true 84 } else if nextIsScoped || previousWasScoped { 85 needsBlankLine = true 86 } else if nextNonCommentEvent.StatementType != "" && previousStatementType != "" && nextNonCommentEvent.StatementType != previousStatementType { 87 needsBlankLine = true 88 } 89 } 90 } 91 } 92 } 93 94 if needsBlankLine { 95 resultBuilder.WriteByte('\n') 96 } 97 98 if hasWrittenContent { 99 resultBuilder.WriteByte('\n') 100 } 101 102 resultBuilder.WriteString(event.Content) 103 104 hasWrittenContent = true 105 previousWasOpenBrace = event.IsOpeningBrace || event.IsCaseLabel 106 previousWasComment = event.IsCommentOnly 107 108 if event.HasASTInfo { 109 previousStatementType = event.StatementType 110 previousWasTopLevel = event.IsTopLevel 111 previousWasScoped = event.IsScoped 112 previousWasSingleLineScope = currentIsSingleLineScope 113 } else if currentStatementType != "" { 114 previousStatementType = currentStatementType 115 previousWasTopLevel = false 116 previousWasScoped = false 117 previousWasSingleLineScope = false 118 } 119 } 120 121 resultBuilder.WriteByte('\n') 122} 123 124func (e *Engine) FormatToString(events []LineEvent) string { 125 var resultBuilder strings.Builder 126 127 resultBuilder.Grow(len(events) * 40) 128 e.format(events, &resultBuilder) 129 130 return resultBuilder.String() 131} 132 133func (e *Engine) FormatToBytes(events []LineEvent) []byte { 134 var resultBuilder strings.Builder 135 136 resultBuilder.Grow(len(events) * 40) 137 e.format(events, &resultBuilder) 138 139 return []byte(resultBuilder.String()) 140} 141 142func (e *Engine) findNextNonComment(events []LineEvent, startIndex int) int { 143 for eventIndex := startIndex; eventIndex < len(events); eventIndex++ { 144 if events[eventIndex].IsBlank { 145 continue 146 } 147 148 if events[eventIndex].IsCommentOnly { 149 continue 150 } 151 152 return eventIndex 153 } 154 155 return -1 156}