Skip to content

Commit

Permalink
feat(generators): go option validator signature check (#194)
Browse files Browse the repository at this point in the history
ref(generators): add generation observer (#194)

ref(generators): add VirtualFS (#194)

feat(generators): implement signature hash (#194)
  • Loading branch information
plastikfan committed Sep 20, 2023
1 parent b0f08fb commit 87e078b
Show file tree
Hide file tree
Showing 30 changed files with 975 additions and 204 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
steps:
- uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: 1.21
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
Expand All @@ -20,7 +20,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.19]
go-version: [1.21]
platform: [ubuntu-latest, macos-latest]

runs-on: ${{ matrix.platform }}
Expand Down
9 changes: 8 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ linters-settings:
goconst:
min-len: 2
min-occurrences: 3
# since upgrading to v1.21.1 of go:
# ERRO [linters_context] gocritic: load embedded ruleguard
# rules: rules/rules.go:13: can't load fmt: setting an explicit GOROOT can fix this problem.
#
gocritic:
enabled-tags:
- diagnostic
Expand All @@ -24,7 +28,10 @@ linters:
disable-all: true
enable:
- bodyclose
- depguard
# depguard needs to be reviewed properly and then configured, before
# it can be re-enabled.
# https://github.com/OpenPeeDeeP/depguard#example-configs
# - depguard
- dogsled
# - dupl
- errcheck
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/golangci/golangci-lint
rev: v1.52.2
rev: v1.54.2
hooks:
- id: golangci-lint

Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"cSpell.words": [
"avfs",
"beezledub",
"bindnative",
"bodyclose",
"cogen",
"colors",
"Comparables",
"deadcode",
Expand All @@ -15,6 +17,7 @@
"faydeaudeau",
"fieldalignment",
"foobar",
"funcs",
"goconst",
"gocritic",
"gocyclo",
Expand All @@ -33,6 +36,7 @@
"ipmask",
"ipnet",
"linters",
"memfs",
"nakedret",
"nolint",
"nolintlint",
Expand Down
150 changes: 135 additions & 15 deletions CODE-GEN.md

Large diffs are not rendered by default.

26 changes: 22 additions & 4 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,20 +73,28 @@ tasks:
# we invoke the generator directly without go generate, passing
# in the test flag.

ov-gen-t:
co-gen-t:
cmds:
- mkdir -p {{.GEN_TEST_OUTPUT_DIR}}
- cobrass-gen -test -cwd ./ -templates generators/gola -write
- go fmt ./generators/gola/out/assistant/*.go

ov-gen:
cmds:
- go generate ./...
# co-gen:
# cmds:
# - go generate ./...

clear-gen-t:
cmds:
- rm -f ./generators/gola/out/assistant/*auto*.go

sign-t:
cmds:
- cobrass-gen -sign -test -cwd ./

sign:
cmds:
- cobrass-gen -sign

# === build/deploy code generator ===========================

b-gen-linux:
Expand All @@ -107,6 +115,16 @@ tasks:
generates:
- "{{.DIST_DIR}}/{{.TARGET_OS}}/{{.GEN_BINARY_NAME}}"

build-play:
vars:
APPLICATION_ENTRY: ./generators/gola/gen
cmds:
- echo "cross compiling generator from {{OS}} to {{.TARGET_OS}}"
- GOOS={{.TARGET_OS}} GOARCH={{.TARGET_ARCH}} go build -gcflags=-m ./generators/gola/gen/main.go

sources:
- ./generators/gola/**/*.go

d:
cmds:
- task: deploy
Expand Down
160 changes: 160 additions & 0 deletions generators/gola/content-parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package gola

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io/fs"
"path/filepath"
"strings"

"github.com/samber/lo"
"github.com/snivilised/cobrass/generators/gola/internal/storage"
)

// parseInline does not need to use the filesystem to acquire
// its content. It is provided as a result of the code
// generation process instead.
func parseInline(contents CodeContent) (*SignatureResult, error) {
return parseContents(contents)
}

// parseFromFS parses content acquired from the filesystem.
func parseFromFS(vfs storage.VirtualFS, directoryPath string) (*SignatureResult, error) {
var (
entries []fs.DirEntry
contents CodeContent
readErr, acqErr error
)

if entries, readErr = readEntries(vfs, directoryPath); readErr != nil {
return nil, readErr
}

if contents, acqErr = acquire(vfs, directoryPath, entries); acqErr != nil {
return nil, acqErr
}

return parseContents(contents)
}

func readEntries(vfs storage.VirtualFS, directoryPath string) ([]fs.DirEntry, error) {
entries, err := vfs.ReadDir(directoryPath)

if err != nil {
return nil, err
}

autoPattern := "*auto*.go"
testPattern := "*auto*_test.go"
entries = lo.Filter(entries, func(item fs.DirEntry, index int) bool {
auto, _ := filepath.Match(autoPattern, item.Name())
test, _ := filepath.Match(testPattern, item.Name())
return !item.IsDir() && auto && !test
})

if len(entries) == 0 {
return nil, fmt.Errorf(
"found no applicable source files at: '%v'",
directoryPath,
)
}

return entries, nil
}

func acquire(vfs storage.VirtualFS, directoryPath string, entries []fs.DirEntry) (CodeContent, error) {
contents := make(CodeContent, len(entries))

for _, entry := range entries {
name := entry.Name()
sourcePath := filepath.Join(directoryPath, name)
c, err := vfs.ReadFile(sourcePath)

if err != nil {
return nil, err
}

contents[CodeFileName(name)] = string(c)
}

return contents, nil
}

func parseContents(contents CodeContent) (*SignatureResult, error) {
ending := "\n"
hashBuilder := strings.Builder{}
metrics := make(SignatureCountsBySource)
totals := &SignatureCounts{}

for _, name := range contents.Keys() {
c := contents[name]

metrics[name] = &SignatureCounts{}
lines := strings.Split(c, ending)

for _, line := range lines {
if strings.HasPrefix(line, "func") {
index := strings.LastIndex(line, " {")
if index >= 0 {
signature := line[0 : index+1]
hashBuilder.WriteString(fmt.Sprintf("%v\n", strings.TrimSpace(signature)))
metrics[name].Func++
}
} else if strings.HasPrefix(line, "type") {
hashBuilder.WriteString(fmt.Sprintf("%v\n", strings.TrimSpace(line)))
metrics[name].Type++
}
}

totals.Func += metrics[name].Func
totals.Type += metrics[name].Type
}

sha256hash := hash(hashBuilder.String())
outputBuilder := strings.Builder{}
outputBuilder.WriteString(fmt.Sprintf("===> [🤖] THIS-HASH: '%v'\n", sha256hash))
outputBuilder.WriteString(fmt.Sprintf("===> [👾] REGISTERED-HASH: '%v'\n", RegisteredHash))

for _, n := range metrics.Keys() {
metric := metrics[n]
funcs := fmt.Sprintf("'%v'", metric.Func)
types := fmt.Sprintf("'%v'", metric.Type)
line := fmt.Sprintf(
"---> 🍄 [%36v] Signature Counts - 🍅functions: %6v, 🥦types: %6v",
n, funcs, types,
)

outputBuilder.WriteString(fmt.Sprintf("%v\n", line))
}

funcs := fmt.Sprintf("'%v'", totals.Func)
types := fmt.Sprintf("'%v'", totals.Type)
totalLine := fmt.Sprintf(
"---> 🍄 Total Counts - 🍅functions: %6v, 🥦types: %6v",
funcs, types,
)

outputBuilder.WriteString(fmt.Sprintf("%v\n", totalLine))

status := lo.Ternary(sha256hash == RegisteredHash,
"✔️ Hashes are equal",
"💥 Api changes detected",
)
outputBuilder.WriteString(fmt.Sprintf(">>>> Status: %v\n", status))

return &SignatureResult{
Totals: totals,
Counters: metrics,
Hash: sha256hash,
Status: status,
Output: outputBuilder.String(),
}, nil
}

func hash(content string) string {
hasher := sha256.New()
hasher.Write([]byte(content))

return hex.EncodeToString(hasher.Sum(nil))
}
Loading

0 comments on commit 87e078b

Please sign in to comment.