feat: add tools formitychecker

This commit is contained in:
Xinwei Xiong (cubxxw) 2024-03-19 12:12:52 +08:00
parent 993e97019a
commit f75506b230
8 changed files with 212 additions and 119 deletions

View File

@ -579,6 +579,7 @@ linters-settings:
rowserrcheck: rowserrcheck:
packages: packages:
- github.com/jmoiron/sqlx - github.com/jmoiron/sqlx
revive: revive:
# see https://github.com/mgechev/revive#available-rules for details. # see https://github.com/mgechev/revive#available-rules for details.
ignore-generated-header: true ignore-generated-header: true
@ -586,15 +587,16 @@ linters-settings:
rules: rules:
- name: indent-error-flow - name: indent-error-flow
severity: warning severity: warning
staticcheck: staticcheck:
# Select the Go version to target. The default is '1.13'. # Select the Go version to target. The default is '1.13'.
go: "1.16" go: "1.20"
# https://staticcheck.io/docs/options#checks # https://staticcheck.io/docs/options#checks
checks: [ "all" ] checks: [ "all" ]
stylecheck: stylecheck:
# Select the Go version to target. The default is '1.13'. # Select the Go version to target. The default is '1.13'.
go: "1.16" go: "1.20"
# https://staticcheck.io/docs/options#checks # https://staticcheck.io/docs/options#checks
checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ] checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ]

View File

