|
package main |
|
|
|
import ( |
|
"bufio" |
|
"bytes" |
|
"encoding/json" |
|
"fmt" |
|
"io" |
|
"log/slog" |
|
"os" |
|
"path" |
|
"slices" |
|
|
|
"github.com/morikuni/failure" |
|
) |
|
|
|
type Violation struct { |
|
CheckName string `json:"check_name"` |
|
Description string `json:"description"` |
|
Fingerprint string `json:"fingerprint"` |
|
Severity string `json:"severity"` |
|
Location struct { |
|
Path string `json:"path"` |
|
Lines struct { |
|
Begin int `json:"begin"` |
|
End int `json:"end"` |
|
} `json:"lines"` |
|
} `json:"location"` |
|
} |
|
|
|
func initLogger() { |
|
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{ |
|
AddSource: true, |
|
Level: slog.LevelInfo, |
|
ReplaceAttr: nil, |
|
})) |
|
slog.SetDefault(logger) |
|
} |
|
|
|
func fixFile(filePath string) error { |
|
whitespaceSensitiveFileExts := []string{".md", ".mdx"} |
|
ext := path.Ext(filePath) |
|
|
|
fin, err := os.OpenFile(filePath, os.O_RDWR, 0) |
|
if err != nil { |
|
|
|
return failure.MarkUnexpected(err) |
|
} |
|
defer fin.Close() |
|
buf, err := io.ReadAll(fin) |
|
if err != nil { |
|
return failure.MarkUnexpected(err) |
|
} |
|
fixed := bytes.NewBuffer(make([]byte, 0, len(buf))) |
|
scanner := bufio.NewScanner(bytes.NewBuffer(buf)) |
|
var lastLineLen int |
|
for index := 0; scanner.Scan(); index++ { |
|
if index != 0 { |
|
fixed.WriteByte('\n') |
|
} |
|
err = scanner.Err() |
|
if err != nil { |
|
return failure.MarkUnexpected(err) |
|
} |
|
line := scanner.Bytes() |
|
if !slices.Contains(whitespaceSensitiveFileExts, ext) { |
|
line = bytes.TrimRight(line, " ") |
|
} |
|
fixed.Write(line) |
|
lastLineLen = len(line) |
|
} |
|
if lastLineLen != 0 { |
|
fixed.WriteByte('\n') |
|
} |
|
|
|
_, err = fin.Seek(0, io.SeekStart) |
|
if err != nil { |
|
return failure.MarkUnexpected(err) |
|
} |
|
err = fin.Truncate(0) |
|
if err != nil { |
|
return failure.MarkUnexpected(err) |
|
} |
|
_, err = io.Copy(fin, fixed) |
|
if err != nil { |
|
return failure.MarkUnexpected(err) |
|
} |
|
err = fin.Sync() |
|
if err != nil { |
|
return failure.MarkUnexpected(err) |
|
} |
|
return nil |
|
} |
|
|
|
func main() { |
|
if len(os.Args) != 2 { |
|
fmt.Printf("Usage: %s <json_file> \n", os.Args[0]) |
|
os.Exit(1) |
|
} |
|
fin, err := os.Open(os.Args[1]) |
|
if err != nil { |
|
slog.Error("Error opening file", "error", err) |
|
os.Exit(1) |
|
} |
|
defer fin.Close() |
|
var violations []Violation |
|
err = json.NewDecoder(fin).Decode(&violations) |
|
if err != nil { |
|
slog.Error("Error decoding JSON", "error", err) |
|
os.Exit(1) |
|
} |
|
violationPaths := make([]string, 0, 100) |
|
|
|
for _, violation := range violations { |
|
if violation.CheckName != "editorconfig-checker" { |
|
slog.Info("unknown checker, skip fix", "checker", violation.CheckName) |
|
continue |
|
} |
|
switch violation.Description { |
|
case "Wrong line endings or no final newline", "Trailing whitespace": |
|
violationPaths = append(violationPaths, violation.Location.Path) |
|
default: |
|
slog.Error("Unknown description", "description", violation.Description) |
|
} |
|
} |
|
for _, filePath := range violationPaths { |
|
err = fixFile(filePath) |
|
if err != nil { |
|
slog.Error("Error fixing line", "error", err) |
|
os.Exit(1) |
|
} |
|
} |
|
slog.Info("all violations fixed.") |
|
} |