Skip to content

Commit

Permalink
Implement enum + documentation
Browse files Browse the repository at this point in the history
# Conflicts:
#	docs/static/schema.json
  • Loading branch information
RomainMuller committed Aug 30, 2024
1 parent 9b30b21 commit d61667b
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 40 deletions.
52 changes: 33 additions & 19 deletions docs/static/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -632,33 +632,47 @@
"struct-literal": {
"title": "Match struct literal expressions",
"markdownDescription": "The `struct-literal` join point matches struct literal expressions that create an instance of the named struct. In case of `match` being equal to `value-only` or `any`, the matched node will be of type `CompositeLit`, and in case of being `pointer-only`, it will be of type `UnaryExpr` (the node itself will be available in the `{{ .X }}` field). If `field` is specified, it only matches the value explicitly associated to the named field.\n\nWhen using `match: any`, the struct literal may have its address immediately taken (`&SomeType{/*...*/}`), and associated advice must be carefully designed to avoid breaking this (for example, wrapping it in an immediately-invoked function expression makes it impossible to take the value's address without first assigning it to a variable).",
"type": "object",
"required": ["type"],
"properties": {
"type": {
"description": "The fully qualified type name of the struct to match.",
"$ref": "#/$defs/go/qualified-identifier"
},
"field": {
"description": "Only match struct literal expressions that include the specified field name.",
"$ref": "#/$defs/go/identifier"
"oneOf": [
{
"type": "object",
"required": ["type", "field"],
"properties": {
"type": {
"description": "The fully qualified type name of the struct to match.",
"$ref": "#/$defs/go/qualified-identifier"
},
"field": {
"description": "Only match struct literal expressions that include the specified field name.",
"$ref": "#/$defs/go/identifier"
}
},
"additionalProperties": false
},
"match": {
"description": "The struct literal expression style to match (value-only, pointer-only or any)",
"type": "string",
"enum": ["value-only", "pointer-only", "any"],
"default": "any"
{
"type": "object",
"required": ["type"],
"properties": {
"type": {
"description": "The fully qualified type name of the struct to match.",
"$ref": "#/$defs/go/qualified-identifier"
},
"match": {
"description": "The struct literal expression style to match (value-only, pointer-only or any)",
"type": "string",
"enum": ["value-only", "pointer-only", "any"],
"default": "any"
}
},
"additionalProperties": false
}
},
"additionalProperties": false
]
}
},
"examples": [
{
"struct-literal": {
"type": "net/http.Server",
"field": "Handler",
"match": "any"
"field": "Handler"
}
},
{
Expand Down
122 changes: 109 additions & 13 deletions internal/injector/aspect/join/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,41 @@ func (s *structDefinition) RenderHTML() string {
return fmt.Sprintf(`<div class="flex join-point struct-definition"><span class="type">Definition of</span>%s</div>`, s.typeName.RenderHTML())
}

type structLiteral struct {
typeName TypeName
field string
match string
}
type (
StructLiteralMatch int
structLiteral struct {
typeName TypeName
field string
match StructLiteralMatch
}
)

func StructLiteral(typeName TypeName, field string, match string) *structLiteral {
const (
// StructLiteralMatchAny matches struct literals regardless of whether they are pointer or value.
// [StructLiteral] join points specified with this match type may match [*dst.CompositeLit] or
// [*dst.UnaryExpr] nodes.
StructLiteralMatchAny StructLiteralMatch = iota
// StructLiteralMatchValueOnly matches struct literals that are not pointers. [StructLiteral] join
// points specified with this match type only ever match [*dst.CompositeLit] nodes.
StructLiteralMatchValueOnly
// StructLiteralMatchPointerOnly matches struct literals that are pointers. [StructLiteral] join
// points specified with this match type only ever match [*dst.UnaryExpr] nodes.
StructLiteralMatchPointerOnly
)

// StructLiteralField matches a specific field in struct literals of the designated type.
func StructLiteralField(typeName TypeName, field string) *structLiteral {
return &structLiteral{
typeName: typeName,
field: field,
}
}

// StructLiteral matches struct literal expressions of the designated type, filtered by the
// specified match type.
func StructLiteral(typeName TypeName, match StructLiteralMatch) *structLiteral {
return &structLiteral{
typeName: typeName,
match: match,
}
}
Expand All @@ -84,15 +109,15 @@ func (s *structLiteral) ImpliesImported() []string {
func (s *structLiteral) Matches(ctx context.AspectContext) bool {
if s.field == "" {
switch s.match {
case "pointer-only":
case StructLiteralMatchPointerOnly:
// match only if the current node is equal to & and the underlying node matches
// the struct literal we are looking for
if expr, ok := ctx.Node().(*dst.UnaryExpr); ok && expr.Op == token.AND {
return s.matchesLiteral(expr.X)
}
return false

case "value-only":
case StructLiteralMatchValueOnly:
// do not match if the parent is equal to &
if parent := ctx.Parent(); parent != nil {
if expr, ok := parent.Node().(*dst.UnaryExpr); ok && expr.Op == token.AND {
Expand Down Expand Up @@ -132,7 +157,10 @@ func (s *structLiteral) matchesLiteral(node dst.Node) bool {
}

func (s *structLiteral) AsCode() jen.Code {
return jen.Qual(pkgPath, "StructLiteral").Call(s.typeName.AsCode(), jen.Lit(s.field), jen.Lit(s.match))
if s.field != "" {
return jen.Qual(pkgPath, "StructLiteralField").Call(s.typeName.AsCode(), jen.Lit(s.field))
}

Check warning on line 162 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L162

Added line #L162 was not covered by tests
return jen.Qual(pkgPath, "StructLiteral").Call(s.typeName.AsCode(), s.match.asCode())
}

func (s *structLiteral) RenderHTML() string {
Expand All @@ -152,6 +180,19 @@ func (s *structLiteral) RenderHTML() string {
buf.WriteString("\n </code>\n")
buf.WriteString(" </li>\n")
buf.WriteString(" </ul>\n")
} else if s.match != StructLiteralMatchAny {
buf.WriteString(" <ul>\n")
buf.WriteString(" <li class=\"flex\">\n")
buf.WriteString(" <span class=\"type\">Only as</span>\n")
buf.WriteString(" <code>\n")
if s.match == StructLiteralMatchValueOnly {
buf.WriteString("value\n")
} else {
buf.WriteString("pointer\n")
}

Check warning on line 192 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L192

Added line #L192 was not covered by tests
buf.WriteString(" </code>\n")
buf.WriteString(" </li>\n")
buf.WriteString(" </ul>\n")
}
buf.WriteString("</div>\n")

Expand Down Expand Up @@ -179,7 +220,7 @@ func init() {
var spec struct {
Type string
Field string
Match string
Match StructLiteralMatch
}
if err := node.Decode(&spec); err != nil {
return nil, err
Expand All @@ -189,9 +230,64 @@ func init() {
if err != nil {
return nil, err
}
if spec.Match == "" {
spec.Match = "any"

if spec.Field != "" {
if spec.Match != StructLiteralMatchAny {
return nil, fmt.Errorf("struct-literal.field is not allowed with struct-literal.match: %s", spec.Match)
}

Check warning on line 237 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L236-L237

Added lines #L236 - L237 were not covered by tests
return StructLiteralField(tn, spec.Field), nil
}
return StructLiteral(tn, spec.Field, spec.Match), nil

return StructLiteral(tn, spec.Match), nil
}
}

var _ yaml.Unmarshaler = (*StructLiteralMatch)(nil)

func (s *StructLiteralMatch) UnmarshalYAML(node *yaml.Node) error {
var name string
if err := node.Decode(&name); err != nil {
return err
}

Check warning on line 251 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L250-L251

Added lines #L250 - L251 were not covered by tests

switch name {
case "any":
*s = StructLiteralMatchAny

Check warning on line 255 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L254-L255

Added lines #L254 - L255 were not covered by tests
case "value-only":
*s = StructLiteralMatchValueOnly
case "pointer-only":
*s = StructLiteralMatchPointerOnly
default:
return fmt.Errorf("invalid struct-literal.match value: %q", name)

Check warning on line 261 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L260-L261

Added lines #L260 - L261 were not covered by tests
}

return nil
}

func (s StructLiteralMatch) String() string {
switch s {
case StructLiteralMatchAny:
return "any"
case StructLiteralMatchValueOnly:
return "value-only"
case StructLiteralMatchPointerOnly:
return "pointer-only"
default:
panic(fmt.Errorf("invalid StructLiteralMatch(%d)", int(s)))

Check warning on line 276 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L267-L276

Added lines #L267 - L276 were not covered by tests
}
}

func (s StructLiteralMatch) asCode() jen.Code {
var constName string
switch s {
case StructLiteralMatchAny:
constName = "StructLiteralMatchAny"
case StructLiteralMatchValueOnly:
constName = "StructLiteralMatchValueOnly"
case StructLiteralMatchPointerOnly:
constName = "StructLiteralMatchPointerOnly"
default:
panic(fmt.Errorf("invalid StructLiteralMatch(%d)", int(s)))

Check warning on line 290 in internal/injector/aspect/join/struct.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/aspect/join/struct.go#L289-L290

Added lines #L289 - L290 were not covered by tests
}
return jen.Qual(pkgPath, constName)
}
12 changes: 6 additions & 6 deletions internal/injector/builtin/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions internal/injector/builtin/generator/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ func documentSchemaInstance(schema *jsonschema.Schema, path string) error {
if len(schema.Examples) > 0 {
fmt.Fprintln(file, "## Examples")
fmt.Fprintln(file)
for _, ex := range schema.Examples {
for idx, ex := range schema.Examples {
if err := schema.Validate(ex); err != nil {
return fmt.Errorf("invalid example: %w", err)
return fmt.Errorf("invalid example (index %d): %w", idx, err)

Check warning on line 129 in internal/injector/builtin/generator/schema.go

View check run for this annotation

Codecov / codecov/patch

internal/injector/builtin/generator/schema.go#L129

Added line #L129 was not covered by tests
}

yml, err := yaml.Marshal(ex)
Expand Down

0 comments on commit d61667b

Please sign in to comment.