Skip to content

Commit

Permalink
Merge pull request #158 from invopop/codecov
Browse files Browse the repository at this point in the history
Codecov. Minor renaming of comments handler
  • Loading branch information
samlown authored Dec 31, 2024
2 parents a446707 + fd4a187 commit 42b1bb0
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 65 deletions.
16 changes: 8 additions & 8 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/setup-go@v4
with:
go-version: "1.18"
cache: false

- name: Check out code
uses: actions/checkout@v3
uses: actions/checkout@v4

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: "go.mod"

- name: Lint
uses: golangci/golangci-lint-action@v3
uses: golangci/golangci-lint-action@v6
with:
version: v1.55
version: v1.62
24 changes: 14 additions & 10 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
name: Test Go
on: [push, pull_request]
on: [push]
jobs:
lint-test-build:
name: Lint, Test
test:
name: Test
runs-on: ubuntu-latest

steps:
- name: Check out code
uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v1
uses: actions/setup-go@v4
with:
go-version: "1.18"
id: go

- name: Check out code
uses: actions/checkout@v2
go-version-file: "go.mod"

- name: Install Dependencies
env:
GOPROXY: https://proxy.golang.org,direct
run: go mod download

- name: Test
run: go test -tags unit -race ./...
run: go test -race -coverprofile=coverage.out -covermode=atomic ./...

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
21 changes: 10 additions & 11 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
run:
tests: true
max-same-issues: 50
skip-dirs:
- resources
- old
skip-files:
- cmd/protopkg/main.go

output:
print-issued-lines: false
Expand All @@ -19,13 +14,12 @@ linters:
- unconvert
- goimports
- unused
- vetshadow
- govet
- nakedret
- errcheck
- revive
- ineffassign
- goconst
- vet
- unparam
- gofmt

Expand All @@ -45,14 +39,19 @@ linters-settings:
- ifElseChain
gofmt:
rewrite-rules:
- pattern: 'interface{}'
replacement: 'any'
- pattern: 'a[b:len(a)]'
replacement: 'a[b:]'
- pattern: "interface{}"
replacement: "any"
- pattern: "a[b:len(a)]"
replacement: "a[b:]"

