@redpanda-data/docs-extensions-and-macros 5.14.0 → 5.17.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 +88 -0
- package/bin/doc-tools.js +252 -102
- package/bin/mcp-tools/index.js +20 -0
- package/bin/mcp-tools/lint-doc-strings.js +75 -0
- package/bin/mcp-tools/overrides-audit.js +100 -0
- package/bin/mcp-tools/preview-doc-string.js +71 -0
- package/cli-utils/diff-utils.js +77 -0
- package/cli-utils/helm-commented-values.js +172 -23
- package/package.json +2 -1
- package/tools/generate-cli-docs.js +28 -67
- package/tools/lint-strings/diff.js +137 -0
- package/tools/lint-strings/engine.js +163 -0
- package/tools/lint-strings/fixtures/connect/lint_connect.go +67 -0
- package/tools/lint-strings/fixtures/crd/crd-ref-docs-config.yaml +15 -0
- package/tools/lint-strings/fixtures/crd/lint_types.go +57 -0
- package/tools/lint-strings/fixtures/helm/lint_values.yaml +47 -0
- package/tools/lint-strings/fixtures/metrics/lint_probe.cc +41 -0
- package/tools/lint-strings/fixtures/properties/lint_fixture.cc +51 -0
- package/tools/lint-strings/fixtures/properties/lint_fixture.h +33 -0
- package/tools/lint-strings/fixtures/rpk/lint_cmd.go +69 -0
- package/tools/lint-strings/go-source.js +327 -0
- package/tools/lint-strings/index.js +229 -0
- package/tools/lint-strings/rules/common.js +148 -0
- package/tools/lint-strings/rules/verbatim-asciidoc.js +108 -0
- package/tools/lint-strings/source-text.js +41 -0
- package/tools/lint-strings/surfaces/connect.js +233 -0
- package/tools/lint-strings/surfaces/crd.js +239 -0
- package/tools/lint-strings/surfaces/helm.js +159 -0
- package/tools/lint-strings/surfaces/metrics.js +368 -0
- package/tools/lint-strings/surfaces/properties.js +216 -0
- package/tools/lint-strings/surfaces/rpk.js +377 -0
- package/tools/overrides-audit/README.adoc +152 -0
- package/tools/overrides-audit/adapters/connect.js +144 -0
- package/tools/overrides-audit/adapters/properties.js +117 -0
- package/tools/overrides-audit/adapters/rpk.js +192 -0
- package/tools/overrides-audit/classify.js +546 -0
- package/tools/overrides-audit/index.js +185 -0
- package/tools/preview-string/index.js +245 -0
- package/tools/property-extractor/Makefile +9 -4
- package/tools/property-extractor/parser.py +26 -0
- package/tools/property-extractor/property_extractor.py +41 -0
- package/tools/property-extractor/transformers.py +15 -2
- package/tools/rpk-docs/detect-platform-commands.js +8 -1
- package/tools/rpk-docs/scan-deprecated-commands.js +15 -18
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const yaml = require('js-yaml')
|
|
6
|
+
|
|
7
|
+
const { SourceCache } = require('../source-text')
|
|
8
|
+
const { collectGoFiles } = require('../go-source')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* CRD surface: Go doc comments above struct fields in the operator API
|
|
12
|
+
* types (operator/api/redpanda/...). These comments ship verbatim to the
|
|
13
|
+
* generated CRD reference on docs.redpanda.com AND to `kubectl explain`.
|
|
14
|
+
*
|
|
15
|
+
* User-facing filtering mirrors crd-ref-docs: the surface reads
|
|
16
|
+
* operator/crd-ref-docs-config.yaml for the `hidefromdoc` custom marker and
|
|
17
|
+
* the processor ignoreTypes/ignoreFields regexes, so fields the generator
|
|
18
|
+
* never documents are never linted.
|
|
19
|
+
*
|
|
20
|
+
* Marker lines (+kubebuilder:..., +optional, +required, +genclient, and any
|
|
21
|
+
* other +directive) are stripped before the prose is judged.
|
|
22
|
+
*
|
|
23
|
+
* The signature bad pattern here is a description that leads with the Go
|
|
24
|
+
* field identifier ("ClusterSource is a reference to...") when users type
|
|
25
|
+
* the json key ("cluster") - the docs and kubectl explain both show the
|
|
26
|
+
* json name, so the Go name means nothing to readers.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
const CONVENTION = {
|
|
30
|
+
case: 'sentence',
|
|
31
|
+
terminal_period: true,
|
|
32
|
+
verbatim_asciidoc: true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const API_ROOT = path.join('operator', 'api', 'redpanda')
|
|
36
|
+
const CONFIG_PATH = path.join('operator', 'crd-ref-docs-config.yaml')
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Compile a list of regex source strings, skipping (with a warning) any
|
|
40
|
+
* pattern that isn't valid JS regex syntax instead of throwing. The config
|
|
41
|
+
* file is written for a Go tool (crd-ref-docs), so it can legally contain
|
|
42
|
+
* Go/RE2-only constructs like `(?i)` or `(?P<name>...)` that JS's RegExp
|
|
43
|
+
* rejects - one such pattern must not abort the entire lint run.
|
|
44
|
+
*/
|
|
45
|
+
function compilePatterns (patterns, configKey) {
|
|
46
|
+
const compiled = []
|
|
47
|
+
for (const p of patterns) {
|
|
48
|
+
try {
|
|
49
|
+
compiled.push(new RegExp(p))
|
|
50
|
+
} catch (err) {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.warn(`lint-strings: skipping invalid ${configKey} pattern in ${CONFIG_PATH} (${JSON.stringify(p)}): ${err.message}`)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return compiled
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Load ignore rules from crd-ref-docs-config.yaml (absent file = no rules). */
|
|
59
|
+
function loadConfig (repo) {
|
|
60
|
+
const configPath = path.join(repo, CONFIG_PATH)
|
|
61
|
+
const config = { ignoreTypes: [], ignoreFields: [], hiddenMarker: 'hidefromdoc' }
|
|
62
|
+
if (!fs.existsSync(configPath)) return config
|
|
63
|
+
const parsed = yaml.load(fs.readFileSync(configPath, 'utf8')) || {}
|
|
64
|
+
const processor = parsed.processor || {}
|
|
65
|
+
config.ignoreTypes = compilePatterns(processor.ignoreTypes || [], 'processor.ignoreTypes')
|
|
66
|
+
config.ignoreFields = compilePatterns(processor.ignoreFields || [], 'processor.ignoreFields')
|
|
67
|
+
return config
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function matchesAny (patterns, ...candidates) {
|
|
71
|
+
return patterns.some((pattern) => candidates.some((c) => pattern.test(c)))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Parse one Go file's struct fields. Exported for tests.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} content - File content
|
|
78
|
+
* @param {string} file - Repo-relative path
|
|
79
|
+
* @param {Object} config - From loadConfig
|
|
80
|
+
* @returns {Array} declarations (without declaration_text)
|
|
81
|
+
*/
|
|
82
|
+
function scanFile (content, file, config = { ignoreTypes: [], ignoreFields: [], hiddenMarker: 'hidefromdoc' }) {
|
|
83
|
+
const declarations = []
|
|
84
|
+
const lines = content.split('\n')
|
|
85
|
+
const packageMatch = content.match(/^package\s+(\w+)/m)
|
|
86
|
+
const pkg = packageMatch ? packageMatch[1] : ''
|
|
87
|
+
|
|
88
|
+
let comment = [] // pending comment lines: { line (0-indexed), text }
|
|
89
|
+
let struct = null // { name, hidden } while inside a struct body
|
|
90
|
+
let depth = 0
|
|
91
|
+
|
|
92
|
+
for (let i = 0; i < lines.length; i++) {
|
|
93
|
+
const line = lines[i]
|
|
94
|
+
const trimmed = line.trim()
|
|
95
|
+
|
|
96
|
+
if (struct === null) {
|
|
97
|
+
if (/^\s*\/\//.test(line)) {
|
|
98
|
+
comment.push({ line: i, text: trimmed.replace(/^\/\/\s?/, '') })
|
|
99
|
+
continue
|
|
100
|
+
}
|
|
101
|
+
const typeMatch = line.match(/^type\s+([A-Za-z0-9_]+)\s+struct\s*\{/)
|
|
102
|
+
if (typeMatch) {
|
|
103
|
+
const name = typeMatch[1]
|
|
104
|
+
const hidden = comment.some((c) => c.text.trim().startsWith(`+${config.hiddenMarker}`)) ||
|
|
105
|
+
matchesAny(config.ignoreTypes, name, `${pkg}.${name}`)
|
|
106
|
+
struct = { name, hidden, exported: /^[A-Z]/.test(name) }
|
|
107
|
+
depth = 1
|
|
108
|
+
comment = []
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (trimmed !== '') comment = []
|
|
112
|
+
continue
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Inside a struct body.
|
|
116
|
+
if (/^\s*\/\//.test(line)) {
|
|
117
|
+
comment.push({ line: i, text: trimmed.replace(/^\/\/\s?/, '') })
|
|
118
|
+
continue
|
|
119
|
+
}
|
|
120
|
+
if (trimmed === '') {
|
|
121
|
+
comment = []
|
|
122
|
+
continue
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Track nested braces (anonymous struct fields) without leaving the type.
|
|
126
|
+
const opens = (trimmed.match(/\{/g) || []).length
|
|
127
|
+
const closes = (trimmed.match(/\}/g) || []).length
|
|
128
|
+
const fieldMatch = line.match(/^\s*([A-Z][A-Za-z0-9_]*)\s+\S/)
|
|
129
|
+
if (fieldMatch && depth === 1 && !struct.hidden && struct.exported) {
|
|
130
|
+
const goName = fieldMatch[1]
|
|
131
|
+
const tagMatch = line.match(/`[^`]*json:"([^",]*)[^`]*`/)
|
|
132
|
+
const jsonName = tagMatch ? tagMatch[1] : null
|
|
133
|
+
const hiddenField = comment.some((c) => c.text.trim().startsWith(`+${config.hiddenMarker}`))
|
|
134
|
+
// Only json-serialized, non-inline, non-ignored fields ship to users.
|
|
135
|
+
if (jsonName && jsonName !== '-' && !hiddenField &&
|
|
136
|
+
!matchesAny(config.ignoreFields, goName, goName.toLowerCase(), jsonName)) {
|
|
137
|
+
const prose = comment
|
|
138
|
+
.filter((c) => !c.text.trim().startsWith('+'))
|
|
139
|
+
.map((c) => c.text)
|
|
140
|
+
.join('\n')
|
|
141
|
+
.trim()
|
|
142
|
+
declarations.push({
|
|
143
|
+
surface: 'crd',
|
|
144
|
+
name: jsonName,
|
|
145
|
+
file,
|
|
146
|
+
line_start: (comment.length > 0 ? comment[0].line : i) + 1,
|
|
147
|
+
line_end: i + 1,
|
|
148
|
+
string: prose || null,
|
|
149
|
+
declaration_text: null,
|
|
150
|
+
convention: CONVENTION,
|
|
151
|
+
meta: {
|
|
152
|
+
kind: 'field',
|
|
153
|
+
struct: struct.name,
|
|
154
|
+
go_name: goName,
|
|
155
|
+
json_name: jsonName
|
|
156
|
+
}
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
comment = []
|
|
161
|
+
depth += opens - closes
|
|
162
|
+
if (depth <= 0) struct = null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return declarations
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Extract CRD field declarations.
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} options - { repo, files (Set of repo-relative paths, diff
|
|
172
|
+
* mode; when omitted, scans operator/api/redpanda), log }
|
|
173
|
+
*/
|
|
174
|
+
function extract ({ repo, files = null }) {
|
|
175
|
+
const config = loadConfig(repo)
|
|
176
|
+
let fileList
|
|
177
|
+
if (files) {
|
|
178
|
+
fileList = [...files].filter((f) => f.endsWith('.go') && !f.endsWith('_test.go') && !path.basename(f).startsWith('zz_generated'))
|
|
179
|
+
} else {
|
|
180
|
+
fileList = collectGoFiles(path.join(repo, API_ROOT))
|
|
181
|
+
.filter((f) => !path.basename(f).startsWith('zz_generated'))
|
|
182
|
+
.map((f) => path.join(API_ROOT, f))
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const cache = new SourceCache(repo)
|
|
186
|
+
const declarations = []
|
|
187
|
+
for (const file of fileList) {
|
|
188
|
+
const absPath = path.isAbsolute(file) ? file : path.join(repo, file)
|
|
189
|
+
if (!fs.existsSync(absPath)) continue
|
|
190
|
+
const content = fs.readFileSync(absPath, 'utf8')
|
|
191
|
+
if (!content.includes('struct')) continue
|
|
192
|
+
for (const decl of scanFile(content, file, config)) {
|
|
193
|
+
decl.declaration_text = cache.span(file, decl.line_start, decl.line_end)
|
|
194
|
+
declarations.push(decl)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return declarations
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Surface-specific convention rules. */
|
|
201
|
+
const RULES = [
|
|
202
|
+
{
|
|
203
|
+
name: 'undocumented-field',
|
|
204
|
+
description: 'Exported user-facing field with no doc comment',
|
|
205
|
+
severity: 'warning',
|
|
206
|
+
check: (decl) => {
|
|
207
|
+
if (decl.string !== null) return []
|
|
208
|
+
return [{ message: `Field "${decl.meta.go_name}" (json: "${decl.name}") in ${decl.meta.struct} has no doc comment. It ships blank in the CRD reference and in kubectl explain.` }]
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'go-field-name-first',
|
|
213
|
+
description: 'Description leads with the Go field name instead of the YAML key',
|
|
214
|
+
severity: 'warning',
|
|
215
|
+
check: (decl) => {
|
|
216
|
+
const text = (decl.string || '').trim()
|
|
217
|
+
if (!text) return []
|
|
218
|
+
const firstWord = (text.match(/^[A-Za-z0-9_]+/) || [null])[0]
|
|
219
|
+
if (!firstWord) return []
|
|
220
|
+
if (firstWord === decl.meta.go_name &&
|
|
221
|
+
decl.meta.go_name.toLowerCase() !== decl.meta.json_name.toLowerCase()) {
|
|
222
|
+
return [{ message: `Description starts with the Go field name "${decl.meta.go_name}", but users type "${decl.meta.json_name}" in YAML. Describe the json key: "${decl.meta.json_name}" (or start with what the field does).` }]
|
|
223
|
+
}
|
|
224
|
+
return []
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
name: 'crd',
|
|
231
|
+
convention: CONVENTION,
|
|
232
|
+
extract,
|
|
233
|
+
scanFile,
|
|
234
|
+
loadConfig,
|
|
235
|
+
rules: RULES,
|
|
236
|
+
// Missing prose is surfaced by the crd-specific undocumented-field rule
|
|
237
|
+
// (warning, per the docs contract) instead of the generic error.
|
|
238
|
+
skipRules: ['empty-description']
|
|
239
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
|
|
6
|
+
const { SourceCache } = require('../source-text')
|
|
7
|
+
const { parseValuesFile: parseSharedValuesFile } = require('../../../cli-utils/helm-commented-values')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Helm surface: helm-docs description comments in chart values.yaml files
|
|
11
|
+
* (charts/<name>/chart/values.yaml, plus charts/<name>/values.yaml for
|
|
12
|
+
* charts without the chart/ subdir).
|
|
13
|
+
*
|
|
14
|
+
* Documentation conventions - the union of what actually ships:
|
|
15
|
+
* - helm-docs: `# -- description` (with plain `# ...` continuation lines)
|
|
16
|
+
* attaches to the real key DIRECTLY below the comment block; `# @default
|
|
17
|
+
* -- text`, `# @raw`, and `# @ignored` annotate it.
|
|
18
|
+
* - doc-tools helm-spec (cli-utils/helm-commented-values.js): a `# --`
|
|
19
|
+
* block directly above a COMMENTED-OUT key documents that key, and
|
|
20
|
+
* `# @doc full.path -- description` documents any path explicitly.
|
|
21
|
+
*
|
|
22
|
+
* Both conventions are decided by ONE walk, cli-utils/helm-commented-values
|
|
23
|
+
* `parseValuesFile`, which this surface calls with attachRealKeys so it also
|
|
24
|
+
* models helm-docs' own real-key attachment. This file used to hold a
|
|
25
|
+
* byte-identical copy of that module's nine regexes and a near-copy of its
|
|
26
|
+
* state machine, on the stated grounds that "the linter never calls a marker
|
|
27
|
+
* dead that the generator in this repo renders" - which nothing enforced.
|
|
28
|
+
* Sharing the walk is what actually enforces it.
|
|
29
|
+
*
|
|
30
|
+
* A DEAD marker is a `# --` block neither pipeline can attach: one buried
|
|
31
|
+
* inside another commented-out key's subtree (the classic commented-out
|
|
32
|
+
* example block), or one separated from any key by a blank line. Its
|
|
33
|
+
* description silently never ships - that is the error this surface exists
|
|
34
|
+
* to catch.
|
|
35
|
+
*
|
|
36
|
+
* helm-docs output is markdown converted via pandoc, not verbatim AsciiDoc,
|
|
37
|
+
* so the verbatim escaping rules do not apply. Terminal periods are
|
|
38
|
+
* optional prose style here; only capitalization is enforced (via the
|
|
39
|
+
* common starts-lowercase rule).
|
|
40
|
+
*/
|
|
41
|
+
|
|
42
|
+
const CONVENTION = {
|
|
43
|
+
case: 'sentence',
|
|
44
|
+
terminal_period: 'optional',
|
|
45
|
+
verbatim_asciidoc: false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse one values.yaml into lint declarations. Exported for tests.
|
|
50
|
+
*
|
|
51
|
+
* The attachment decisions and the line spans both come from the shared walk;
|
|
52
|
+
* this function only reshapes its records into the linter's declaration form.
|
|
53
|
+
*
|
|
54
|
+
* @param {string} content - File content
|
|
55
|
+
* @param {string} file - Repo-relative path
|
|
56
|
+
* @returns {Array} declarations (without declaration_text)
|
|
57
|
+
*/
|
|
58
|
+
function parseValuesFile (content, file) {
|
|
59
|
+
return parseSharedValuesFile(content, { attachRealKeys: true }).map((r) => {
|
|
60
|
+
// The generator keeps newlines in a description; helm-docs renders one
|
|
61
|
+
// paragraph, and the rules read one string, so flatten here.
|
|
62
|
+
const string = r.descLines.map((l) => l.trim()).filter(Boolean).join(' ') || null
|
|
63
|
+
let meta
|
|
64
|
+
if (r.kind === 'dead-marker') {
|
|
65
|
+
meta = { kind: 'dead-marker', unverifiable: true }
|
|
66
|
+
} else if (r.undocumented) {
|
|
67
|
+
// Nothing to lint for content: the rule fires on the absence itself.
|
|
68
|
+
meta = { kind: 'key', top_level: true, undocumented: true, unverifiable: true }
|
|
69
|
+
} else if (r.commentedOut) {
|
|
70
|
+
meta = { kind: 'key', commented_out: true, top_level: r.topLevel, default_annotation: r.default || null }
|
|
71
|
+
} else {
|
|
72
|
+
meta = { kind: 'key', top_level: r.topLevel, raw: Boolean(r.annotations.raw), default_annotation: r.default || null }
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
surface: 'helm',
|
|
76
|
+
name: r.kind === 'dead-marker' ? null : r.path,
|
|
77
|
+
file,
|
|
78
|
+
line_start: r.lineStart + 1, // 0-indexed -> 1-indexed
|
|
79
|
+
line_end: r.lineEnd + 1,
|
|
80
|
+
string: r.undocumented ? null : string,
|
|
81
|
+
declaration_text: null,
|
|
82
|
+
convention: CONVENTION,
|
|
83
|
+
meta
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Extract helm declarations.
|
|
90
|
+
*
|
|
91
|
+
* @param {Object} options - { repo, files (Set of repo-relative paths, diff
|
|
92
|
+
* mode; when omitted, scans charts/<*>/chart/values.yaml and
|
|
93
|
+
* charts/<*>/values.yaml), log }
|
|
94
|
+
*/
|
|
95
|
+
function extract ({ repo, files = null }) {
|
|
96
|
+
let fileList
|
|
97
|
+
if (files) {
|
|
98
|
+
fileList = [...files]
|
|
99
|
+
} else {
|
|
100
|
+
fileList = []
|
|
101
|
+
const chartsRoot = path.join(repo, 'charts')
|
|
102
|
+
if (fs.existsSync(chartsRoot)) {
|
|
103
|
+
for (const entry of fs.readdirSync(chartsRoot, { withFileTypes: true })) {
|
|
104
|
+
if (!entry.isDirectory()) continue
|
|
105
|
+
for (const candidate of [
|
|
106
|
+
path.join('charts', entry.name, 'chart', 'values.yaml'),
|
|
107
|
+
path.join('charts', entry.name, 'values.yaml')
|
|
108
|
+
]) {
|
|
109
|
+
if (fs.existsSync(path.join(repo, candidate))) fileList.push(candidate)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const cache = new SourceCache(repo)
|
|
116
|
+
const declarations = []
|
|
117
|
+
for (const file of fileList) {
|
|
118
|
+
const absPath = path.isAbsolute(file) ? file : path.join(repo, file)
|
|
119
|
+
if (!fs.existsSync(absPath)) continue
|
|
120
|
+
const content = fs.readFileSync(absPath, 'utf8')
|
|
121
|
+
for (const decl of parseValuesFile(content, file)) {
|
|
122
|
+
decl.declaration_text = cache.span(file, decl.line_start, decl.line_end)
|
|
123
|
+
declarations.push(decl)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return declarations
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Surface-specific convention rules. */
|
|
130
|
+
const RULES = [
|
|
131
|
+
{
|
|
132
|
+
name: 'dead-marker',
|
|
133
|
+
description: 'A # -- marker no docs pipeline can attach to any key',
|
|
134
|
+
severity: 'error',
|
|
135
|
+
runOnUnverifiable: true,
|
|
136
|
+
check: (decl) => {
|
|
137
|
+
if (decl.meta.kind !== 'dead-marker') return []
|
|
138
|
+
return [{ message: 'This "# --" description is buried where neither helm-docs nor the helm-spec commented-values pass can attach it (inside another commented-out key\'s subtree, or separated from any key), so it silently never ships. Move it directly above the key it documents, use "# @doc full.path -- ...", or delete it.' }]
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'undocumented-top-level-key',
|
|
143
|
+
description: 'Top-level user-visible key with no # -- description',
|
|
144
|
+
severity: 'info',
|
|
145
|
+
runOnUnverifiable: true,
|
|
146
|
+
check: (decl) => {
|
|
147
|
+
if (decl.meta.kind !== 'key' || !decl.meta.undocumented) return []
|
|
148
|
+
return [{ message: `Top-level key "${decl.name}" has no "# --" description, so helm-docs ships it undocumented. Add a marker comment directly above it (or "# @ignored" if it is not user-facing).` }]
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
|
|
153
|
+
module.exports = {
|
|
154
|
+
name: 'helm',
|
|
155
|
+
convention: CONVENTION,
|
|
156
|
+
extract,
|
|
157
|
+
parseValuesFile,
|
|
158
|
+
rules: RULES
|
|
159
|
+
}
|