@ -89,10 +89,7 @@ func FriendsDB2Pb(
} }
func FriendRequestDB2Pb(ctx context.Context, func FriendRequestDB2Pb(ctx context.Context, friendRequests []*relation.FriendRequestModel, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) {
friendRequests []*relation.FriendRequestModel,
getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
) ([]*sdkws.FriendRequest, error) {
if len(friendRequests) == 0 { if len(friendRequests) == 0 {
return nil, nil return nil, nil
} }

View File

@ -56,12 +56,7 @@ func Pb2DbGroupRequest(req *pbgroup.GroupApplicationResponseReq, handleUserID st
} }
} }
func Db2PbCMSGroup( func Db2PbCMSGroup(m *relation.GroupModel, ownerUserID string, ownerUserName string, memberCount uint32) *pbgroup.CMSGroup {
m *relation.GroupModel,
ownerUserID string,
ownerUserName string,
memberCount uint32,
) *pbgroup.CMSGroup {
return &pbgroup.CMSGroup{ return &pbgroup.CMSGroup{
GroupInfo: Db2PbGroupInfo(m, ownerUserID, memberCount), GroupInfo: Db2PbGroupInfo(m, ownerUserID, memberCount),
GroupOwnerUserID: ownerUserID, GroupOwnerUserID: ownerUserID,
@ -86,11 +81,7 @@ func Db2PbGroupMember(m *relation.GroupMemberModel) *sdkws.GroupMemberFullInfo {
} }
} }
func Db2PbGroupRequest( func Db2PbGroupRequest(m *relation.GroupRequestModel, user *sdkws.PublicUserInfo, group *sdkws.GroupInfo) *sdkws.GroupRequest {
m *relation.GroupRequestModel,
user *sdkws.PublicUserInfo,
group *sdkws.GroupInfo,
) *sdkws.GroupRequest {
return &sdkws.GroupRequest{ return &sdkws.GroupRequest{
UserInfo: user, UserInfo: user,
GroupInfo: group, GroupInfo: group,

View File

@ -19,8 +19,5 @@
# Usage: `scripts/list-feature-tests.sh`. # Usage: `scripts/list-feature-tests.sh`.
OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/..
grep "\[Feature:\w+\]" "${OPENIM_ROOT}"/test/e2e/**/*.go -Eoh | LC_ALL=C sort -u grep "\[Feature:\w+\]" "${OPENIM_ROOT}"/test/e2e/**/*.go -Eoh | LC_ALL=C sort -u

View File

@ -243,7 +243,7 @@ func checkKafka(config *config.GlobalConfig) error {
for _, requiredTopic := range requiredTopics { for _, requiredTopic := range requiredTopics {
if !isTopicPresent(requiredTopic, topics) { if !isTopicPresent(requiredTopic, topics) {
return errs.Wrap(err, fmt.Sprintf("Kafka doesn't contain topic: %v", requiredTopic)) return errs.WrapMsg(nil, "Kafka missing required topic", "topic", requiredTopic, "availableTopics", strings.Join(topics, ", "))
} }
} }

View File

@ -15,99 +15,136 @@
package checker package checker
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"github.com/OpenIMSDK/tools/errs"
"github.com/openimsdk/open-im-server/tools/formitychecker/config" "github.com/openimsdk/open-im-server/tools/formitychecker/config"
) )
var ( type Issue struct {
underscoreRegex = regexp.MustCompile(`^[a-zA-Z0-9_]+\.[a-zA-Z0-9]+$`) Type string
hyphenRegex = regexp.MustCompile(`^[a-zA-Z0-9\-]+\.[a-zA-Z0-9]+$`) Path string
) Message string
}
// CheckDirectory initiates the checking process for the specified directories using configuration from config.Config. type Checker struct {
func CheckDirectory(cfg *config.Config) error { Config *config.Config
ignoreMap := make(map[string]struct{}) Summary struct {
for _, dir := range cfg.IgnoreDirs { CheckedDirectories int
ignoreMap[dir] = struct{}{} CheckedFiles int
Issues []Issue
}
}
func (c *Checker) Check() error {
return filepath.Walk(c.Config.BaseConfig.SearchDirectory, c.checkPath)
}
func (c *Checker) checkPath(path string, info os.FileInfo, err error) error {
if err != nil {
return err
} }
for _, targetDir := range cfg.TargetDirs { relativePath, err := filepath.Rel(c.Config.BaseConfig.SearchDirectory, path)
err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { if err != nil {
if err != nil { return err
return errs.Wrap(err, fmt.Sprintf("error walking directory '%s'", targetDir)) }
}
// Skip if the directory is in the ignore list if relativePath == "." {
dirName := filepath.Base(filepath.Dir(path)) return nil
if _, ok := ignoreMap[dirName]; ok && info.IsDir() { }
return filepath.SkipDir
}
// Check the naming convention
if err := checkNamingConvention(path, info); err != nil {
fmt.Println(err)
}
if info.IsDir() {
c.Summary.CheckedDirectories++
if c.isIgnoredDirectory(relativePath) {
c.Summary.Issues = append(c.Summary.Issues, Issue{
Type: "ignoredDirectory",
Path: path,
Message: "此目录已被忽略",
})
return filepath.SkipDir
}
if !c.checkDirectoryName(relativePath) {
c.Summary.Issues = append(c.Summary.Issues, Issue{
Type: "directoryNaming",
Path: path,
Message: "目录名称不符合规范",
})
}
} else {
if c.isIgnoredFile(path) {
return nil return nil
}) }
c.Summary.CheckedFiles++
if err != nil { if !c.checkFileName(relativePath) {
return fmt.Errorf("error checking directory '%s': %w", targetDir, err) c.Summary.Issues = append(c.Summary.Issues, Issue{
Type: "fileNaming",
Path: path,
Message: "文件名称不符合规范",
})
} }
} }
return nil return nil
} }
// checkNamingConvention checks if the file or directory name conforms to the standard naming conventions. func (c *Checker) isIgnoredDirectory(path string) bool {
func checkNamingConvention(path string, info os.FileInfo) error { for _, ignoredDir := range c.Config.IgnoreDirectories {
fileName := info.Name() if strings.Contains(path, ignoredDir) {
return true
// Handle special cases for directories like .git
if info.IsDir() && strings.HasPrefix(fileName, ".") {
return nil // Skip special directories
}
// Extract the main part of the name (without extension for files)
mainName := fileName
if !info.IsDir() {
mainName = strings.TrimSuffix(fileName, filepath.Ext(fileName))
}
// Determine the type of file and apply corresponding naming rule
switch {
case info.IsDir():
if !isValidName(mainName, "_") { // Directory names must only contain underscores
return fmt.Errorf("!!! invalid directory name: %s", path)
}
case strings.HasSuffix(fileName, ".go"):
if !isValidName(mainName, "_") { // Go files must only contain underscores
return fmt.Errorf("!!! invalid Go file name: %s", path)
}
case strings.HasSuffix(fileName, ".yml"), strings.HasSuffix(fileName, ".yaml"), strings.HasSuffix(fileName, ".md"):
if !isValidName(mainName, "-") { // YML, YAML, and Markdown files must only contain hyphens
return fmt.Errorf("!!! invalid file name: %s", path)
} }
} }
return false
return nil
} }
// isValidName checks if the file name conforms to the specified rule (underscore or hyphen). func (c *Checker) isIgnoredFile(path string) bool {
func isValidName(name, charType string) bool { ext := filepath.Ext(path)
switch charType { for _, format := range c.Config.IgnoreFormats {
case "_": if ext == format {
return underscoreRegex.MatchString(name) return true
case "-": }
return hyphenRegex.MatchString(name) }
default: return false
}
func (c *Checker) checkDirectoryName(path string) bool {
dirName := filepath.Base(path)
if c.Config.DirectoryNaming.MustBeLowercase && (dirName != strings.ToLower(dirName)) {
return false return false
} }
if !c.Config.DirectoryNaming.AllowHyphens && strings.Contains(dirName, "-") {
return false
}
if !c.Config.DirectoryNaming.AllowUnderscores && strings.Contains(dirName, "_") {
return false
}
return true
}
func (c *Checker) checkFileName(path string) bool {
fileName := filepath.Base(path)
ext := filepath.Ext(fileName)
allowHyphens := c.Config.FileNaming.AllowHyphens
allowUnderscores := c.Config.FileNaming.AllowUnderscores
mustBeLowercase := c.Config.FileNaming.MustBeLowercase
if specificNaming, ok := c.Config.FileTypeSpecificNaming[ext]; ok {
allowHyphens = specificNaming.AllowHyphens
allowUnderscores = specificNaming.AllowUnderscores
mustBeLowercase = specificNaming.MustBeLowercase
}
if mustBeLowercase && (fileName != strings.ToLower(fileName)) {
return false
}
if !allowHyphens && strings.Contains(fileName, "-") {
return false
}
if !allowUnderscores && strings.Contains(fileName, "_") {
return false
}
return true
} }

