Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codecov. Minor renaming of comments handler #158

Merged
merged 4 commits into from
Dec 31, 2024
Merged
Show file tree
Hide file tree
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
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
Loading