[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: Test\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  test:\n    name: Test\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-go@v2\n        with:\n          go-version: 1.19\n      - name: Cache\n        uses: actions/cache@v2\n        with:\n          path: |\n            ~/go/bin\n            ~/go/src\n            ~/go/pkg/mod\n            ~/.cache/go-build\n          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}\n          restore-keys: |\n            ${{ runner.os }}-go-\n      - run: go version\n      - run: go test -race -v ./...\n  lint:\n    name: Lint\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: golangci/golangci-lint-action@v2\n"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n"
  },
  {
    "path": "LICENSE",
    "content": "Author:: LE Manh Cuong <cuong.manhle.vn@gmail.com>\nCopyright:: Copyright (c) 2016, Cuong Manh Le\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n    * Redistributions of source code must retain the above copyright\n      notice, this list of conditions and the following disclaimer.\n\n    * Redistributions in binary form must reproduce the above\n      copyright notice, this list of conditions and the following\n      disclaimer in the documentation and/or other materials provided\n      with the distribution.\n\n    * Neither the name of the @organization@ nor the names of its\n      contributors may be used to endorse or promote products derived\n      from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LE MANH CUONG\nBE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR\nCONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF\nSUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR\nBUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,\nWHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE\nOR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN\nIF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# gocmt - Add missing comment on exported function, method, type, constant, variable in go file\n\n![Build status](https://github.com/cuonglm/gocmt/actions/workflows/ci.yml/badge.svg?branch=main)\n[![Go Reference](https://pkg.go.dev/badge/github.com/cuonglm/gocmt.svg)](https://pkg.go.dev/github.com/cuonglm/gocmt)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cuonglm/gocmt)](https://goreportcard.com/report/github.com/cuonglm/gocmt)\n\n# Installation\n\nFor go1.15 and below:\n\n```sh\ngo get -u github.com/cuonglm/gocmt\n```\n\nFor go1.16 and above:\n\n```sh\ngo install github.com/cuonglm/gocmt@latest\n```\n\n# Why gocmt\n\nSome of my projects have many files with exported fields, variables, functions missing comment, so lint tools will complain.\n\nI find a way to auto add missing comment to them, just to pass the lint tools but nothing existed.\n\nSo `gocmt` comes in.\n\n# Usage\n```sh\n$ gocmt -h\nusage: gocmt [flags] [file ...]\n  -d string\n    \tDirectory to process\n  -i\tMake in-place editing\n  -t string\n    \tComment template (default \"...\")\n```\n\n# Example\n```sh\n$ cat testdata/main.go\npackage p\n\nvar i = 0\n\nvar I = 1\n\nvar c = \"constant un-exported\"\n\nconst C = \"constant exported\"\n\ntype t struct{}\n\ntype T struct{}\n\nfunc main() {\n}\n\nfunc unexport(s string) {\n}\nfunc Export(s string) {\n}\n\nfunc ExportWithComment(s string) {\n}\n```\n\nUsing `gocmt` give you:\n```sh\n$ gocmt testdata/main.go\npackage p\n\nvar i = 0\n\n// I ...\nvar I = 1\n\nvar c = \"constant un-exported\"\n\n// C ...\nconst C = \"constant exported\"\n\ntype t struct{}\n\n// T ...\ntype T struct{}\n\nfunc main() {\n}\n\nfunc unexport(s string) {\n}\n// Export ...\nfunc Export(s string) {\n}\n\n// ExportWithComment ...\nfunc ExportWithComment(s string) {\n}\n```\n\nDefault template is `...`, you can change it using `-t` option.\n\n# Author\n\nCuong Manh Le <cuong.manhle.vn@gmail.com>\n\n# License\n\nSee [LICENSE](https://github.com/cuonglm/gocmt/blob/main/LICENSE)\n"
  },
  {
    "path": "doc.go",
    "content": "// gocmt adds missing comments on exported identifiers in Go source files.\n//\n// Usage:\n//\n//  gocmt [-i] [-p] [-t \"comment template\"] [-d dir]\n//\n// This tools exists because I have to work with some existed code base, which\n// is lack of documentation for many exported identifiers. Iterating over them\n// is time consuming and maybe not suitable at a stage of the project. So I\n// wrote this tool to quickly bypassing CI system. Once thing is settle, we can\n// lookback and fix missing comments.\n//\n// You SHOULD always write documentation for all your exported identifiers.\npackage main\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/cuonglm/gocmt\n\ngo 1.19\n\nrequire github.com/stretchr/testify v1.6.1\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.0 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=\ngithub.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\n"
  },
  {
    "path": "helper.go",
    "content": "package main\n\nimport (\n\t\"go/ast\"\n\t\"go/scanner\"\n\t\"os\"\n\t\"strings\"\n)\n\nfunc isGoFile(f os.FileInfo) bool {\n\tname := f.Name()\n\treturn !f.IsDir() && !strings.HasPrefix(name, \".\") && strings.HasSuffix(name, \".go\")\n}\n\nfunc printError(err error) {\n\tscanner.PrintError(os.Stderr, err)\n}\n\nfunc walkFunc(path string, fi os.FileInfo, err error) error {\n\tif err == nil && isGoFile(fi) {\n\t\terr = processFile(path, *template, *inPlace)\n\t}\n\n\tif err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc isLineComment(comment *ast.CommentGroup) bool {\n\tif comment == nil {\n\t\treturn false\n\t}\n\tif len(comment.List) == 0 {\n\t\treturn false\n\t}\n\thead := comment.List[0].Text\n\thead = strings.TrimSpace(head)\n\treturn strings.HasPrefix(head, \"//\")\n}\n\nfunc hasCommentPrefix(comment *ast.CommentGroup, prefix string) bool {\n\treturn strings.HasPrefix(strings.TrimSpace(comment.Text()), prefix)\n}\n\nfunc appendCommentGroup(list []*ast.CommentGroup, item *ast.CommentGroup) []*ast.CommentGroup {\n\tret := []*ast.CommentGroup{}\n\thasInsert := false\n\tfor _, group := range list {\n\t\tif group.Pos() < item.Pos() {\n\t\t\tret = append(ret, group)\n\t\t\tcontinue\n\t\t}\n\t\tif group.Pos() == item.Pos() {\n\t\t\tret = append(ret, item)\n\t\t\thasInsert = true\n\t\t\tcontinue\n\t\t}\n\t\tif group.Pos() > item.Pos() {\n\t\t\tif !hasInsert {\n\t\t\t\tret = append(ret, item)\n\t\t\t\thasInsert = true\n\t\t\t}\n\t\t\tret = append(ret, group)\n\t\t\tcontinue\n\t\t}\n\t}\n\tif !hasInsert {\n\t\tret = append(ret, item)\n\t}\n\treturn ret\n}\n"
  },
  {
    "path": "helper_test.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc Test_isGoFile(t *testing.T) {\n\tisGoFileTest := []struct {\n\t\tpath     string\n\t\texpected bool\n\t}{\n\t\t{\"main.go\", true},\n\t\t{\"README.md\", false},\n\t}\n\n\tfor _, tt := range isGoFileTest {\n\t\tfi, err := os.Stat(tt.path)\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\n\t\tif got := isGoFile(fi); got != tt.expected {\n\t\t\tt.Fatalf(\"isGoFile(%+v): expected %v, got %v\", fi, tt.expected, got)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "main.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"flag\"\n\t\"fmt\"\n\t\"go/format\"\n\t\"go/token\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nvar (\n\t// ensure that the comment starts on a newline (without the \\n, sometimes it starts on the previous }\n\tcommentBase = \"\\n// %s \"\n\t// if it's in an indented block, this makes sure that the indentation is correct\n\tcommentIndentedBase = \"// %s \"\n\tfset                = token.NewFileSet()\n\tdefaultMode         = os.FileMode(0644)\n\ttralingWsRegex      = regexp.MustCompile(`(?m)[\\t ]+$`)\n\tnewlinesRegex       = regexp.MustCompile(`(?m)\\n{3,}`)\n)\n\nvar (\n\tinPlace      = flag.Bool(\"i\", false, \"Make in-place editing\")\n\ttemplate     = flag.String(\"t\", \"...\", \"Comment template\")\n\tdir          = flag.String(\"d\", \"\", \"Directory to process\")\n\tparenComment = flag.Bool(\"p\", false, \"Add comments to all const inside the parens if true\")\n)\n\nfunc main() {\n\tos.Exit(gocmtRun())\n}\n\nfunc usage() {\n\tfmt.Fprintf(os.Stderr, \"usage: gocmt [flags] [file ...]\\n\")\n\tflag.PrintDefaults()\n}\n\nfunc gocmtRun() int {\n\tflag.Parse()\n\n\tif *dir != \"\" {\n\t\tif err := filepath.Walk(*dir, walkFunc); err != nil {\n\t\t\tprintError(err)\n\t\t\treturn 1\n\t\t}\n\t\treturn 0\n\t}\n\n\tif flag.NArg() == 0 {\n\t\tusage()\n\t}\n\n\tfor i := 0; i < flag.NArg(); i++ {\n\t\tpath := flag.Arg(i)\n\t\tswitch fi, err := os.Stat(path); {\n\t\tcase err != nil:\n\t\t\tprintError(err)\n\t\tcase fi.IsDir():\n\t\t\tprintError(fmt.Errorf(\"%s is a directory\", path))\n\t\tdefault:\n\t\t\tif err := processFile(path, *template, *inPlace); err != nil {\n\t\t\t\tprintError(err)\n\t\t\t\treturn 1\n\t\t\t}\n\t\t}\n\t}\n\n\treturn 0\n}\n\nfunc processFile(filename, template string, inPlace bool) error {\n\t// skip test files and files in vendor/\n\tif strings.HasSuffix(filename, \"_test.go\") || strings.Contains(filename, \"/vendor/\") {\n\t\treturn nil\n\t}\n\n\taf, modified, err := parseFile(fset, filename, template)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tvar buf bytes.Buffer\n\tif err := format.Node(&buf, fset, af); err != nil {\n\t\tpanic(err)\n\t}\n\n\tnewBuf := buf.Bytes()\n\tif modified {\n\t\tnewBuf = tralingWsRegex.ReplaceAll(newBuf, []byte(\"\"))\n\t\tnewBuf = newlinesRegex.ReplaceAll(newBuf, []byte(\"\\n\\n\"))\n\t\tif inPlace {\n\t\t\treturn os.WriteFile(filename, newBuf, defaultMode)\n\t\t}\n\n\t\tfmt.Fprintf(os.Stdout, \"%s\", newBuf)\n\t\treturn nil\n\t}\n\n\tfmt.Fprintf(os.Stderr, \"%s no changes\\n\", filename)\n\n\treturn nil\n}\n"
  },
  {
    "path": "parse.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"go/ast\"\n\t\"go/parser\"\n\t\"go/token\"\n\t\"strings\"\n)\n\n// parseFile parses and modifies the input file if necessary. Returns AST represents of (new) source, a boolean\n// to report whether the source file was modified, and any error if occurred.\nfunc parseFile(fset *token.FileSet, filePath, template string) (af *ast.File, modified bool, err error) {\n\taf, err = parser.ParseFile(fset, filePath, nil, parser.ParseComments|parser.AllErrors)\n\tif err != nil {\n\t\treturn\n\t}\n\n\t// Inject first comment to prevent nil comment map\n\tif len(af.Comments) == 0 {\n\t\taf.Comments = []*ast.CommentGroup{{List: []*ast.Comment{{Slash: -1, Text: \"// gocmt\"}}}}\n\t\tdefer func() {\n\t\t\t// Remove the injected comment\n\t\t\taf.Comments = af.Comments[1:]\n\t\t}()\n\t}\n\n\tcommentTemplate := commentBase + template\n\n\toriginalCommentSign := \"\"\n\tfor _, c := range af.Comments {\n\t\toriginalCommentSign += c.Text()\n\t}\n\n\tcmap := ast.NewCommentMap(fset, af, af.Comments)\n\n\tskipped := make(map[ast.Node]bool)\n\tast.Inspect(af, func(n ast.Node) bool {\n\t\tswitch typ := n.(type) {\n\t\tcase *ast.FuncDecl:\n\t\t\tif skipped[typ] || !typ.Name.IsExported() {\n\t\t\t\treturn true\n\t\t\t}\n\t\t\taddFuncDeclComment(typ, commentTemplate)\n\t\t\tcmap[typ] = appendCommentGroup(cmap[typ], typ.Doc)\n\n\t\tcase *ast.DeclStmt:\n\t\t\tskipped[typ.Decl] = true\n\n\t\tcase *ast.GenDecl:\n\t\t\tswitch typ.Tok {\n\t\t\tcase token.CONST, token.VAR:\n\t\t\t\tif !(typ.Lparen == token.NoPos && typ.Rparen == token.NoPos) {\n\t\t\t\t\t// if there's a () and parenComment is true, add comment for each sub entry\n\t\t\t\t\tif *parenComment {\n\t\t\t\t\t\tfor _, spec := range typ.Specs {\n\t\t\t\t\t\t\tvs := spec.(*ast.ValueSpec)\n\t\t\t\t\t\t\tif !vs.Names[0].IsExported() {\n\t\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\taddParenValueSpecComment(vs, commentTemplate)\n\t\t\t\t\t\t\tcmap[vs] = appendCommentGroup(cmap[vs], vs.Doc)\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// empty var block\n\t\t\t\tif len(typ.Specs) == 0 {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\n\t\t\t\tvs := typ.Specs[0].(*ast.ValueSpec)\n\t\t\t\tif skipped[typ] || !vs.Names[0].IsExported() {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\taddValueSpecComment(typ, vs, commentTemplate)\n\n\t\t\tcase token.TYPE:\n\t\t\t\tts := typ.Specs[0].(*ast.TypeSpec)\n\t\t\t\tif skipped[typ] || !ts.Name.IsExported() {\n\t\t\t\t\treturn true\n\t\t\t\t}\n\t\t\t\taddTypeSpecComment(typ, ts, commentTemplate)\n\t\t\tdefault:\n\t\t\t\treturn true\n\t\t\t}\n\t\t\tcmap[typ] = appendCommentGroup(cmap[typ], typ.Doc)\n\t\t}\n\t\treturn true\n\t})\n\n\t// Rebuild comments\n\taf.Comments = cmap.Filter(af).Comments()\n\n\tcurrentCommentSign := \"\"\n\tfor _, c := range af.Comments {\n\t\tcurrentCommentSign += c.Text()\n\t}\n\n\tmodified = currentCommentSign != originalCommentSign\n\treturn\n}\n\nfunc addFuncDeclComment(fd *ast.FuncDecl, commentTemplate string) {\n\tif fd.Doc == nil || strings.TrimSpace(fd.Doc.Text()) == fd.Name.Name {\n\t\ttext := fmt.Sprintf(commentTemplate, fd.Name)\n\t\tpos := fd.Pos() - token.Pos(1)\n\t\tif fd.Doc != nil {\n\t\t\tpos = fd.Doc.Pos()\n\t\t}\n\t\tfd.Doc = &ast.CommentGroup{List: []*ast.Comment{{Slash: pos, Text: text}}}\n\t\treturn\n\t}\n\tif fd.Doc != nil && isLineComment(fd.Doc) && !hasCommentPrefix(fd.Doc, fd.Name.Name) {\n\t\tmodifyComment(fd.Doc, fd.Name.Name)\n\t\treturn\n\t}\n}\n\nfunc addValueSpecComment(gd *ast.GenDecl, vs *ast.ValueSpec, commentTemplate string) {\n\tif gd.Doc == nil || strings.TrimSpace(gd.Doc.Text()) == vs.Names[0].Name {\n\t\ttext := fmt.Sprintf(commentTemplate, vs.Names[0].Name)\n\t\tpos := gd.Pos() - token.Pos(1)\n\t\tif gd.Doc != nil {\n\t\t\tpos = gd.Doc.Pos()\n\t\t}\n\t\tgd.Doc = &ast.CommentGroup{List: []*ast.Comment{{Slash: pos, Text: text}}}\n\t\treturn\n\t}\n\tif gd.Doc != nil && isLineComment(gd.Doc) && !hasCommentPrefix(gd.Doc, vs.Names[0].Name) {\n\t\tmodifyComment(gd.Doc, vs.Names[0].Name)\n\t\treturn\n\t}\n}\n\nfunc addParenValueSpecComment(vs *ast.ValueSpec, commentTemplate string) {\n\tif vs.Doc == nil || strings.TrimSpace(vs.Doc.Text()) == vs.Names[0].Name {\n\t\tcommentTemplate = strings.Replace(commentTemplate, commentBase, commentIndentedBase, 1)\n\t\ttext := fmt.Sprintf(commentTemplate, vs.Names[0].Name)\n\t\tpos := vs.Pos() - token.Pos(1)\n\t\tif vs.Doc != nil {\n\t\t\tpos = vs.Doc.Pos()\n\t\t}\n\t\tvs.Doc = &ast.CommentGroup{List: []*ast.Comment{{Slash: pos, Text: text}}}\n\t\treturn\n\t}\n\tif vs.Doc != nil && isLineComment(vs.Doc) && !hasCommentPrefix(vs.Doc, vs.Names[0].Name) {\n\t\tmodifyComment(vs.Doc, vs.Names[0].Name)\n\t\treturn\n\t}\n}\n\nfunc addTypeSpecComment(gd *ast.GenDecl, ts *ast.TypeSpec, commentTemplate string) {\n\tif gd.Doc == nil || strings.TrimSpace(gd.Doc.Text()) == ts.Name.Name {\n\t\ttext := fmt.Sprintf(commentTemplate, ts.Name.Name)\n\t\tpos := gd.Pos() - token.Pos(1)\n\t\tif gd.Doc != nil {\n\t\t\tpos = gd.Doc.Pos()\n\t\t}\n\t\tgd.Doc = &ast.CommentGroup{List: []*ast.Comment{{Slash: pos, Text: text}}}\n\t\treturn\n\t}\n\tif gd.Doc != nil && isLineComment(gd.Doc) && !hasCommentPrefix(gd.Doc, ts.Name.Name) {\n\t\tmodifyComment(gd.Doc, ts.Name.Name)\n\t\treturn\n\t}\n}\n\nfunc modifyComment(comment *ast.CommentGroup, prefix string) {\n\tcommentTemplate := commentBase + *template\n\tfirst := comment.List[0].Text\n\tif strings.HasPrefix(first, \"//\") && !strings.HasPrefix(first, \"// \") {\n\t\ttext := fmt.Sprintf(commentTemplate, prefix)\n\t\tcomment.List = append([]*ast.Comment{{Text: text, Slash: comment.Pos()}}, comment.List...)\n\t\treturn\n\t}\n\tfirst = strings.TrimPrefix(first, \"// \")\n\tfirst = fmt.Sprintf(commentBase+\"%s\", prefix, first)\n\tcomment.List[0].Text = first\n}\n"
  },
  {
    "path": "parse_test.go",
    "content": "package main\n\nimport (\n\t\"bytes\"\n\t\"go/format\"\n\t\"go/token\"\n\t\"os\"\n\t\"testing\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nconst baseSrc = `package p\n\nvar i = 0\n\n// I ...\nvar I = 1\n\nvar c = \"constant un-exported\"\n\n// C ...\nconst C = \"constant exported\"\n\ntype t struct{}\n\n// T ...\ntype T struct{}\n\nfunc main() {\n}\n\nfunc unexport(s string) {\n}\n// Export ...\nfunc Export(s string) {\n}\n\n// ExportWithComment ...\nfunc ExportWithComment(s string) {\n}\n\n// ExistedComment ...\nfunc ExistedComment() {}\n`\n\nconst parenSrc = `package p\n\n// Summon ...\ntype Summon string\n\n// DarkOmega ...\nconst (\n\tDarkOmega Summon = \"celeste\"\n\t// LightOmega best summon\n\tLightOmega Summon = \"luminineria\"\n\t// WindOmega\n\tWindOmega Summon = \"tiamat\"\n)\n\n// FireUtility ...\nconst FireUtility Summon = \"the sun\"\n\n// Light ...\nconst (\n\t// Light best summon\n\tLight Summon = \"lucifer\"\n)\n\n// Light2 best summon\nconst (\n\tLight2 Summon = \"lucifer\"\n)\n`\nconst parenSrc2 = `package p\n\n// Summon ...\ntype Summon string\n\nconst (\n\t// DarkOmega ...\n\tDarkOmega Summon = \"celeste\"\n\t// LightOmega best summon\n\tLightOmega Summon = \"luminineria\"\n\t// WindOmega ...\n\tWindOmega Summon = \"tiamat\"\n)\n\n// FireUtility ...\nconst FireUtility Summon = \"the sun\"\n\nconst (\n\t// Light best summon\n\tLight Summon = \"lucifer\"\n)\n\n// Light2 best summon\nconst (\n\t// Light2 ...\n\tLight2 Summon = \"lucifer\"\n)\n`\n\nconst issue7 = `package p\n\nimport \"log\"\n\n// I ...\nvar I = 1\n\nfunc a() {\n\n        // LogAll ...\n\tvar LogAll map[string]struct{}\n\tlog.Println(LogAll)\n}`\n\nconst existed = `package p\n\n// global comments should never be deleted\n// something\n\nimport \"embed\"\n\n// global comment 1\n\n// global comment 2\n// global comment 3\n\n// ============= function =============\n\n// FuncWithExistedComment1 ...\nfunc FuncWithExistedComment1() {\n}\n\n// FuncWithExistedComment2 ...\nfunc FuncWithExistedComment2() {\n\t// this comments should never be deleted\n}\n\n// FuncWithExistedComment3 something\nfunc FuncWithExistedComment3() {\n}\n\n// FuncWithExistedComment4 multi-line comments\n// something\nfunc FuncWithExistedComment4() {\n}\n\n/*\nsomething\n*/\nfunc FuncWithExistedComment5() {\n}\n\n// ============= value =============\n\n// ValueWithExistedComment1 existed comment\nvar ValueWithExistedComment1 = 1\n\n// ValueWithExistedComment2 existed comment with spaces\nvar ValueWithExistedComment2 = 1\n\n// ValueWithExistedComment3 multi-line comments\n// something\nvar ValueWithExistedComment3 = 1\n\n/*\nshould't change C style comment\n*/\nvar ValueWithExistedComment4 = 1\n\n// ============= paren value =============\n\n// ParenValueWithExistedComment1 existed comment\nconst (\n\tParenValueWithExistedComment1 = 1\n\t// ParenValueWithExistedComment2 something\n\tParenValueWithExistedComment2 = 1\n)\n\n// ParenValueWithExistedComment3 multi-line comments\n// something\nconst (\n\tParenValueWithExistedComment3 = 1\n\t// ParenValueWithExistedComment2 something\n\tParenValueWithExistedComment4 = 1\n)\n\n// ============= type =============\n\n// TypeWithExistedComment1 existed comment\ntype TypeWithExistedComment1 int\n\n// TypeWithExistedComment2 existed comment with spaces\ntype TypeWithExistedComment2 int\n\n// TypeWithExistedComment3 multi-line comments\n// something\ntype TypeWithExistedComment3 int\n\n/*\nshould't change C style comment\n*/\ntype TypeWithExistedComment4 int\n\n// ============= marker comment =============\n\n// Embed ...\n//go:embed dont_modify_this_comment.txt\n//go:embed image/*\nvar Embed embed.FS\n\n// Embed something\n//go:embed dont_modify_this_comment.txt\nvar Embed embed.FS\n\n// Generate ...\n//go:generate goyacc -o gopher.go -p parser gopher.y\nfunc Generate() {\n}\n\n// ============= end =============\n`\n\nconst generic = `package p\n\n// G ...\ntype G[T any] struct{}\n`\n\nfunc Test_parseFile(t *testing.T) {\n\tparseFileTests := []struct {\n\t\tpath        string\n\t\texpectedSrc string\n\t\tmodified    bool\n\t\twantErr     bool\n\t}{\n\t\t{\"testdata/main.go\", baseSrc, true, false},\n\t\t{\"testdata/parenthesis.go\", parenSrc, true, false},\n\t\t{\"testdata/invalid_file.go\", \"\", false, true},\n\t\t{\"testdata/issue7.go\", issue7, false, false},\n\t\t{\"testdata/existed.go\", existed, true, false},\n\t\t{\"testdata/generic.go\", generic, true, false},\n\t}\n\n\tfor _, tc := range parseFileTests {\n\t\ttc := tc\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tfset := token.NewFileSet()\n\t\t\taf, modified, err := parseFile(fset, tc.path, \"...\")\n\t\t\tassert.True(t, tc.wantErr == (err != nil))\n\t\t\tassert.Equal(t, tc.modified, modified)\n\n\t\t\tif tc.modified {\n\t\t\t\tbuf := new(bytes.Buffer)\n\t\t\t\tassert.NoError(t, format.Node(buf, fset, af))\n\t\t\t\tnewBuf := buf.Bytes()\n\t\t\t\tnewBuf = tralingWsRegex.ReplaceAll(newBuf, []byte(\"\"))\n\t\t\t\tnewBuf = newlinesRegex.ReplaceAll(newBuf, []byte(\"\\n\\n\"))\n\t\t\t\tassert.Equal(t, tc.expectedSrc, string(newBuf))\n\t\t\t}\n\t\t})\n\n\t}\n}\nfunc Test_parseFileWithParenComment(t *testing.T) {\n\t*parenComment = true\n\tparseFileTests := []struct {\n\t\tpath        string\n\t\texpectedSrc string\n\t\tmodified    bool\n\t\twantErr     bool\n\t}{\n\t\t{\"testdata/parenthesis.go\", parenSrc2, true, false},\n\t}\n\n\tfor _, tc := range parseFileTests {\n\t\ttc := tc\n\t\tt.Run(tc.path, func(t *testing.T) {\n\t\t\tt.Parallel()\n\t\t\tfset := token.NewFileSet()\n\t\t\taf, modified, err := parseFile(fset, tc.path, \"...\")\n\t\t\tassert.True(t, tc.wantErr == (err != nil))\n\t\t\tassert.Equal(t, tc.modified, modified)\n\n\t\t\tif tc.modified {\n\t\t\t\tbuf := new(bytes.Buffer)\n\t\t\t\tassert.NoError(t, format.Node(buf, fset, af))\n\t\t\t\tnewBuf := buf.Bytes()\n\t\t\t\tnewBuf = tralingWsRegex.ReplaceAll(newBuf, []byte(\"\"))\n\t\t\t\tnewBuf = newlinesRegex.ReplaceAll(newBuf, []byte(\"\\n\\n\"))\n\t\t\t\tassert.Equal(t, tc.expectedSrc, string(newBuf))\n\t\t\t}\n\t\t})\n\n\t}\n}\n\nfunc TestSkipVendor(t *testing.T) {\n\tfilePath := \"testdata/vendor/main.go\"\n\torigBuf, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif err := processFile(filePath, \"...\", true); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tbuf, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tif !bytes.Equal(buf, origBuf) {\n\t\tt.Fatal(\"file in vendor/ directory was edited\")\n\t}\n}\n"
  },
  {
    "path": "testdata/empty_var_block.go",
    "content": "package p\n\nvar (\n\t// Error\n)\n"
  },
  {
    "path": "testdata/existed.go",
    "content": "package p\n\n// global comments should never be deleted\n// something\n\nimport \"embed\"\n\n// global comment 1\n\n// global comment 2\n// global comment 3\n\n// ============= function =============\n\n// FuncWithExistedComment1\nfunc FuncWithExistedComment1() {\n}\n\nfunc FuncWithExistedComment2() {\n\t// this comments should never be deleted\n}\n\n// something\nfunc FuncWithExistedComment3() {\n}\n\n// multi-line comments\n// something\nfunc FuncWithExistedComment4() {\n}\n\n/*\nsomething\n*/\nfunc FuncWithExistedComment5() {\n}\n\n// ============= value =============\n\n// existed comment\nvar ValueWithExistedComment1 = 1\n\n// existed comment with spaces     \nvar ValueWithExistedComment2 = 1\n\n// multi-line comments\n// something\nvar ValueWithExistedComment3 = 1\n\n/*\nshould't change C style comment\n*/\nvar ValueWithExistedComment4 = 1\n\n// ============= paren value =============\n\n// existed comment\nconst (\n\tParenValueWithExistedComment1 = 1\n\t// ParenValueWithExistedComment2 something\n\tParenValueWithExistedComment2 = 1\n)\n\n// multi-line comments\n// something\nconst (\n\tParenValueWithExistedComment3 = 1\n\t// ParenValueWithExistedComment2 something\n\tParenValueWithExistedComment4 = 1\n)\n\n// ============= type =============\n\n// existed comment\ntype TypeWithExistedComment1 int\n\n// existed comment with spaces     \ntype TypeWithExistedComment2 int\n\n// multi-line comments\n// something\ntype TypeWithExistedComment3 int\n\n/*\nshould't change C style comment\n*/\ntype TypeWithExistedComment4 int\n\n// ============= marker comment =============\n\n//go:embed dont_modify_this_comment.txt\n//go:embed image/*\nvar Embed embed.FS\n\n// something\n//go:embed dont_modify_this_comment.txt\nvar Embed embed.FS\n\n//go:generate goyacc -o gopher.go -p parser gopher.y\nfunc Generate() {\n}\n\n// ============= end =============\n"
  },
  {
    "path": "testdata/generic.go",
    "content": "package p\n\ntype G[T any] struct{}\n"
  },
  {
    "path": "testdata/invalid_file.go",
    "content": "package p\n\nvar i := 1\n"
  },
  {
    "path": "testdata/issue7.go",
    "content": "package p\n\nimport \"log\"\n\n// I ...\nvar I = 1\n\nfunc a() {\n\n\t// LogAll ...\n\tvar LogAll map[string]struct{}\n\tlog.Println(LogAll)\n}\n"
  },
  {
    "path": "testdata/main.go",
    "content": "package p\n\nvar i = 0\n\nvar I = 1\n\nvar c = \"constant un-exported\"\n\nconst C = \"constant exported\"\n\ntype t struct{}\n\ntype T struct{}\n\nfunc main() {\n}\n\nfunc unexport(s string) {\n}\nfunc Export(s string) {\n}\n\nfunc ExportWithComment(s string) {\n}\n\n// ExistedComment\nfunc ExistedComment() {}\n"
  },
  {
    "path": "testdata/parenthesis.go",
    "content": "package p\n\ntype Summon string\n\nconst (\n\tDarkOmega Summon = \"celeste\"\n\t// LightOmega best summon\n\tLightOmega Summon = \"luminineria\"\n\t// WindOmega\n\tWindOmega Summon = \"tiamat\"\n)\n\nconst FireUtility Summon = \"the sun\"\n\nconst (\n\t// Light best summon\n\tLight Summon = \"lucifer\"\n)\n\n// Light2 best summon\nconst (\n\tLight2 Summon = \"lucifer\"\n)\n"
  },
  {
    "path": "testdata/vendor/main.go",
    "content": "package p\n\nvar I = 1\n"
  }
]