issues:
max-per-linter: 0
max-same: 0
exclude-dirs:
- resources
- old
exclude-files:
- cmd/protopkg/main.go
exclude-use-default: false
exclude:
# Captured by errcheck.
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Test Go](https://github.com/invopop/jsonschema/actions/workflows/test.yaml/badge.svg)](https://github.com/invopop/jsonschema/actions/workflows/test.yaml)
[![Go Report Card](https://goreportcard.com/badge/github.com/invopop/jsonschema)](https://goreportcard.com/report/github.com/invopop/jsonschema)
[![GoDoc](https://godoc.org/github.com/invopop/jsonschema?status.svg)](https://godoc.org/github.com/invopop/jsonschema)
[![codecov](https://codecov.io/gh/invopop/jsonschema/graph/badge.svg?token=JMEB8W8GNZ)](https://codecov.io/gh/invopop/jsonschema)
![Latest Tag](https://img.shields.io/github/v/tag/invopop/jsonschema)

This package can be used to generate [JSON Schemas](http://json-schema.org/latest/json-schema-validation.html) from Go types through reflection.
Expand Down Expand Up @@ -52,10 +53,10 @@ jsonschema.Reflect(&TestUser{})
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema_test/sample-user",
"$ref": "#/$defs/SampleUser",
"$id": "https://github.com/invopop/jsonschema_test/test-user",
"$ref": "#/$defs/TestUser",
"$defs": {
"SampleUser": {
"TestUser": {
"oneOf": [
{
"required": ["birth_date"],
Expand Down
113 changes: 113 additions & 0 deletions fixtures/go_comments_full.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/invopop/jsonschema/examples/user",
"$ref": "#/$defs/User",
"$defs": {
"NamedPets": {
"additionalProperties": {
"$ref": "#/$defs/Pet"
},
"type": "object",
"description": "NamedPets is a map of animal names to pets."
},
"Pet": {
"properties": {
"name": {
"type": "string",
"title": "Name",
"description": "Name of the animal."
}
},
"additionalProperties": false,
"type": "object",
"required": [
"name"
],
"description": "Pet defines the user's fury friend."
},
"Pets": {
"items": {
"$ref": "#/$defs/Pet"
},
"type": "array",
"description": "Pets is a collection of Pet objects."
},
"Plant": {
"properties": {
"variant": {
"type": "string",
"title": "Variant",
"description": "This comment will be used"
},
"multicellular": {
"type": "boolean",
"title": "Multicellular",
"description": "Multicellular is true if the plant is multicellular"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"variant"
],
"description": "Plant represents the plants the user might have and serves as a test\nof structs inside a `type` set."
},
"User": {
"properties": {
"id": {
"type": "integer",
"description": "Unique sequential identifier."
},
"name": {
"type": "string",
"maxLength": 20,
"minLength": 1,
"pattern": ".*",
"title": "the name",
"description": "this is a property",
"default": "alex",
"examples": [
"joe",
"lucy"
]
},
"friends": {
"items": {
"type": "integer"
},
"type": "array",
"description": "list of IDs, omitted when empty"
},
"tags": {
"type": "object"
},
"pets": {
"$ref": "#/$defs/Pets",
"description": "An array of pets the user cares for."
},
"named_pets": {
"$ref": "#/$defs/NamedPets",
"description": "Set of animal names to pets"
},
"plants": {
"items": {
"$ref": "#/$defs/Plant"
},
"type": "array",
"title": "Plants",
"description": "Set of plants that the user likes"
}
},
"additionalProperties": false,
"type": "object",
"required": [
"id",
"name",
"pets",
"named_pets",
"plants"
],
"description": "User is used as a base to provide tests for comments.\nDon't forget to checkout the nested path."
}
}
}
12 changes: 1 addition & 11 deletions reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -1098,7 +1098,7 @@ func (t *Schema) MarshalJSON() ([]byte, error) {
if err != nil {
return nil, err
}
if t.Extras == nil || len(t.Extras) == 0 {
if len(t.Extras) == 0 {
return b, nil
}
m, err := json.Marshal(t.Extras)
Expand Down Expand Up @@ -1149,13 +1149,3 @@ func splitOnUnescapedCommas(tagString string) []string {
func fullyQualifiedTypeName(t reflect.Type) string {
return t.PkgPath() + "." + t.Name()
}

// AddGoComments will update the reflectors comment map with all the comments
// found in the provided source directories. See the #ExtractGoComments method
// for more details.
func (r *Reflector) AddGoComments(base, path string) error {
if r.CommentMap == nil {
r.CommentMap = make(map[string]string)
}
return ExtractGoComments(base, path, r.CommentMap)
}
53 changes: 43 additions & 10 deletions comment_extractor.go → reflect_comments.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,49 @@ import (
"go/token"
)

// ExtractGoComments will read all the go files contained in the provided path,
// including sub-directories, in order to generate a dictionary of comments
// associated with Types and Fields. The results will be added to the `commentsMap`
// provided in the parameters and expected to be used for Schema "description" fields.
type commentOptions struct {
fullObjectText bool // use the first sentence only?
}

// CommentOption allows for special configuration options when preparing Go
// source files for comment extraction.
type CommentOption func(*commentOptions)

// WithFullComment will configure the comment extraction to process to use an
// object type's full comment text instead of just the synopsis.
func WithFullComment() CommentOption {
return func(o *commentOptions) {
o.fullObjectText = true
}
}

// AddGoComments will update the reflectors comment map with all the comments
// found in the provided source directories including sub-directories, in order to
// generate a dictionary of comments associated with Types and Fields. The results
// will be added to the `Reflect.CommentMap` ready to use with Schema "description"
// fields.
//
// The `go/parser` library is used to extract all the comments and unfortunately doesn't
// have a built-in way to determine the fully qualified name of a package. The `base` paremeter,
// the URL used to import that package, is thus required to be able to match reflected types.
// have a built-in way to determine the fully qualified name of a package. The `base`
// parameter, the URL used to import that package, is thus required to be able to match
// reflected types.
//
// When parsing type comments, we use the `go/doc`'s Synopsis method to extract the first phrase
// only. Field comments, which tend to be much shorter, will include everything.
func ExtractGoComments(base, path string, commentMap map[string]string) error {
// When parsing type comments, by default we use the `go/doc`'s Synopsis method to extract
// the first phrase only. Field comments, which tend to be much shorter, will include everything.
// This behavior can be changed by using the `WithFullComment` option.
func (r *Reflector) AddGoComments(base, path string, opts ...CommentOption) error {
if r.CommentMap == nil {
r.CommentMap = make(map[string]string)
}
co := new(commentOptions)
for _, opt := range opts {
opt(co)
}

return r.extractGoComments(base, path, r.CommentMap, co)
}

func (r *Reflector) extractGoComments(base, path string, commentMap map[string]string, opts *commentOptions) error {
fset := token.NewFileSet()
dict := make(map[string][]*ast.Package)
err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
Expand Down Expand Up @@ -64,7 +95,9 @@ func ExtractGoComments(base, path string, commentMap map[string]string) error {
txt = gtxt
gtxt = ""
}
txt = doc.Synopsis(txt)
if !opts.fullObjectText {
txt = doc.Synopsis(txt)
}
commentMap[fmt.Sprintf("%s.%s", pkg, typ)] = strings.TrimSpace(txt)
}
case *ast.Field:
Expand Down
37 changes: 37 additions & 0 deletions reflect_comments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package jsonschema

import (
"path/filepath"
"strings"
"testing"

"github.com/invopop/jsonschema/examples"
"github.com/stretchr/testify/require"
)

func TestCommentsSchemaGeneration(t *testing.T) {
tests := []struct {
typ any
reflector *Reflector
fixture string
}{
{&examples.User{}, prepareCommentReflector(t), "fixtures/go_comments.json"},
{&examples.User{}, prepareCommentReflector(t, WithFullComment()), "fixtures/go_comments_full.json"},
}
for _, tt := range tests {
name := strings.TrimSuffix(filepath.Base(tt.fixture), ".json")
t.Run(name, func(t *testing.T) {
compareSchemaOutput(t,
tt.fixture, tt.reflector, tt.typ,
)
})
}
}

func prepareCommentReflector(t *testing.T, opts ...CommentOption) *Reflector {
t.Helper()
r := new(Reflector)
err := r.AddGoComments("github.com/invopop/jsonschema", "./examples", opts...)
require.NoError(t, err, "did not expect error while adding comments")
return r
}
Loading

0 comments on commit 42b1bb0

Please sign in to comment.