View File

@ -15,27 +15,66 @@
package config package config
import ( import (
"strings" "os"
"github.com/openimsdk/open-im-server/tools/codescan/config"
"gopkg.in/yaml.v2"
) )
// Config holds all the configuration parameters for the checker.
type Config struct { type Config struct {
TargetDirs []string // Directories to check BaseConfig struct {
IgnoreDirs []string // Directories to ignore SearchDirectory string `yaml:"searchDirectory"`
IgnoreCase bool `yaml:"ignoreCase"`
} `yaml:"baseConfig"`
DirectoryNaming struct {
AllowHyphens bool `yaml:"allowHyphens"`
AllowUnderscores bool `yaml:"allowUnderscores"`
MustBeLowercase bool `yaml:"mustBeLowercase"`
} `yaml:"directoryNaming"`
FileNaming struct {
AllowHyphens bool `yaml:"allowHyphens"`
AllowUnderscores bool `yaml:"allowUnderscores"`
MustBeLowercase bool `yaml:"mustBeLowercase"`
} `yaml:"fileNaming"`
IgnoreFormats []string `yaml:"ignoreFormats"`
IgnoreDirectories []string `yaml:"ignoreDirectories"`
FileTypeSpecificNaming map[string]FileTypeSpecificNaming `yaml:"fileTypeSpecificNaming"`
} }
// NewConfig creates and returns a new Config instance. type FileTypeSpecificNaming struct {
func NewConfig(targetDirs, ignoreDirs string) *Config { AllowHyphens bool `yaml:"allowHyphens"`
return &Config{ AllowUnderscores bool `yaml:"allowUnderscores"`
TargetDirs: parseDirs(targetDirs), MustBeLowercase bool `yaml:"mustBeLowercase"`
IgnoreDirs: parseDirs(ignoreDirs),
}
} }
// parseDirs splits a comma-separated string into a slice of directory names. type Issue struct {
func parseDirs(dirs string) []string { Type string
if dirs == "" { Path string
return nil Message string
} }
return strings.Split(dirs, ",")
type Checker struct {
Config *config.Config
Summary struct {
CheckedDirectories int
CheckedFiles int
Issues []Issue
}
Errors []string
}
func LoadConfig(configPath string) (*Config, error) {
var config Config
file, err := os.ReadFile(configPath)
if err != nil {
return nil, err
}
err = yaml.Unmarshal(file, &config)
if err != nil {
return nil, err
}
return &config, nil
} }

View File

@ -15,27 +15,57 @@
package main package main
import ( import (
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"os"
"github.com/openimsdk/open-im-server/tools/formitychecker/checker" "github.com/openimsdk/open-im-server/tools/formitychecker/checker"
"github.com/openimsdk/open-im-server/tools/formitychecker/config" "github.com/openimsdk/open-im-server/tools/formitychecker/config"
) )
func main() { func main() {
defaultTargetDirs := "." var configPath string
defaultIgnoreDirs := "components,.git" flag.StringVar(&configPath, "config", "", "Path to the configuration file")
var targetDirs string
var ignoreDirs string
flag.StringVar(&targetDirs, "target", defaultTargetDirs, "Directories to check (default: current directory)")
flag.StringVar(&ignoreDirs, "ignore", defaultIgnoreDirs, "Directories to ignore (default: A/, B/)")
flag.Parse() flag.Parse()
conf := config.NewConfig(targetDirs, ignoreDirs) if configPath == "" {
configPath = os.Getenv("CONFIG_PATH")
err := checker.CheckDirectory(conf)
if err != nil {
fmt.Println("Error:", err)
} }
if configPath == "" {
configPath = "config.yaml"
if _, err := os.Stat(".github/formitychecker.yaml"); err == nil {
configPath = ".github/formitychecker.yaml"
}
}
cfg, err := config.LoadConfig(configPath)
if err != nil {
fmt.Println("Error loading config:", err)
return
}
c := &checker.Checker{Config: cfg}
err = c.Check()
if err != nil {
fmt.Println("Error during check:", err)
os.Exit(1)
}
// if len(c.Errors) > 0 {
// fmt.Println("Found errors:")
// for _, errMsg := range c.Errors {
// fmt.Println("-", errMsg)
// }
// os.Exit(1)
// }
summaryJSON, err := json.MarshalIndent(c.Summary, "", " ")
if err != nil {
fmt.Println("Error marshalling summary:", err)
return
}
fmt.Println(string(summaryJSON))
} }