Skip to content

Commit

Permalink
Merge pull request #1 from ldemailly/initial
Browse files Browse the repository at this point in the history
Initial / start of implementation
  • Loading branch information
ldemailly committed Mar 23, 2024
2 parents e5e45ff + b56cd66 commit 4209ea7
Show file tree
Hide file tree
Showing 11 changed files with 365 additions and 0 deletions.
16 changes: 16 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates

version: 2
updates:
- package-ecosystem: "gomod"
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
32 changes: 32 additions & 0 deletions .github/workflows/include.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Remember to change/update `description` below when copying
# this include
name: "Shared cli/server fortio workflows"
on:
push:
branches: [ main ]
tags:
# so a vX.Y.Z-test1 doesn't trigger build
- 'v[0-9]+.[0-9]+.[0-9]+'
- 'v[0-9]+.[0-9]+.[0-9]+-pre*'
pull_request:
branches: [ main ]

jobs:
call-gochecks:
uses: fortio/workflows/.github/workflows/gochecks.yml@main
call-codecov:
uses: fortio/workflows/.github/workflows/codecov.yml@main
call-codeql:
uses: fortio/workflows/.github/workflows/codeql-analysis.yml@main
permissions:
actions: read
contents: read
security-events: write
call-releaser:
uses: fortio/workflows/.github/workflows/releaser.yml@main
with:
description: "Fix line too long lll linter errors"
secrets:
GH_PAT: ${{ secrets.GH_PAT }}
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
DOCKER_USER: ${{ secrets.DOCKER_USER }}
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
.golangci.yml
.goreleaser.yaml
*.lll
*.bak
test.txt
lll-fixer
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
Expand Down
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM scratch
COPY lll-fixer /usr/bin/lll-fixer
ENTRYPOINT ["/usr/bin/lll-fixer"]
CMD ["help"]
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

manual-test:
cp lll_fixer.go test.txt && go run . -loglevel debug test.txt ; colordiff -u lll_fixer.go test.txt


.PHONY: manual-test
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,41 @@
# lll-fixer
Fix lll (line length limit) lines too long linter errors in go files

## Installation

From source
```
go install github.com/ldemailly/lll-fixer@latest
```

Or see the numerous binaries in https://github.com/ldemailly/lll-fixer/releases

Or docker `fortio/lll-fixer:latest`

Or brew `brew install fortio/tap/lll-fixer`

(I manage the fortio org and usually put everything there but this one is a bit unrelated so for now it is hosted here in `ldemailly` yet uses fortio's org for docker and brew)

## Example

Test on itself:
```
$ go run . lll_fixer.go
```

```diff
diff --git a/lll_fixer.go b/lll_fixer.go
index c5edf97..30452c3 100644
--- a/lll_fixer.go
+++ b/lll_fixer.go
@@ -43,7 +43,8 @@ func main() {
}
}

-// process modifies the file filename to split long comments at maxlen. making this line longer than 80 characters to test.
+// process modifies the file filename to split long comments at maxlen. making
+// this line longer than 80 characters to test.
func process(fset *token.FileSet, filename string, maxlen int) string {
log.Infof("Processing file %q", filename)
// Parse the Go file
```
44 changes: 44 additions & 0 deletions bug_repro/bug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

import (
"fmt"
"go/format"
"go/parser"
"go/token"
"os"
)

/*
* multi line comment.
*/
func main() {
code := `package main
/*
* multi line comment.
*/
func main() {
}`
fmt.Println("---input---")
fmt.Println(code)
fmt.Println("---processing---")
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, "bug.go", code, parser.ParseComments)
if err != nil {
panic(err)
}
for _, cg := range node.Comments {
for _, c := range cg.List {
fmt.Printf("Found comment %q\n", c.Text)
if len(c.Text) > 11 {
fmt.Printf("Splitting comment %q\n", c.Text)
c.Text = c.Text[:11] + "\n *" + c.Text[11:]
fmt.Printf("into -> %q\n", c.Text)
}
}
}
fmt.Println("---result---")
if err := format.Node(os.Stdout, fset, node); err != nil {
panic(err)
}
}
13 changes: 13 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module github.com/ldemailly/lll-fixer

go 1.21

require (
fortio.org/cli v1.5.1
fortio.org/log v1.12.0
)

