···1212build: ## Build all executables
1313 go build ./cmd/handlr
1414 go build ./cmd/lexidex
1515+ go build ./cmd/glot
15161617.PHONY: all
1718all: build
+38-10
cmd/glot/lint.go
···55 "context"
66 "encoding/json"
77 "errors"
88- "slices"
98 "fmt"
109 "io/fs"
1110 "log/slog"
···1312 "path"
1413 "path/filepath"
1514 "regexp"
1515+ "slices"
16161717 "github.com/bluesky-social/indigo/atproto/lexicon"
1818 "github.com/bluesky-social/indigo/atproto/syntax"
···107107 // parse file regularly
108108 // TODO: use json/v2 when available for case-sensitivity
109109 var sf lexicon.SchemaFile
110110- if err := json.Unmarshal(b, &sf); err != nil {
111111- return err
110110+111111+ // two-part parsing before looking at errors
112112+ err = json.Unmarshal(b, &sf)
113113+ if err == nil {
114114+ err = sf.FinishParse()
112115 }
113113- if err := sf.FinishParse(); err != nil {
114114- return err
116116+ if err != nil {
117117+ iss := LintIssue{
118118+ FilePath: p,
119119+ //NSID
120120+ LintLevel: "error",
121121+ LintName: "schema-json-parse",
122122+ LintDescription: "parsing schema JSON file",
123123+ Message: err.Error(),
124124+ }
125125+ if cmd.Bool("json") {
126126+ b, err := json.Marshal(iss)
127127+ if err != nil {
128128+ return nil
129129+ }
130130+ fmt.Println(string(b))
131131+ } else {
132132+ fmt.Printf(" 🔴 %s\n", p)
133133+ fmt.Printf(" [%s]: %s\n", iss.LintName, iss.Message)
134134+ }
135135+ return ErrLintFailures
115136 }
116137117138 issues := lintSchemaFile(p, sf)
···178199 }
179200 return nil
180201}
181181-182202183203func lintSchemaFile(p string, sf lexicon.SchemaFile) []LintIssue {
184204 issues := []LintIssue{}
···376396 }
377397 // TODO: at least one message type
378398 case lexicon.SchemaPermissionSet:
379379- if v.Description == nil || *v.Description == "" {
380380- issues = append(issues, missingDesc())
399399+ if v.Title == nil || *v.Title == "" {
400400+ issues = append(issues, LintIssue{
401401+ FilePath: p,
402402+ NSID: nsid,
403403+ LintLevel: "warn",
404404+ LintName: "permissionset-no-title",
405405+ LintDescription: "permission sets should include a title",
406406+ Message: "missing title",
407407+ })
381408 }
409409+ // TODO: missing detail
382410 // TODO: translated descriptions?
383411 if len(v.Permissions) == 0 {
384412 issues = append(issues, LintIssue{
385413 FilePath: p,
386414 NSID: nsid,
387415 LintLevel: "warn",
388388- LintName: "permissions-no-members",
416416+ LintName: "permissionset-no-permissions",
389417 LintDescription: "permission sets should define at least one permission",
390418 Message: "empty permission set",
391419 })
···450478 LintLevel: "warn",
451479 LintName: "nullable-and-optional",
452480 LintDescription: "object properties should not be both optional and nullable",
453453- Message: fmt.Sprintf("field is both nullabor and optional: %s", k),
481481+ Message: fmt.Sprintf("field is both nullable and optional: %s", k),
454482 })
455483 }
456484 }