Skip to content

Commit 24c91c0

Browse files
committed
perf(output): order jsonDiagnostic/jsonRelatedLocation pointer fields before scalars
internal/output.jsonDiagnostic interleaved Line/Column/ SourceStartLine/Deprecated (scalars) among its string/slice/pointer fields; jsonRelatedLocation interleaved Line/Column before Message. Same defect and same fix as internal/lint.Diagnostic and pkg/mdsmith.Diagnostic: Go's GC ptrdata for a struct spans from offset 0 through the last pointer-containing field, so a scalar declared before that point sits inside the scanned span for nothing. One of these is built per diagnostic on every `--format json` run (CI pipelines, `mdsmith check --format json`). Per docs/development/high-performance-go.md "Struct layout": order fields large-to-small, group pointer fields first and scalars last. Adds structlayout_test.go with the project's existing structlayout.AssertPointerFieldsFirst pattern. Reordering the Go struct fields changes encoding/json's emitted key order (Go encodes struct fields in declaration order), so TestJSONFormatter_ExactOutput is updated to match; JSON key order carries no semantic meaning and no reference doc commits to a specific order, and the CLI's own e2e JSON tests parse into a map rather than asserting exact text. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SNiF3CVZG2NCizbsTgur4V
1 parent efb2d17 commit 24c91c0

3 files changed

Lines changed: 44 additions & 19 deletions

File tree

internal/output/json.go

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,36 +10,45 @@ import (
1010
// JSONFormatter outputs diagnostics as a JSON array.
1111
type JSONFormatter struct{}
1212

13+
// Fields are ordered pointer-containing (string/slice/pointer) first,
14+
// then scalar (int/bool) last, matching internal/lint.Diagnostic's
15+
// layout. Go's GC computes a struct's ptrdata as the offset through
16+
// the last pointer-containing field; one of these is built per
17+
// diagnostic on every `--format json` run. See
18+
// docs/development/high-performance-go.md "Struct layout".
1319
type jsonDiagnostic struct {
14-
File string `json:"file"`
15-
Line int `json:"line"`
16-
Column int `json:"column"`
17-
Rule string `json:"rule"`
18-
Name string `json:"name"`
19-
Severity string `json:"severity"`
20-
Message string `json:"message"`
21-
SourceLines []string `json:"source_lines,omitempty"`
22-
SourceStartLine int `json:"source_start_line,omitempty"`
23-
Explanation *jsonExplanation `json:"explanation,omitempty"`
24-
// Deprecated and ReplacedBy mirror lint.Diagnostic's plan-136
25-
// fields so CI scripts can route a deprecation warning without
26-
// scanning the message body. Both are omitempty so non-
27-
// deprecation diagnostics stay unchanged on the wire.
28-
Deprecated bool `json:"deprecated,omitempty"`
20+
File string `json:"file"`
21+
Rule string `json:"rule"`
22+
Name string `json:"name"`
23+
Severity string `json:"severity"`
24+
Message string `json:"message"`
25+
SourceLines []string `json:"source_lines,omitempty"`
26+
Explanation *jsonExplanation `json:"explanation,omitempty"`
27+
// ReplacedBy mirrors lint.Diagnostic's plan-136 field so CI
28+
// scripts can route a deprecation warning without scanning the
29+
// message body. omitempty so non-deprecation diagnostics stay
30+
// unchanged on the wire.
2931
ReplacedBy string `json:"replaced_by,omitempty"`
3032
// RelatedLocations mirrors lint.Diagnostic's plan-230 field so CI
3133
// scripts can read the schema-constraint location without parsing
3234
// the message. omitempty so diagnostics that carry none stay
3335
// unchanged on the wire. The rule-doc URL is not emitted here — it
3436
// is derivable from the `rule` field and is an editor (LSP) concern.
3537
RelatedLocations []jsonRelatedLocation `json:"related_locations,omitempty"`
38+
39+
Line int `json:"line"`
40+
Column int `json:"column"`
41+
SourceStartLine int `json:"source_start_line,omitempty"`
42+
Deprecated bool `json:"deprecated,omitempty"`
3643
}
3744

45+
// File and Message (pointer-containing) precede Line and Column
46+
// (scalar) for the same GC-ptrdata reason as jsonDiagnostic above.
3847
type jsonRelatedLocation struct {
3948
File string `json:"file,omitempty"`
49+
Message string `json:"message"`
4050
Line int `json:"line,omitempty"`
4151
Column int `json:"column,omitempty"`
42-
Message string `json:"message"`
4352
}
4453

4554
type jsonExplanation struct {

internal/output/json_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,12 +170,12 @@ func TestJSONFormatter_ExactOutput(t *testing.T) {
170170
expected := `[
171171
{
172172
"file": "README.md",
173-
"line": 10,
174-
"column": 5,
175173
"rule": "MDS001",
176174
"name": "line-length",
177175
"severity": "error",
178-
"message": "line too long (120 \u003e 80)"
176+
"message": "line too long (120 \u003e 80)",
177+
"line": 10,
178+
"column": 5
179179
}
180180
]
181181
`
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package output
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/jeduden/mdsmith/internal/structlayout"
8+
)
9+
10+
func TestJSONDiagnostic_PointerFieldsPrecedeScalars(t *testing.T) {
11+
structlayout.AssertPointerFieldsFirst(t, reflect.TypeOf(jsonDiagnostic{}))
12+
}
13+
14+
func TestJSONRelatedLocation_PointerFieldsPrecedeScalars(t *testing.T) {
15+
structlayout.AssertPointerFieldsFirst(t, reflect.TypeOf(jsonRelatedLocation{}))
16+
}

0 commit comments

Comments
 (0)