require (
fortio.org/struct2env v0.4.0 // indirect
fortio.org/version v1.0.3 // indirect
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
fortio.org/cli v1.5.1 h1:lqPvkxRVSajsVwLfblaN62BPAICPp05Oab+yjRvI3DU=
fortio.org/cli v1.5.1/go.mod h1:Tp7AypudP1mJomTUN/J/vlOTlZDWTMsok09MMyA99ow=
fortio.org/log v1.12.0 h1:5Yg4pL9Pp0jcWeJYixm2xikMCldVaSDMgDFDmQJZfho=
fortio.org/log v1.12.0/go.mod h1:1tMBG/Elr6YqjmJCWiejJp2FPvXg7/9UAN0Rfpkyt1o=
fortio.org/struct2env v0.4.0 h1:k5alSOTf3YHiB3MuacjDHQ3YhVWvNZ95ZP/a6MqvyLo=
fortio.org/struct2env v0.4.0/go.mod h1:lENUe70UwA1zDUCX+8AsO663QCFqYaprk5lnPhjD410=
fortio.org/version v1.0.3 h1:5gJ3plj6isAOMq52cI5ifo4cC+QHmJF76Wevc5Cp4x0=
fortio.org/version v1.0.3/go.mod h1:2JQp9Ax+tm6QKiGuzR5nJY63kFeANcgrZ0osoQFDVm0=
147 changes: 147 additions & 0 deletions lll_fixer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package main

import (
"flag"
"go/ast"
"go/format"
"go/parser"
"go/token"
"os"
"os/exec"
"strings"

"fortio.org/cli"
"fortio.org/log"
)

func main() {
maxlen := flag.Int("len", 79, "max line length")
funmpt := flag.Bool("fumpt", false, "run gofumpt on the modified file")
cli.MinArgs = 1
cli.MaxArgs = -1
cli.ArgsHelp = "filenames..."
if false {
// just to test literal string split
cli.ArgsHelp = "this is a very a long string literal to test the split of long strings inside code"
}
cli.Main()
fset := token.NewFileSet()
for _, filename := range flag.Args() {
newname := process(fset, filename, *maxlen)
// swap .lll to .go and .go to .bak
backup := filename + ".bak"
if err := os.Rename(filename, backup); err != nil {
log.Fatalf("Error renaming file %q to %q: %v", filename, backup, err)
}
log.Infof("Renamed file %q to %q", filename, backup)
if err := os.Rename(newname, filename); err != nil {
log.Fatalf("Error renaming file %q to %q: %v", newname, filename, err)
}
log.Infof("Renamed file %q to %q", newname, filename)
// Run gofumpt on the modified file
if *funmpt {
cmd := exec.Command("gofumpt", "-w", filename)
if err := cmd.Run(); err != nil {
log.Errf("Error running gofumpt: %v", err)
return
}
log.Infof("Ran gofumpt on the now modified file %q", filename)
}
}
}

func lineLead(s string) string {
i := strings.LastIndex(s, "\n")
return s[i+1 : i+4] // will break with -len too low
}

/*
* Multi line comment with one line longer than 80 characters to test the split of multi line comments.
*/
func splitAtWord(s string, maxlen int) string {
if len(s) <= maxlen {
return s
}
// find the last space before maxlen
i := strings.LastIndex(s[:maxlen], " ")
nospace := (i == -1)
if nospace {
// no space found, split at maxlen
log.Warnf("No word/space found in first %d characters for %q", maxlen, s)
i = maxlen
}
start := s[:i]
lead := lineLead(start)
var mid string
switch {
case strings.HasPrefix(lead, "/* "):
mid = "\n * "
case strings.HasPrefix(lead, " * "):
mid = "\n * "
case strings.HasPrefix(lead, "// "):
mid = "\n// "
case strings.HasPrefix(lead, "\""):
mid = "\" +\n\t\"" // for string literals splitting
if !nospace {
mid += " "
}
default:
log.Warnf("Unexpected lead %q", lead)
mid = "\n "
}
log.Debugf("Start lead is %q", lead)
return strings.TrimSpace(s[:i]) + mid + strings.TrimLeft(s[i:], " ")
}

// TODO process other nodes (and also maybe split leftmost position in line vs length of comment/literal
// which could be far to the right)

func processNode(n ast.Node, maxlen int) bool {
if false {
log.Debugf("Found node: %+v to shrink to %d", n, maxlen)
}
// process string literals
if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING {
lit.Value = splitAtWord(lit.Value, maxlen)
}
// more nodes...
return true
}

// process modifies the file filename to split long comments at maxlen. making this line longer than 80 characters to test.
func process(fset *token.FileSet, filename string, maxlen int) string {
log.Infof("Processing file %q", filename)
// Parse the Go file and this is an indented comment to test the split past column 80.
node, err := parser.ParseFile(fset, filename, nil, parser.ParseComments)
if err != nil {
log.Fatalf("Error parsing %q: %v", filename, err)
return "error.lll" // unreachable
}
for _, cg := range node.Comments {
for _, c := range cg.List {
log.Debugf("Found comment %q", c.Text)
if len(c.Text) > maxlen {
log.LogVf("Splitting comment %q", c.Text)
c.Text = splitAtWord(c.Text, maxlen)
log.LogVf("into -> %q", c.Text)
}
}
}
// Traverse and modify the AST
ast.Inspect(node, func(n ast.Node) bool {
return processNode(n, maxlen)
})

// Generate the modified code
newname := filename + ".lll"
f, err := os.Create(newname)
if err != nil {
log.Errf("Error creating modified file %q: %v", newname, err)
}
defer f.Close()
if err := format.Node(f, fset, node); err != nil {
log.Errf("Error outputting modified file: %v", err)
}
log.Infof("Modified file written to %q", newname)
return newname
}
50 changes: 50 additions & 0 deletions lll_fixer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package main

import (
"testing"
)

func TestLineLead(t *testing.T) {
// tests input vs actual struct:
tests := []struct {
input string
expected string
}{
{"Hello", "Hel"},
{"\nHello", "Hel"},
{"abc\nxyz\nTest", "Tes"},
}
// loop through the tests
for _, test := range tests {
actual := lineLead(test.input)
// compare the actual vs expected
if actual != test.expected {
t.Errorf("Test failed! Expected: %q, Actual: %q", test.expected, actual)
}
}
}

func TestSplitAtWord(t *testing.T) {
tests := []struct {
input string
expected string
}{
{"Hello", "Hello"},
{"123456789ABC", "123456789A\n BC"},
{"// abc 1234567890", "// abc\n// 1234567890"},
{"/* abc 1234567890 */", "/* abc\n * 1234567890 */"},
{"/*\n * abc 1234567890\n*/", "/*\n * abc\n * 1234567890\n*/"},
{`"abc 1234567890"`, `"abc" +
" 1234567890"`},
{`"123456789ABC"`, `"123456789" +
"ABC"`},
}
// loop through the tests
for _, test := range tests {
actual := splitAtWord(test.input, 10)
// compare the actual vs expected
if actual != test.expected {
t.Errorf("Test failed! Expected: %q, Actual: %q", test.expected, actual)
}
}
}

0 comments on commit 4209ea7

Please sign in to comment.