Skip to content
This repository has been archived by the owner on Jul 11, 2024. It is now read-only.

chore: enforce naming concistency #432

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -133,6 +133,12 @@ You can think of Disgord as layered, in which case it will look something like:
### Design Decisions
Disgord should handle events, REST, voice, caching; these can be split into separate logical parts. Because of this Disgord must have an event driven architecture to support events and voice. Caching should be done behind the scenes.

For incoming events and outgoing commands/request, the parameters/structs are named to respect the direction between client and server.
A type named **ACTION**_*OBJECT* has the intent of introducing a change to the discord state, by the client. Imagine UpdateMessage, CreateChannel, etc. The opposite name pattern, *OBJECT*_**ACTION**, dictates a read only change sent by discord, such as MESSAGE_CREATE.
Common actions are GET, UPDATE, DELETE, CREATE.

This naming convention is preserved for request / response / event types, and can not be used by constants.

#### Code flow / design
Prefer procedural when possible. Note that disgord.Snowflake and disgord.Time, should be treated as OOP. Especially their .IsZero() implementation to avoid any potential zero checking.

1 change: 1 addition & 0 deletions disgord.go
Original file line number Diff line number Diff line change
@@ -119,6 +119,7 @@
package disgord

//go:generate go run internal/generate/intents/main.go
//go:generate go run internal/generate/type-naming-convention/main.go

import (
"fmt"
15 changes: 15 additions & 0 deletions internal/generate/type-naming-convention/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
A little hack to detect and fail when reserved naming convention is abused.

# DirectionalNames: "CRUD" types
to keep it simple a lot has been renamed to only use UPDATE, DELETE and CREATE ("DUC" for the rest of this section). Especially for events and REST communication.
We want to reserve names that starts and ends with "DUC" for updating and receiving information about changes at the Discord state.

In this scope, a type is used for either:
- incoming changes about the discord state. Where the naming pattern is **DUC***Object*
- to request changes to the discord state. Where the naming pattern is *Object***DUC**

examples:
- *Message***Create** (sent by discord as a consequence of change in the discord state)
- **Create***Message* (sent by client to change discord state)

By preserving we mean that only type definition are whitelisted for usage. consts, vars, etc. are not allowed to use a similar naming convention.
176 changes: 176 additions & 0 deletions internal/generate/type-naming-convention/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package main

import (
"fmt"
"k8s.io/gengo/parser"
"k8s.io/gengo/types"
"sort"
"strings"
)

const (
PKGName = "github.com/andersfylling/disgord"
)

var noStruct struct{}

func NewWhitelist(kinds ...types.Kind) Whitelist {
whitelist := Whitelist{}
for i := range kinds {
whitelist[kinds[i]] = noStruct
}
return whitelist
}

type Whitelist map[types.Kind]struct{}

func (w Whitelist) ok(kind types.Kind) bool {
if len(w) == 0 {
return true
}

_, ok := w[kind]
return ok
}

func DisgordTypes(whitelistedKinds ...types.Kind) (typesList []*types.Type, p *types.Package, err error) {
return DisgordDefinitions(func(p *types.Package) map[string]*types.Type {
return p.Types
}, whitelistedKinds...)
}

func DisgordVars(whitelistedKinds ...types.Kind) (typesList []*types.Type, p *types.Package, err error) {
return DisgordDefinitions(func(p *types.Package) map[string]*types.Type {
return p.Variables
}, whitelistedKinds...)
}

func DisgordConsts(whitelistedKinds ...types.Kind) (typesList []*types.Type, p *types.Package, err error) {
return DisgordDefinitions(func(p *types.Package) map[string]*types.Type {
return p.Constants
}, whitelistedKinds...)
}

func DisgordDefinitions(target func(p *types.Package) map[string]*types.Type, whitelistedKinds ...types.Kind) (typesList []*types.Type, p *types.Package, err error) {
builder := parser.New()
if err := builder.AddDir(PKGName); err != nil {
return nil, nil, fmt.Errorf("unable to add disgord package to gengo-parser builder. %w", err)
}

universe, err := builder.FindTypes()
if err != nil {
return nil, nil, fmt.Errorf("unable to find types for disgord package. %w", err)
}

disgord := universe.Package(PKGName)
whitelist := NewWhitelist(whitelistedKinds...)
for _, typeData := range target(disgord) {
if !whitelist.ok(typeData.Kind) {
continue
}

typesList = append(typesList, typeData)
}

return typesList, disgord, nil
}



func Sort(tSlice []*types.Type) []*types.Type {
sort.Slice(tSlice, func(i, j int) bool {
name := func(t *types.Type) string {
return strings.ToLower(t.Name.Name)
}
return name(tSlice[i]) < name(tSlice[j])
})
return tSlice
}

func FilterOutPrivateTypes(tSlice []*types.Type) []*types.Type {
IsExported := func(name string) bool {
firstChar := string(name[0])
return firstChar == strings.ToUpper(firstChar)
}

filtered := make([]*types.Type, 0, len(tSlice))
for i := range tSlice {
if !IsExported(tSlice[i].Name.Name) {
continue
}
filtered = append(filtered, tSlice[i])
}

return filtered
}

func main() {
disgordTypes, _, err := DisgordTypes()
if err != nil {
panic(err)
}

disgordTypes = FilterOutPrivateTypes(disgordTypes)
disgordTypes = Sort(disgordTypes)

disgordVars, _, err := DisgordVars()
if err != nil {
panic(err)
}

disgordVars = FilterOutPrivateTypes(disgordVars)
disgordVars = Sort(disgordVars)

disgordConsts, _, err := DisgordConsts()
if err != nil {
panic(err)
}

disgordConsts = FilterOutPrivateTypes(disgordConsts)
disgordConsts = Sort(disgordConsts)

illegals := DirectionalNamesRule(disgordVars)
illegals = append(illegals, DirectionalNamesRule(disgordVars)...)
illegals = append(illegals, DirectionalNamesRule(disgordConsts)...)

if len(illegals) > 0 {
panic(fmt.Sprintf("%+v", illegals))
}
}

func DirectionalNamesRule(typesList []*types.Type) (illegal []*types.Type) {
HasCRUDIdentifier := func(name string) bool {
for _, keyword := range []string{"Update", "Create", "Delete"} {
if strings.HasPrefix(name, keyword) || strings.HasSuffix(name, keyword) {
return true
}
}
return false
}

for _, t := range typesList {
if t.Kind == types.Struct {
continue
}

if !HasCRUDIdentifier(TypeName(t)) {
continue
}

// TODO: temporary edge case
if t.Kind == types.Interface && strings.HasSuffix(TypeName(t), "Builder") {
continue
}

illegal = append(illegal, t)
}
return illegal
}

func TypeName(t *types.Type) string {
if strings.Contains(t.Name.Name, ".") {
subs := strings.Split(t.Name.Name, ".")
return subs[len(subs)-1]
}
return t.Name.Name
}