-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Detect Missing Packages or Unused Packages From
gno.mod
File (#…
…61) * mod * mod file handler * change lint error name * fix ci * universal position type * fix * Revert "universal position type" This reverts commit 3d4ac96. * fix weird error * stored in mod but not imported
- Loading branch information
Showing
9 changed files
with
315 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package formatter | ||
|
||
import ( | ||
"github.com/gnoswap-labs/tlin/internal" | ||
tt "github.com/gnoswap-labs/tlin/internal/types" | ||
) | ||
|
||
type MissingModPackageFormatter struct{} | ||
|
||
func (f *MissingModPackageFormatter) Format(issue tt.Issue, snippet *internal.SourceCode) string { | ||
builder := NewIssueFormatterBuilder(issue, snippet) | ||
return builder. | ||
AddHeader(errorHeader). | ||
AddMessage(). | ||
Build() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
package lints | ||
|
||
import ( | ||
"bufio" | ||
"fmt" | ||
"go/ast" | ||
"go/token" | ||
"os" | ||
"path/filepath" | ||
"strings" | ||
|
||
tt "github.com/gnoswap-labs/tlin/internal/types" | ||
) | ||
|
||
func DetectMissingModPackage(filename string, node *ast.File, fset *token.FileSet) ([]tt.Issue, error) { | ||
dir := filepath.Dir(filename) | ||
modFile := filepath.Join(dir, "gno.mod") | ||
|
||
requiredPackages, err := extractImports(node) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to extract imports: %w", err) | ||
} | ||
|
||
declaredPackages, err := extractDeclaredPackages(modFile) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to extract declared packages: %w", err) | ||
} | ||
|
||
var issues []tt.Issue | ||
|
||
var unusedPackages []string | ||
for pkg := range declaredPackages { | ||
if _, ok := requiredPackages[pkg]; !ok { | ||
unusedPackages = append(unusedPackages, pkg) | ||
} | ||
} | ||
|
||
if len(unusedPackages) > 0 { | ||
issue := tt.Issue{ | ||
Rule: "gno-mod-tidy", | ||
Filename: modFile, | ||
Start: token.Position{Filename: modFile}, | ||
End: token.Position{Filename: modFile}, | ||
Message: fmt.Sprintf("Packages %s are declared in gno.mod file but not imported.\nRun `gno mod tidy`", strings.Join(unusedPackages, ", ")), | ||
} | ||
issues = append(issues, issue) | ||
} | ||
|
||
for pkg := range requiredPackages { | ||
if !declaredPackages[pkg] { | ||
issue := tt.Issue{ | ||
Rule: "gno-mod-tidy", | ||
Filename: modFile, | ||
Start: token.Position{Filename: modFile}, | ||
End: token.Position{Filename: modFile}, | ||
Message: fmt.Sprintf("Package %s is imported but not declared in gno.mod file. Please consider to remove.\nRun `gno mod tidy`", pkg), | ||
} | ||
issues = append(issues, issue) | ||
} | ||
} | ||
|
||
return issues, nil | ||
} | ||
|
||
func extractImports(node *ast.File) (map[string]bool, error) { | ||
imports := make(map[string]bool) | ||
for _, imp := range node.Imports { | ||
if imp.Path != nil { | ||
path := strings.Trim(imp.Path.Value, "\"") | ||
if strings.HasPrefix(path, "gno.land/p/") || strings.HasPrefix(path, "gno.land/r/") { | ||
imports[path] = true | ||
} | ||
} | ||
} | ||
return imports, nil | ||
} | ||
|
||
func extractDeclaredPackages(modFile string) (map[string]bool, error) { | ||
file, err := os.Open(modFile) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer file.Close() | ||
|
||
packages := make(map[string]bool) | ||
scanner := bufio.NewScanner(file) | ||
for scanner.Scan() { | ||
line := strings.TrimSpace(scanner.Text()) | ||
if strings.HasPrefix(line, "gno.land/p/") || strings.HasPrefix(line, "gno.land/r/") { | ||
parts := strings.Fields(line) | ||
if len(parts) >= 2 { | ||
packages[parts[0]] = true | ||
} | ||
} | ||
} | ||
|
||
if err := scanner.Err(); err != nil { | ||
return nil, err | ||
} | ||
|
||
return packages, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
package lints | ||
|
||
import ( | ||
"go/parser" | ||
"go/token" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestDetectMissingPackageInMod(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
gnoContent string | ||
modContent string | ||
expectedIssues int | ||
}{ | ||
{ | ||
name: "No missing packages", | ||
gnoContent: ` | ||
package foo | ||
import ( | ||
"gno.land/p/demo/avl" | ||
"gno.land/r/demo/users" | ||
) | ||
func SomeFunc() {} | ||
`, | ||
modContent: ` | ||
module foo | ||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
gno.land/r/demo/users v0.0.0-latest | ||
) | ||
`, | ||
expectedIssues: 0, | ||
}, | ||
{ | ||
name: "One missing package", | ||
gnoContent: ` | ||
package foo | ||
import ( | ||
"gno.land/p/demo/avl" | ||
"gno.land/r/demo/users" | ||
) | ||
func Foo() {} | ||
`, | ||
modContent: ` | ||
module foo | ||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
) | ||
`, | ||
expectedIssues: 1, | ||
}, | ||
{ | ||
name: "Multiple missing packages", | ||
gnoContent: ` | ||
package bar | ||
import ( | ||
"gno.land/p/demo/avl" | ||
"gno.land/r/demo/users" | ||
"gno.land/p/demo/ufmt" | ||
) | ||
func main() {} | ||
`, | ||
modContent: ` | ||
module bar | ||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
) | ||
`, | ||
expectedIssues: 2, | ||
}, | ||
{ | ||
name: "declared but not imported", | ||
gnoContent: ` | ||
package bar | ||
func main() {} | ||
`, | ||
modContent: ` | ||
module bar | ||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
) | ||
`, | ||
expectedIssues: 1, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
tmpDir, err := os.MkdirTemp("", "test") | ||
require.NoError(t, err) | ||
defer os.RemoveAll(tmpDir) | ||
|
||
gnoFile := filepath.Join(tmpDir, "main.gno") | ||
err = os.WriteFile(gnoFile, []byte(tt.gnoContent), 0644) | ||
require.NoError(t, err) | ||
|
||
modFile := filepath.Join(tmpDir, "gno.mod") | ||
err = os.WriteFile(modFile, []byte(tt.modContent), 0644) | ||
require.NoError(t, err) | ||
|
||
fset := token.NewFileSet() | ||
node, err := parser.ParseFile(fset, gnoFile, nil, parser.ParseComments) | ||
require.NoError(t, err) | ||
|
||
issues, err := DetectMissingModPackage(gnoFile, node, fset) | ||
|
||
require.NoError(t, err) | ||
assert.Len(t, issues, tt.expectedIssues) | ||
|
||
for _, issue := range issues { | ||
assert.Equal(t, "gno-mod-tidy", issue.Rule) | ||
assert.Equal(t, modFile, issue.Filename) | ||
println(issue.String()) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package foo | ||
|
||
import ( | ||
"gno.land/p/demo/ufmt" | ||
) | ||
|
||
func main() { | ||
ufmt.Println("Hello, World!") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module foo | ||
|
||
require ( | ||
gno.land/p/demo/avl v0.0.0-latest | ||
) |