mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 00:55:53 +08:00
119 lines
3.4 KiB
Go
119 lines
3.4 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package rules
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"github.com/larksuite/cli/internal/qualitygate/facts"
|
|
"github.com/larksuite/cli/internal/qualitygate/manifest"
|
|
"github.com/larksuite/cli/internal/qualitygate/report"
|
|
)
|
|
|
|
func CheckDefaultOutput(m manifest.Manifest) ([]report.Diagnostic, []facts.OutputFact) {
|
|
var diags []report.Diagnostic
|
|
var out []facts.OutputFact
|
|
for _, cmd := range m.Commands {
|
|
if !cmd.Runnable || !looksLikeListCommand(cmd.Path) {
|
|
continue
|
|
}
|
|
fact := facts.OutputFact{
|
|
Command: cmd.Path,
|
|
Fields: cmd.DefaultFields,
|
|
IsList: true,
|
|
HasDefaultLimit: hasBoundedDefaultLimit(cmd),
|
|
HasFieldSelector: hasAnyFlag(cmd, "fields", "field", "field-id", "select-fields"),
|
|
HasDecisionField: hasDecisionField(cmd.DefaultFields),
|
|
}
|
|
if len(cmd.DefaultFields) > 0 && (!fact.HasDefaultLimit || !fact.HasDecisionField) {
|
|
diags = append(diags, report.Diagnostic{
|
|
Rule: "default_output_contract",
|
|
Action: report.ActionReject,
|
|
File: "command-manifest",
|
|
Message: cmd.Path + " default output must include a default limit and agent decision fields",
|
|
Suggestion: "add a default page-size/page-limit and include fields such as id, name, status, url, or time in default output",
|
|
SubjectType: "output",
|
|
CommandPath: cmd.Path,
|
|
})
|
|
}
|
|
if !fact.HasDefaultLimit {
|
|
diags = append(diags, report.Diagnostic{
|
|
Rule: "default_output",
|
|
Action: report.ActionWarning,
|
|
File: "command-manifest",
|
|
Message: cmd.Path + " looks like a list command without an explicit default limit flag",
|
|
Suggestion: "add a default page-size/page-limit or document why the command is bounded",
|
|
SubjectType: "output",
|
|
CommandPath: cmd.Path,
|
|
})
|
|
}
|
|
out = append(out, fact)
|
|
}
|
|
return diags, out
|
|
}
|
|
|
|
func looksLikeListCommand(path string) bool {
|
|
parts := strings.Fields(path)
|
|
if len(parts) == 0 {
|
|
return false
|
|
}
|
|
last := parts[len(parts)-1]
|
|
return last == "list" ||
|
|
last == "search" ||
|
|
strings.HasSuffix(last, "-list") ||
|
|
strings.HasSuffix(last, "-search") ||
|
|
strings.HasSuffix(last, "_list") ||
|
|
strings.HasSuffix(last, "_search")
|
|
}
|
|
|
|
func hasAnyFlag(cmd manifest.Command, names ...string) bool {
|
|
for _, fl := range cmd.Flags {
|
|
for _, name := range names {
|
|
if fl.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func hasBoundedDefaultLimit(cmd manifest.Command) bool {
|
|
for _, fl := range cmd.Flags {
|
|
switch fl.Name {
|
|
case "page-size", "page-limit", "limit", "max":
|
|
if fl.DefValue != "" && fl.DefValue != "0" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
var decisionFieldNames = []string{"id", "name", "status", "url", "time", "created_at", "updated_at", "message_id", "file_token"}
|
|
|
|
func hasDecisionField(fields []string) bool {
|
|
want := make(map[string]bool, len(decisionFieldNames))
|
|
for _, name := range decisionFieldNames {
|
|
want[name] = true
|
|
}
|
|
for _, field := range fields {
|
|
normalized := normalizeFieldName(field)
|
|
if want[normalized] {
|
|
return true
|
|
}
|
|
for _, part := range strings.FieldsFunc(normalized, func(r rune) bool { return r == '_' }) {
|
|
if want[part] {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func normalizeFieldName(field string) string {
|
|
normalized := strings.ToLower(field)
|
|
replacer := strings.NewReplacer("-", "_", ".", "_", "/", "_", " ", "_")
|
|
return replacer.Replace(normalized)
|
|
}
|