diff --git a/.golangci.yml b/.golangci.yml index 7dbb642a8..be68696de 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -579,6 +579,7 @@ linters-settings: rowserrcheck: packages: - github.com/jmoiron/sqlx + revive: # see https://github.com/mgechev/revive#available-rules for details. ignore-generated-header: true @@ -586,15 +587,16 @@ linters-settings: rules: - name: indent-error-flow severity: warning + staticcheck: # Select the Go version to target. The default is '1.13'. - go: "1.16" + go: "1.20" # https://staticcheck.io/docs/options#checks checks: [ "all" ] stylecheck: # Select the Go version to target. The default is '1.13'. - go: "1.16" + go: "1.20" # https://staticcheck.io/docs/options#checks checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ] diff --git a/pkg/common/convert/friend.go b/pkg/common/convert/friend.go index 9d463c6ff..ccde35bbf 100644 --- a/pkg/common/convert/friend.go +++ b/pkg/common/convert/friend.go @@ -89,10 +89,7 @@ func FriendsDB2Pb( } -func FriendRequestDB2Pb(ctx context.Context, - friendRequests []*relation.FriendRequestModel, - getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error), -) ([]*sdkws.FriendRequest, error) { +func FriendRequestDB2Pb(ctx context.Context, friendRequests []*relation.FriendRequestModel, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) { if len(friendRequests) == 0 { return nil, nil } diff --git a/pkg/common/convert/group.go b/pkg/common/convert/group.go index 18940f518..cb049dd36 100644 --- a/pkg/common/convert/group.go +++ b/pkg/common/convert/group.go @@ -56,12 +56,7 @@ func Pb2DbGroupRequest(req *pbgroup.GroupApplicationResponseReq, handleUserID st } } -func Db2PbCMSGroup( - m *relation.GroupModel, - ownerUserID string, - ownerUserName string, - memberCount uint32, -) *pbgroup.CMSGroup { +func Db2PbCMSGroup(m *relation.GroupModel, ownerUserID string, ownerUserName string, memberCount uint32) *pbgroup.CMSGroup { return &pbgroup.CMSGroup{ GroupInfo: Db2PbGroupInfo(m, ownerUserID, memberCount), GroupOwnerUserID: ownerUserID, @@ -86,11 +81,7 @@ func Db2PbGroupMember(m *relation.GroupMemberModel) *sdkws.GroupMemberFullInfo { } } -func Db2PbGroupRequest( - m *relation.GroupRequestModel, - user *sdkws.PublicUserInfo, - group *sdkws.GroupInfo, -) *sdkws.GroupRequest { +func Db2PbGroupRequest(m *relation.GroupRequestModel, user *sdkws.PublicUserInfo, group *sdkws.GroupInfo) *sdkws.GroupRequest { return &sdkws.GroupRequest{ UserInfo: user, GroupInfo: group, diff --git a/scripts/list-feature-tests.sh b/scripts/list-feature-tests.sh index d6eaa4873..f48a7a7e3 100755 --- a/scripts/list-feature-tests.sh +++ b/scripts/list-feature-tests.sh @@ -19,8 +19,5 @@ # Usage: `scripts/list-feature-tests.sh`. - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. grep "\[Feature:\w+\]" "${OPENIM_ROOT}"/test/e2e/**/*.go -Eoh | LC_ALL=C sort -u \ No newline at end of file diff --git a/tools/component/component.go b/tools/component/component.go index bed5a0d48..fa023106a 100644 --- a/tools/component/component.go +++ b/tools/component/component.go @@ -243,7 +243,7 @@ func checkKafka(config *config.GlobalConfig) error { for _, requiredTopic := range requiredTopics { if !isTopicPresent(requiredTopic, topics) { - return errs.WrapMsg(err, fmt.Sprintf("Kafka doesn't contain topic: %v", requiredTopic)) + return errs.WrapMsg(nil, "Kafka missing required topic", "topic", requiredTopic, "availableTopics", strings.Join(topics, ", ")) } } diff --git a/tools/formitychecker/checker/checker.go b/tools/formitychecker/checker/checker.go index 03cff3613..93d3651d6 100644 --- a/tools/formitychecker/checker/checker.go +++ b/tools/formitychecker/checker/checker.go @@ -15,99 +15,136 @@ package checker import ( - "fmt" "os" "path/filepath" - "regexp" "strings" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/tools/formitychecker/config" ) -var ( - underscoreRegex = regexp.MustCompile(`^[a-zA-Z0-9_]+\.[a-zA-Z0-9]+$`) - hyphenRegex = regexp.MustCompile(`^[a-zA-Z0-9\-]+\.[a-zA-Z0-9]+$`) -) +type Issue struct { + Type string + Path string + Message string +} -// CheckDirectory initiates the checking process for the specified directories using configuration from config.Config. -func CheckDirectory(cfg *config.Config) error { - ignoreMap := make(map[string]struct{}) - for _, dir := range cfg.IgnoreDirs { - ignoreMap[dir] = struct{}{} +type Checker struct { + Config *config.Config + Summary struct { + CheckedDirectories int + 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 { - err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return errs.WrapMsg(err, fmt.Sprintf("error walking directory '%s'", targetDir)) - } + relativePath, err := filepath.Rel(c.Config.BaseConfig.SearchDirectory, path) + if err != nil { + return err + } - // Skip if the directory is in the ignore list - dirName := filepath.Base(filepath.Dir(path)) - 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 relativePath == "." { + return nil + } + if info.IsDir() { + c.Summary.CheckedDirectories++ + if c.isIgnoredDirectory(relativePath) { + c.Summary.Issues = append(c.Summary.Issues, Issue{ + Type: "ignoredDirectory", + Path: path, + Message: "This directory has been ignored", + }) + return filepath.SkipDir + } + if !c.checkDirectoryName(relativePath) { + c.Summary.Issues = append(c.Summary.Issues, Issue{ + Type: "directoryNaming", + Path: path, + Message: "The directory name is invalid", + }) + } + } else { + if c.isIgnoredFile(path) { return nil - }) - - if err != nil { - return fmt.Errorf("error checking directory '%s': %w", targetDir, err) + } + c.Summary.CheckedFiles++ + if !c.checkFileName(relativePath) { + c.Summary.Issues = append(c.Summary.Issues, Issue{ + Type: "fileNaming", + Path: path, + Message: "The file name does not comply with the specification", + }) } } return nil } -// checkNamingConvention checks if the file or directory name conforms to the standard naming conventions. -func checkNamingConvention(path string, info os.FileInfo) error { - fileName := info.Name() - - // 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) +func (c *Checker) isIgnoredDirectory(path string) bool { + for _, ignoredDir := range c.Config.IgnoreDirectories { + if strings.Contains(path, ignoredDir) { + return true } } - - return nil + return false } -// isValidName checks if the file name conforms to the specified rule (underscore or hyphen). -func isValidName(name, charType string) bool { - switch charType { - case "_": - return underscoreRegex.MatchString(name) - case "-": - return hyphenRegex.MatchString(name) - default: +func (c *Checker) isIgnoredFile(path string) bool { + ext := filepath.Ext(path) + for _, format := range c.Config.IgnoreFormats { + if ext == format { + return true + } + } + return false +} + +func (c *Checker) checkDirectoryName(path string) bool { + dirName := filepath.Base(path) + if c.Config.DirectoryNaming.MustBeLowercase && (dirName != strings.ToLower(dirName)) { 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 } diff --git a/tools/formitychecker/config.yaml b/tools/formitychecker/config.yaml new file mode 100644 index 000000000..5fec42012 --- /dev/null +++ b/tools/formitychecker/config.yaml @@ -0,0 +1,47 @@ +# config.yaml 示例配置 + +# 基础配置 +baseConfig: + searchDirectory: "./" # 检索的根目录,"./" 代表当前目录 + ignoreCase: false # 是否忽略大小写,true 为忽略,false 为区分大小写 + +# 目录命名风格配置 +directoryNaming: + allowHyphens: true # 是否允许目录名中含有中划线 + allowUnderscores: false # 是否允许目录名中含有下划线 + mustBeLowercase: true # 目录名是否必须为小写 + +# 文件命名风格配置 +fileNaming: + allowHyphens: true # 是否允许文件名中含有中划线 + allowUnderscores: true # 是否允许文件名中含有下划线 + mustBeLowercase: true # 文件名是否必须为小写 + +# 忽略的文件格式列表 +ignoreFormats: + - ".log" + - ".env" + - "_test.go" + +# 忽略的目录列表 +ignoreDirectories: + - "vendor" + - ".git" + - "node_modules" + - "logs" + - "components" + - "_output" + - "README.md" + - "tools/openim-web" + - "CHANGELOG" + - "docs/readme" + +fileTypeSpecificNaming: + ".yaml": + allowHyphens: true + allowUnderscores: false + mustBeLowercase: true + ".go": + allowHyphens: false + allowUnderscores: true + mustBeLowercase: true diff --git a/tools/formitychecker/config/config.go b/tools/formitychecker/config/config.go index 0c4f6a16b..cec6e10c0 100644 --- a/tools/formitychecker/config/config.go +++ b/tools/formitychecker/config/config.go @@ -15,27 +15,66 @@ package config 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 { - TargetDirs []string // Directories to check - IgnoreDirs []string // Directories to ignore + BaseConfig struct { + 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. -func NewConfig(targetDirs, ignoreDirs string) *Config { - return &Config{ - TargetDirs: parseDirs(targetDirs), - IgnoreDirs: parseDirs(ignoreDirs), - } +type FileTypeSpecificNaming struct { + AllowHyphens bool `yaml:"allowHyphens"` + AllowUnderscores bool `yaml:"allowUnderscores"` + MustBeLowercase bool `yaml:"mustBeLowercase"` } -// parseDirs splits a comma-separated string into a slice of directory names. -func parseDirs(dirs string) []string { - if dirs == "" { - return nil - } - return strings.Split(dirs, ",") +type Issue struct { + Type string + Path string + Message string +} + +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 } diff --git a/tools/formitychecker/formitychecker.go b/tools/formitychecker/formitychecker.go index 2bedbfb32..240773458 100644 --- a/tools/formitychecker/formitychecker.go +++ b/tools/formitychecker/formitychecker.go @@ -15,27 +15,57 @@ package main import ( + "encoding/json" "flag" "fmt" + "os" "github.com/openimsdk/open-im-server/tools/formitychecker/checker" "github.com/openimsdk/open-im-server/tools/formitychecker/config" ) func main() { - defaultTargetDirs := "." - defaultIgnoreDirs := "components,.git" - - 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/)") + var configPath string + flag.StringVar(&configPath, "config", "", "Path to the configuration file") flag.Parse() - conf := config.NewConfig(targetDirs, ignoreDirs) - - err := checker.CheckDirectory(conf) - if err != nil { - fmt.Println("Error:", err) + if configPath == "" { + configPath = os.Getenv("CONFIG_PATH") } + + 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)) }