@redpanda-data/docs-extensions-and-macros 5.31.0 → 5.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/doc-tools-mcp.js +1 -1
- package/bin/doc-tools.js +3 -1
- package/bin/mcp-tools/lint-doc-strings.js +2 -1
- package/package.json +1 -1
- package/tools/lint-strings/diff.js +15 -1
- package/tools/lint-strings/fixtures/api/lint_admin_v2.proto +114 -0
- package/tools/lint-strings/fixtures/api/lint_api.proto +123 -0
- package/tools/lint-strings/fixtures/connect/lint_connect.go +1 -1
- package/tools/lint-strings/fixtures/crd/lint_types.go +70 -0
- package/tools/lint-strings/fixtures/helm/lint_values.yaml +16 -3
- package/tools/lint-strings/index.js +2 -1
- package/tools/lint-strings/rules/common.js +87 -1
- package/tools/lint-strings/surfaces/api.js +786 -0
- package/tools/lint-strings/surfaces/crd.js +204 -7
- package/tools/lint-strings/surfaces/helm.js +338 -12
package/bin/doc-tools-mcp.js
CHANGED
|
@@ -446,7 +446,7 @@ const tools = [
|
|
|
446
446
|
},
|
|
447
447
|
surface: {
|
|
448
448
|
type: 'string',
|
|
449
|
-
description:
|
|
449
|
+
description: `Comma-separated surfaces to lint: ${Object.keys(require('../tools/lint-strings').SURFACES).join(', ')} (optional, defaults to all)`
|
|
450
450
|
},
|
|
451
451
|
diff: {
|
|
452
452
|
type: 'string',
|
package/bin/doc-tools.js
CHANGED
|
@@ -2622,7 +2622,9 @@ programCli
|
|
|
2622
2622
|
.command('lint-strings')
|
|
2623
2623
|
.description('Lint user-facing doc strings embedded in engineering source code (properties, metrics, ...)')
|
|
2624
2624
|
.requiredOption('--repo <path>', 'Path to an existing engineering checkout (for example, a local redpanda clone). Nothing is cloned.')
|
|
2625
|
-
|
|
2625
|
+
// Read off the registry rather than restated here: a hardcoded list silently
|
|
2626
|
+
// went stale the moment a surface was added.
|
|
2627
|
+
.option('--surface <list>', `Comma-separated surfaces to lint (default: all registered). Registered: ${Object.keys(require('../tools/lint-strings').SURFACES).join(', ')}`)
|
|
2626
2628
|
.option('--diff <base>', 'Declaration-anchored diff mode: lint only declarations whose full span intersects lines changed in <base>...HEAD')
|
|
2627
2629
|
.option('--format <format>', 'Output format: human or json', 'human')
|
|
2628
2630
|
.option('--skip-rules <list>', 'Comma-separated rule ids to skip')
|
|
@@ -16,7 +16,8 @@ const DOC_TOOLS_BIN = path.resolve(__dirname, '..', 'doc-tools.js');
|
|
|
16
16
|
* Lint user-facing doc strings embedded in engineering source code
|
|
17
17
|
* @param {Object} args - Arguments
|
|
18
18
|
* @param {string} args.repo - Path to the engineering checkout (required)
|
|
19
|
-
* @param {string} [args.surface] - Comma-separated surfaces
|
|
19
|
+
* @param {string} [args.surface] - Comma-separated surfaces; see the SURFACES
|
|
20
|
+
* registry in tools/lint-strings/index.js for the current list
|
|
20
21
|
* @param {string} [args.diff] - Base ref for declaration-anchored diff mode
|
|
21
22
|
* @param {string} [args.skip_rules] - Comma-separated rule ids to skip
|
|
22
23
|
* @param {string} [args.only_rules] - Comma-separated rule ids to run exclusively
|
package/package.json
CHANGED
|
@@ -26,7 +26,21 @@ const SURFACE_ROUTES = [
|
|
|
26
26
|
// console) and charts/<name>/values.yaml (connectors).
|
|
27
27
|
{ surface: 'helm', pattern: /^charts\/[^/]+\/(chart\/)?values\.yaml$/ },
|
|
28
28
|
{ surface: 'crd', pattern: /^operator\/api\// },
|
|
29
|
-
{ surface: 'connect', pattern: /^internal\/impl\// }
|
|
29
|
+
{ surface: 'connect', pattern: /^internal\/impl\// },
|
|
30
|
+
// API protos, whose comments and openapiv2 option strings reach readers as
|
|
31
|
+
// OpenAPI descriptions. console holds the data plane and Console APIs under
|
|
32
|
+
// proto/redpanda/api/; cloudv2's control plane lives under proto/public/.
|
|
33
|
+
// Anchored on the api/ segment so cloudv2's proto/descriptors/ tree (private
|
|
34
|
+
// and internal) and every vendored proto/public/**/wellknown/ path stay out.
|
|
35
|
+
{ surface: 'api', pattern: /^proto\/(?:redpanda\/api|public\/[^/]+\/redpanda\/api)\/.*\.proto$/ },
|
|
36
|
+
// Admin API v2, in the redpanda/streaming-enterprise repo. A different
|
|
37
|
+
// generator (protoc-gen-connect-openapi) and a third string form; see
|
|
38
|
+
// surfaces/api.js. These two directories are exactly what the api-docs
|
|
39
|
+
// bundler feeds into the published spec (tools/bundle-openapi.js):
|
|
40
|
+
// `admin/internal` and `core/testing` are excluded by that repo's buf.yaml
|
|
41
|
+
// and generate nothing, and `core/rest` generates a fragment that is never
|
|
42
|
+
// bundled, so none of them reach readers.
|
|
43
|
+
{ surface: 'api', pattern: /^proto\/redpanda\/core\/(?:admin\/v2|common)\/.*\.proto$/ }
|
|
30
44
|
]
|
|
31
45
|
|
|
32
46
|
/**
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// Copyright 2026 Redpanda Data, Inc.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
//
|
|
6
|
+
// A license header is a leading comment on nothing. It must not attach itself
|
|
7
|
+
// to the first declaration in the file.
|
|
8
|
+
|
|
9
|
+
syntax = "proto3";
|
|
10
|
+
|
|
11
|
+
package redpanda.core.admin.v2;
|
|
12
|
+
|
|
13
|
+
import "proto/redpanda/core/pbgen/rpc.proto";
|
|
14
|
+
|
|
15
|
+
option (pbgen.cpp_namespace) = "proto::fixture";
|
|
16
|
+
|
|
17
|
+
service FixtureService {
|
|
18
|
+
// GetFixture
|
|
19
|
+
//
|
|
20
|
+
// Returns a single fixture.
|
|
21
|
+
rpc GetFixture(GetFixtureRequest) returns (GetFixtureResponse) {
|
|
22
|
+
option (pbgen.rpc) = {
|
|
23
|
+
authz: SUPERUSER
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// CreateFixture
|
|
28
|
+
//
|
|
29
|
+
// Creates a fixture. The description can run over several comment
|
|
30
|
+
// lines, and the resolved prose joins them.
|
|
31
|
+
//
|
|
32
|
+
// A blank comment line inside the description is a paragraph break and
|
|
33
|
+
// stays part of the description.
|
|
34
|
+
rpc CreateFixture(CreateFixtureRequest)
|
|
35
|
+
returns (CreateFixtureResponse) {
|
|
36
|
+
option (pbgen.rpc) = {
|
|
37
|
+
authz: SUPERUSER
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Reports the collapsed case: with no blank line the generator uses this
|
|
42
|
+
// whole comment as BOTH the summary and the description, so the API
|
|
43
|
+
// reference's nav item becomes a paragraph.
|
|
44
|
+
rpc CollapsedFixture(CollapsedFixtureRequest)
|
|
45
|
+
returns (CollapsedFixtureResponse) {
|
|
46
|
+
option (pbgen.rpc) = {
|
|
47
|
+
authz: SUPERUSER
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// DeleteFixture.
|
|
52
|
+
//
|
|
53
|
+
// Deletes a fixture.
|
|
54
|
+
rpc DeleteFixture(DeleteFixtureRequest) returns (DeleteFixtureResponse) {
|
|
55
|
+
option (pbgen.rpc) = {
|
|
56
|
+
authz: SUPERUSER
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ListFixtures
|
|
61
|
+
// over two lines
|
|
62
|
+
//
|
|
63
|
+
// Lists every fixture.
|
|
64
|
+
rpc ListFixtures(ListFixturesRequest) returns (ListFixturesResponse) {
|
|
65
|
+
option (pbgen.rpc) = {
|
|
66
|
+
authz: SUPERUSER
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// BodylessFixture
|
|
71
|
+
//
|
|
72
|
+
// A body-less rpc opens no block, so it must not swallow the
|
|
73
|
+
// declarations that follow it.
|
|
74
|
+
rpc BodylessFixture(BodylessFixtureRequest) returns (BodylessFixtureResponse);
|
|
75
|
+
|
|
76
|
+
// WatchFixture
|
|
77
|
+
//
|
|
78
|
+
// Confirms the rpc after a body-less one is still found.
|
|
79
|
+
rpc WatchFixture(WatchFixtureRequest) returns (WatchFixtureResponse) {
|
|
80
|
+
option (pbgen.rpc) = {
|
|
81
|
+
authz: SUPERUSER
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The request for a single fixture.
|
|
87
|
+
message GetFixtureRequest {
|
|
88
|
+
// The node ID for the fixture. If unset, the broker handling the RPC
|
|
89
|
+
// returns information about itself.
|
|
90
|
+
optional int32 node_id = 1;
|
|
91
|
+
|
|
92
|
+
// Fixture name
|
|
93
|
+
string name = 2;
|
|
94
|
+
|
|
95
|
+
int32 undocumented = 3;
|
|
96
|
+
|
|
97
|
+
// Partition ID
|
|
98
|
+
int32 partition_id = 4;
|
|
99
|
+
|
|
100
|
+
// The protocol versions the fixture supports. See
|
|
101
|
+
// https://kafka.apache.org/protocol.html#protocol_compatibility
|
|
102
|
+
repeated int32 api_versions = 5;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Fixture mode
|
|
106
|
+
//
|
|
107
|
+
// - Not specified
|
|
108
|
+
// - Runs fast
|
|
109
|
+
enum FixtureMode {
|
|
110
|
+
FIXTURE_MODE_UNSPECIFIED = 0;
|
|
111
|
+
// Per-value comments are discarded by protoc-gen-connect-openapi, so they
|
|
112
|
+
// are not extracted and must not be reported as declarations.
|
|
113
|
+
FIXTURE_MODE_FAST = 1;
|
|
114
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
package redpanda.api.fixture.v1;
|
|
4
|
+
|
|
5
|
+
import "google/protobuf/descriptor.proto";
|
|
6
|
+
import "protoc-gen-openapiv2/options/annotations.proto";
|
|
7
|
+
|
|
8
|
+
// Extension fields are wire metadata, never published schema. The field
|
|
9
|
+
// inside this block must not be extracted.
|
|
10
|
+
extend google.protobuf.MethodOptions {
|
|
11
|
+
string fixture_extension = 60002;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// FixtureMode selects how the fixture behaves.
|
|
15
|
+
enum FixtureMode {
|
|
16
|
+
FIXTURE_MODE_UNSPECIFIED = 0;
|
|
17
|
+
FIXTURE_MODE_FAST = 1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// A fixture topic.
|
|
21
|
+
message FixtureTopic {
|
|
22
|
+
// A topic-level config key (e.g. `segment.bytes`).
|
|
23
|
+
string name = 1;
|
|
24
|
+
|
|
25
|
+
// Whether the config is read-only, or is dynamic and can be altered.
|
|
26
|
+
bool read_only = 2;
|
|
27
|
+
|
|
28
|
+
// Wrapped option lists must be covered by the declaration span, so a
|
|
29
|
+
// suggestion block can replace the whole statement.
|
|
30
|
+
string wrapped = 3 [
|
|
31
|
+
(buf.validate.field).string.min_len = 1,
|
|
32
|
+
(buf.validate.field).string.max_len = 255
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
// Sets default_topic_partitions when the topic is created.
|
|
36
|
+
int32 partitions = 4;
|
|
37
|
+
|
|
38
|
+
// A comment can wrap over several lines, and the resolved prose joins them
|
|
39
|
+
// rather than keeping only the first.
|
|
40
|
+
string wrapped_comment = 5;
|
|
41
|
+
|
|
42
|
+
string undocumented = 6;
|
|
43
|
+
|
|
44
|
+
// An openapiv2_field description OVERRIDES this comment, so the comment is
|
|
45
|
+
// dead text and the option's string is what must be linted.
|
|
46
|
+
string option_documented = 9 [
|
|
47
|
+
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
48
|
+
description: "The string the generator actually publishes."
|
|
49
|
+
example: "\"ignored\""
|
|
50
|
+
}
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/*
|
|
54
|
+
* A field documented ONLY by an openapiv2_field option is not an
|
|
55
|
+
* undocumented field. 73 of console's fields are this shape and every one
|
|
56
|
+
* of them used to be reported as having no description.
|
|
57
|
+
*
|
|
58
|
+
* This note is a detached block comment, separated by a blank line, so that
|
|
59
|
+
* option_only genuinely has no leading comment of its own.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
string option_only = 10 [
|
|
63
|
+
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
|
64
|
+
description: "Documented by the option alone."
|
|
65
|
+
}
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
// A oneof moves the brace depth without opening a message. Matching a bare
|
|
69
|
+
// closing brace here popped FixtureTopic and reported the sibling message
|
|
70
|
+
// below as nested inside it.
|
|
71
|
+
oneof selector {
|
|
72
|
+
// Selects by name.
|
|
73
|
+
string by_name = 7;
|
|
74
|
+
// Selects by id.
|
|
75
|
+
string by_id = 8;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Nested messages are genuinely nested and should report a dotted path.
|
|
79
|
+
message Nested {
|
|
80
|
+
// The nested value.
|
|
81
|
+
string value = 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// A sibling of FixtureTopic, not a child of it.
|
|
86
|
+
message FixtureSibling {
|
|
87
|
+
// The sibling name.
|
|
88
|
+
string name = 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
service FixtureService {
|
|
92
|
+
rpc CreateFixture(FixtureTopic) returns (FixtureTopic) {
|
|
93
|
+
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
94
|
+
summary: "Create fixture."
|
|
95
|
+
description: "Create a [fixture](https://docs.redpanda.com/fixture/) with a `{prefix}` path."
|
|
96
|
+
responses: {
|
|
97
|
+
key: "200"
|
|
98
|
+
value: {description: "OK"}
|
|
99
|
+
}
|
|
100
|
+
responses: {
|
|
101
|
+
key: "201"
|
|
102
|
+
value: {description: "Fixture created"}
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
rpc ListFixtures(FixtureTopic) returns (FixtureTopic) {
|
|
108
|
+
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
109
|
+
summary: "List fixtures"
|
|
110
|
+
description: "Short one"
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
rpc JoinFixtures(FixtureTopic) returns (FixtureTopic) {
|
|
115
|
+
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
|
116
|
+
summary: "Join fixtures"
|
|
117
|
+
// Proto concatenates adjacent string literals like C++, so the resolved
|
|
118
|
+
// description is both halves, not just the first.
|
|
119
|
+
description: "Joins two fixtures together "
|
|
120
|
+
"and returns the result."
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -28,7 +28,7 @@ This fixture exercises the ` + "`raw` + interpreted + `raw`" + ` concatenation i
|
|
|
28
28
|
This heading and the table below are legitimate AsciiDoc:
|
|
29
29
|
|
|
30
30
|
|===
|
|
31
|
-
| kafka_key | The record key
|
|
31
|
+
| `+"`kafka_key`"+` | The record key
|
|
32
32
|
|===
|
|
33
33
|
`).
|
|
34
34
|
Fields(fixtureConfigFields()...)
|
|
@@ -4,6 +4,7 @@ package v1alpha2
|
|
|
4
4
|
|
|
5
5
|
import (
|
|
6
6
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
7
|
+
"k8s.io/apimachinery/pkg/runtime"
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
// WidgetSpec defines the configuration of a fixture widget.
|
|
@@ -35,6 +36,75 @@ type WidgetReference struct {
|
|
|
35
36
|
Version int `json:"version"`
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
// EmptyOnOneLine opens and closes on a single line. Assuming it opened a
|
|
40
|
+
// struct body left the parser inside a type that had already ended, which
|
|
41
|
+
// dropped every declaration below it in the file.
|
|
42
|
+
type EmptyOnOneLine struct{}
|
|
43
|
+
|
|
44
|
+
// ValueSource represents where a value can be pulled from.
|
|
45
|
+
type ValueSource struct {
|
|
46
|
+
// The literal value.
|
|
47
|
+
Value string `json:"value,omitempty"`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// UndocumentedType has no doc comment of its own, so a field referencing it
|
|
51
|
+
// has nothing to inherit and genuinely ships blank.
|
|
52
|
+
type undocumentedTypeMarker int
|
|
53
|
+
|
|
54
|
+
type UndocumentedTarget struct {
|
|
55
|
+
// The only field.
|
|
56
|
+
Only string `json:"only,omitempty"`
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// InlinedValues is +hidefromdoc, but it is embedded with `json:",inline"`
|
|
60
|
+
// below, so crd-ref-docs flattens its fields into the embedding type and
|
|
61
|
+
// publishes every one of them. The marker suppresses this type's own section,
|
|
62
|
+
// not its fields.
|
|
63
|
+
// +hidefromdoc
|
|
64
|
+
type InlinedValues struct {
|
|
65
|
+
ReplicaCount *int32 `json:"replicaCount,omitempty"`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// HiddenNotInlined is +hidefromdoc and embedded nowhere, so nothing it
|
|
69
|
+
// declares is ever published.
|
|
70
|
+
// +hidefromdoc
|
|
71
|
+
type HiddenNotInlined struct {
|
|
72
|
+
NeverPublished string `json:"neverPublished,omitempty"`
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// EmbeddingSpec inlines a hidden type.
|
|
76
|
+
type EmbeddingSpec struct {
|
|
77
|
+
InlinedValues `json:",inline"`
|
|
78
|
+
// The name of the thing.
|
|
79
|
+
Name string `json:"name,omitempty"`
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// StructuralSpec covers external types that controller-gen publishes with no
|
|
83
|
+
// description of their own.
|
|
84
|
+
type StructuralSpec struct {
|
|
85
|
+
// +optional
|
|
86
|
+
Config *runtime.RawExtension `json:"config,omitempty"`
|
|
87
|
+
// +optional
|
|
88
|
+
Expiration *metav1.Time `json:"expiration,omitempty"`
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// FallbackSpec covers what an uncommented field actually publishes.
|
|
92
|
+
type FallbackSpec struct {
|
|
93
|
+
// +optional
|
|
94
|
+
Inherited *ValueSource `json:"inherited,omitempty"`
|
|
95
|
+
// +optional
|
|
96
|
+
FromUndocumented *UndocumentedTarget `json:"fromUndocumented,omitempty"`
|
|
97
|
+
// +optional
|
|
98
|
+
Primitive string `json:"primitive,omitempty"`
|
|
99
|
+
// +optional
|
|
100
|
+
SliceOfDocumented []ValueSource `json:"sliceOfDocumented,omitempty"`
|
|
101
|
+
// +optional
|
|
102
|
+
External *metav1.Duration `json:"external,omitempty"`
|
|
103
|
+
// How long to wait before giving up on the widget.
|
|
104
|
+
// +optional
|
|
105
|
+
Both *ValueSource `json:"both,omitempty"`
|
|
106
|
+
}
|
|
107
|
+
|
|
38
108
|
// WidgetList is ignored via the config's ignoreTypes ('List$').
|
|
39
109
|
type WidgetList struct {
|
|
40
110
|
metav1.TypeMeta `json:",inline"`
|
|
@@ -11,7 +11,8 @@ image:
|
|
|
11
11
|
tag: latest
|
|
12
12
|
|
|
13
13
|
# -- Service settings that were commented out wholesale. The nested markers
|
|
14
|
-
# below are dead:
|
|
14
|
+
# below are dead: they use the indented `# -- ` form, which helm-docs does
|
|
15
|
+
# not recognize as a marker at all, so no pipeline can attach them.
|
|
15
16
|
# service:
|
|
16
17
|
# -- set service.name to override the default service name
|
|
17
18
|
# name: redpanda
|
|
@@ -20,8 +21,9 @@ image:
|
|
|
20
21
|
# annotations: {}
|
|
21
22
|
|
|
22
23
|
|
|
23
|
-
# A marker separated from
|
|
24
|
-
#
|
|
24
|
+
# A marker separated from the next key by blank lines is NOT dead: helm-docs
|
|
25
|
+
# ignores the gap and hands it to the next real key below, which is `resources`.
|
|
26
|
+
# -- Orphaned marker; helm-docs attaches this to the next real key.
|
|
25
27
|
|
|
26
28
|
# @doc external.domain -- Optional domain advertised to external clients.
|
|
27
29
|
# @default -- `nil`
|
|
@@ -33,6 +35,17 @@ resources:
|
|
|
33
35
|
# @ignored
|
|
34
36
|
internalHiddenKey: {}
|
|
35
37
|
|
|
38
|
+
misattached:
|
|
39
|
+
# -- Written for the commented-out key below, but with no blank line and no
|
|
40
|
+
# later marker, helm-docs hands this text to realKeyBelow instead.
|
|
41
|
+
# optionalKey:
|
|
42
|
+
realKeyBelow: value
|
|
43
|
+
# -- Written for the commented-out key below. The real key that follows has
|
|
44
|
+
# its own marker, so helm-docs uses that one and nothing is misattached.
|
|
45
|
+
# anotherOptional:
|
|
46
|
+
# -- The guarded key's own description.
|
|
47
|
+
guarded: value
|
|
48
|
+
|
|
36
49
|
undocumentedTopLevel:
|
|
37
50
|
child: value
|
|
38
51
|
|
|
@@ -25,7 +25,8 @@ const SURFACES = {
|
|
|
25
25
|
rpk: require('./surfaces/rpk'),
|
|
26
26
|
helm: require('./surfaces/helm'),
|
|
27
27
|
crd: require('./surfaces/crd'),
|
|
28
|
-
connect: require('./surfaces/connect')
|
|
28
|
+
connect: require('./surfaces/connect'),
|
|
29
|
+
api: require('./surfaces/api')
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
/**
|
|
@@ -54,6 +54,79 @@ function isNameEcho (name, description) {
|
|
|
54
54
|
return true
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
/**
|
|
58
|
+
* Blank out the spans of a description where a code-ish token is expected to
|
|
59
|
+
* appear bare, so the inline-code rule only sees prose:
|
|
60
|
+
*
|
|
61
|
+
* * backticked spans - already marked up, which is the whole point;
|
|
62
|
+
* * URLs and Markdown link targets - a path inside a link is part of the
|
|
63
|
+
* link, and backticking it would break the link;
|
|
64
|
+
* * anything the declaration is named after, which name-echo owns.
|
|
65
|
+
*
|
|
66
|
+
* Replaced with spaces rather than removed so match indices stay meaningful.
|
|
67
|
+
*/
|
|
68
|
+
function maskNonProse (text) {
|
|
69
|
+
let out = text
|
|
70
|
+
const blank = (m) => ' '.repeat(m.length)
|
|
71
|
+
// Backticked spans first: a URL inside backticks is already marked up.
|
|
72
|
+
out = out.replace(/`[^`]*`/g, blank)
|
|
73
|
+
// Markdown links: mask the whole construct, target included.
|
|
74
|
+
out = out.replace(/\[[^\]]*\]\([^)]*\)/g, blank)
|
|
75
|
+
// Bare URLs, and AsciiDoc's url[text] form.
|
|
76
|
+
out = out.replace(/\b[a-z][a-z0-9+.-]*:\/\/\S+/gi, blank)
|
|
77
|
+
return out
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Tokens that are code and read as code, so they belong in inline code.
|
|
82
|
+
* Backticks are the portable choice: a single-backtick span is inline code in
|
|
83
|
+
* both AsciiDoc and Markdown, so one rule covers every surface regardless of
|
|
84
|
+
* which format the generator emits.
|
|
85
|
+
*
|
|
86
|
+
* Deliberately narrow. This rule posts inline suggestions on engineering PRs,
|
|
87
|
+
* so a false positive costs more than a miss:
|
|
88
|
+
*
|
|
89
|
+
* * snake_case with at least two segments - in this domain that is always an
|
|
90
|
+
* identifier (`default_topic_partitions`), never prose;
|
|
91
|
+
* * long flags (`--tolerate-data-loss`);
|
|
92
|
+
* * absolute paths with at least two segments (`/etc/redpanda/redpanda.yaml`,
|
|
93
|
+
* `/v1/topics`).
|
|
94
|
+
*
|
|
95
|
+
* Not included, on purpose: dotted keys (`segment.bytes`) collide with
|
|
96
|
+
* sentence boundaries and abbreviations; bare `true`/`false` and numbers
|
|
97
|
+
* appear in ordinary prose; single-segment lowercase words are unrecoverable
|
|
98
|
+
* from prose without a symbol table.
|
|
99
|
+
*/
|
|
100
|
+
const CODE_TOKEN_PATTERNS = Object.freeze([
|
|
101
|
+
{ id: 'identifier', re: /\b[a-z][a-z0-9]*(?:_[a-z0-9]+)+\b/g },
|
|
102
|
+
{ id: 'flag', re: /(?<![\w-])--[a-z][a-z0-9]*(?:-[a-z0-9]+)*\b/g },
|
|
103
|
+
{ id: 'path', re: /(?<![\w/])\/[a-z][a-zA-Z0-9_.-]*(?:\/[a-zA-Z0-9_.{}-]+)+\/?/g }
|
|
104
|
+
])
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Code-ish tokens in a description that are not inside inline code.
|
|
108
|
+
* Exported for tests and for the surfaces that scope the rule differently.
|
|
109
|
+
*/
|
|
110
|
+
function findBareCodeTokens (text, name = null) {
|
|
111
|
+
const masked = maskNonProse(String(text || ''))
|
|
112
|
+
const found = []
|
|
113
|
+
const seen = new Set()
|
|
114
|
+
for (const { id, re } of CODE_TOKEN_PATTERNS) {
|
|
115
|
+
re.lastIndex = 0
|
|
116
|
+
let match
|
|
117
|
+
while ((match = re.exec(masked)) !== null) {
|
|
118
|
+
const token = match[0]
|
|
119
|
+
// The declaration's own name restating itself is name-echo's finding,
|
|
120
|
+
// not a markup one.
|
|
121
|
+
if (name && token === name) continue
|
|
122
|
+
if (seen.has(token)) continue
|
|
123
|
+
seen.add(token)
|
|
124
|
+
found.push({ token, kind: id })
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return found
|
|
128
|
+
}
|
|
129
|
+
|
|
57
130
|
const COMMON_RULES = [
|
|
58
131
|
{
|
|
59
132
|
name: 'empty-description',
|
|
@@ -142,7 +215,20 @@ const COMMON_RULES = [
|
|
|
142
215
|
}
|
|
143
216
|
return []
|
|
144
217
|
}
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'missing-inline-code',
|
|
221
|
+
description: 'Code value or field name in prose without inline code',
|
|
222
|
+
severity: 'warning',
|
|
223
|
+
check: (decl) => {
|
|
224
|
+
const bare = findBareCodeTokens(decl.string, decl.name)
|
|
225
|
+
if (bare.length === 0) return []
|
|
226
|
+
const list = bare.map((b) => `\`${b.token}\``).join(', ')
|
|
227
|
+
return [{
|
|
228
|
+
message: `Code values and field names belong in inline code: ${list}. Wrap each in backticks, which render as inline code in both AsciiDoc and Markdown.`
|
|
229
|
+
}]
|
|
230
|
+
}
|
|
145
231
|
}
|
|
146
232
|
]
|
|
147
233
|
|
|
148
|
-
module.exports = { COMMON_RULES, JARGON_TERMS, isNameEcho, tokenize }
|
|
234
|
+
module.exports = { COMMON_RULES, JARGON_TERMS, isNameEcho, tokenize, findBareCodeTokens, maskNonProse }
|