@redpanda-data/docs-extensions-and-macros 5.14.0 → 5.22.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.
Files changed (56) hide show
  1. package/bin/doc-tools-mcp.js +88 -0
  2. package/bin/doc-tools.js +334 -102
  3. package/bin/mcp-tools/index.js +20 -0
  4. package/bin/mcp-tools/lint-doc-strings.js +75 -0
  5. package/bin/mcp-tools/overrides-audit.js +100 -0
  6. package/bin/mcp-tools/preview-doc-string.js +71 -0
  7. package/cli-utils/diff-utils.js +77 -0
  8. package/cli-utils/helm-commented-values.js +172 -23
  9. package/cli-utils/validation-result.js +53 -0
  10. package/macros/REFERENCE.adoc +3 -2
  11. package/macros/prop.js +34 -6
  12. package/package.json +2 -1
  13. package/tools/generate-cli-docs.js +28 -67
  14. package/tools/lint-strings/diff.js +137 -0
  15. package/tools/lint-strings/engine.js +163 -0
  16. package/tools/lint-strings/fixtures/connect/lint_connect.go +67 -0
  17. package/tools/lint-strings/fixtures/crd/crd-ref-docs-config.yaml +15 -0
  18. package/tools/lint-strings/fixtures/crd/lint_types.go +57 -0
  19. package/tools/lint-strings/fixtures/helm/lint_values.yaml +47 -0
  20. package/tools/lint-strings/fixtures/metrics/lint_probe.cc +41 -0
  21. package/tools/lint-strings/fixtures/properties/lint_fixture.cc +51 -0
  22. package/tools/lint-strings/fixtures/properties/lint_fixture.h +33 -0
  23. package/tools/lint-strings/fixtures/rpk/lint_cmd.go +69 -0
  24. package/tools/lint-strings/go-source.js +327 -0
  25. package/tools/lint-strings/index.js +229 -0
  26. package/tools/lint-strings/rules/common.js +148 -0
  27. package/tools/lint-strings/rules/verbatim-asciidoc.js +108 -0
  28. package/tools/lint-strings/source-text.js +41 -0
  29. package/tools/lint-strings/surfaces/connect.js +233 -0
  30. package/tools/lint-strings/surfaces/crd.js +239 -0
  31. package/tools/lint-strings/surfaces/helm.js +159 -0
  32. package/tools/lint-strings/surfaces/metrics.js +368 -0
  33. package/tools/lint-strings/surfaces/properties.js +216 -0
  34. package/tools/lint-strings/surfaces/rpk.js +377 -0
  35. package/tools/overrides-audit/README.adoc +152 -0
  36. package/tools/overrides-audit/adapters/connect.js +144 -0
  37. package/tools/overrides-audit/adapters/properties.js +117 -0
  38. package/tools/overrides-audit/adapters/rpk.js +192 -0
  39. package/tools/overrides-audit/classify.js +546 -0
  40. package/tools/overrides-audit/index.js +185 -0
  41. package/tools/preview-string/index.js +245 -0
  42. package/tools/property-extractor/Makefile +9 -4
  43. package/tools/property-extractor/README.adoc +17 -3
  44. package/tools/property-extractor/helpers/index.js +1 -2
  45. package/tools/property-extractor/helpers/seeAlsoView.js +76 -0
  46. package/tools/property-extractor/parser.py +26 -0
  47. package/tools/property-extractor/property_extractor.py +65 -6
  48. package/tools/property-extractor/templates/property.hbs +25 -37
  49. package/tools/property-extractor/templates/topic-property.hbs +25 -37
  50. package/tools/property-extractor/transformers.py +15 -2
  51. package/tools/property-extractor/validate-overrides.js +120 -0
  52. package/tools/rpk-docs/detect-platform-commands.js +8 -1
  53. package/tools/rpk-docs/scan-deprecated-commands.js +15 -18
  54. package/tools/rpk-docs/validate-overrides.js +1 -45
  55. package/tools/property-extractor/helpers/allTopicsConditional.js +0 -19
  56. package/tools/property-extractor/helpers/parseRelatedTopic.js +0 -13
@@ -963,6 +963,44 @@ def clean_private_fields_from_definitions(definitions):
963
963
 
964
964
 
965
965
  # The definitions.json file contains type definitions that the extractor uses to standardize and centralize type information. After extracting and transforming the properties from the source code, the function merge_properties_and_definitions looks up each property's type in the definitions. If a property's type (or the type of its items, in the case of arrays) matches one of the definitions, the transformer replaces that type with a JSON pointer ( such as #/definitions/<type>) to the corresponding entry in definitions.json. The final JSON output then includes both a properties section (with types now referencing the definitions) and a definitions section, so that consumers of the output can easily resolve the full type information.
966
+ # Fields recorded for tooling only. lint-strings and preview-string read them
967
+ # from the raw --output to anchor a finding to its declaration; nothing on the
968
+ # docs site does. The enhanced JSON is copied verbatim into
969
+ # modules/reference/attachments and downloaded by every browser that hovers a
970
+ # prop: macro, so ~40 bytes per property (~56KB across the real ~1400-property
971
+ # file) would be paid by readers for data no reader uses.
972
+ TOOLING_ONLY_PROPERTY_FIELDS = ("line_start", "line_end")
973
+
974
+
975
+ def strip_tooling_only_fields(properties):
976
+ """
977
+ Remove tooling-only span fields from properties bound for the PUBLISHED
978
+ JSON attachment.
979
+
980
+ Only the enhanced output is published; the raw --output keeps the spans,
981
+ because that is the file lint-strings and preview-string consume.
982
+
983
+ Args:
984
+ properties: Dictionary of properties
985
+
986
+ Returns:
987
+ The same dictionary, with the tooling-only fields removed
988
+ """
989
+ removed = 0
990
+ for prop in properties.values():
991
+ if not isinstance(prop, dict):
992
+ continue
993
+ for field in TOOLING_ONLY_PROPERTY_FIELDS:
994
+ if field in prop:
995
+ del prop[field]
996
+ removed += 1
997
+ if removed:
998
+ logging.info(
999
+ f"Stripped {removed} tooling-only span field(s) from the published properties JSON"
1000
+ )
1001
+ return properties
1002
+
1003
+
966
1004
  def merge_properties_and_definitions(properties, definitions):
967
1005
  # Do not overwrite the resolved type/default with a reference. Just return the resolved properties and definitions.
968
1006
  return dict(properties=properties, definitions=definitions)
@@ -1003,7 +1041,7 @@ def apply_property_overrides(properties, overrides, overrides_file_path=None):
1003
1041
  - Otherwise, searches existing properties for an entry whose `"name"` equals the override key and applies the override if found.
1004
1042
  - If no matching property is found, creates a new property from the override and adds it under the override key.
1005
1043
 
1006
- The function supports overrides that add or replace description, version, example content, default, type, config_scope, related_topics, and other metadata. When examples reference external files, relative paths are resolved relative to overrides_file_path.
1044
+ The function supports overrides that add or replace description, version, example content, default, type, config_scope, see_also (related_topics is the deprecated predecessor, still read for existing overrides), and other metadata. When examples reference external files, relative paths are resolved relative to overrides_file_path.
1007
1045
 
1008
1046
  Parameters:
1009
1047
  properties (dict): Mapping of existing property entries (modified in-place).
@@ -1106,13 +1144,24 @@ def _apply_override_to_existing_property(property_dict, override, overrides_file
1106
1144
  if "config_scope" in override:
1107
1145
  property_dict["config_scope"] = override["config_scope"]
1108
1146
 
1109
- # Apply related_topics override
1147
+ # Apply related_topics override (deprecated, see see_also)
1110
1148
  if "related_topics" in override:
1111
1149
  if isinstance(override["related_topics"], list):
1112
1150
  property_dict["related_topics"] = override["related_topics"]
1113
1151
  else:
1114
1152
  logger.warning(f"related_topics for property must be an array")
1115
1153
 
1154
+ # Apply see_also override. Structural shape (plain string, or an object
1155
+ # naming exactly one of cloud_only/self_hosted_only) is enforced by
1156
+ # docs-data/property-overrides.schema.json via `doc-tools validate
1157
+ # property-overrides`, not here — this just passes the data through for
1158
+ # seeAlsoView.js to normalize at render time.
1159
+ if "see_also" in override:
1160
+ if isinstance(override["see_also"], list):
1161
+ property_dict["see_also"] = override["see_also"]
1162
+ else:
1163
+ logger.warning(f"see_also for property must be an array")
1164
+
1116
1165
  # Apply exclude_from_docs override
1117
1166
  if "exclude_from_docs" in override:
1118
1167
  property_dict["exclude_from_docs"] = override["exclude_from_docs"]
@@ -1154,19 +1203,26 @@ def _create_property_from_override(prop_name, override, overrides_file_path):
1154
1203
  if example_content:
1155
1204
  new_property["example"] = example_content
1156
1205
 
1157
- # Add related_topics if specified
1206
+ # Add related_topics if specified (deprecated, see see_also)
1158
1207
  if "related_topics" in override:
1159
1208
  if isinstance(override["related_topics"], list):
1160
1209
  new_property["related_topics"] = override["related_topics"]
1161
1210
  else:
1162
1211
  logger.warning(f"related_topics for property '{prop_name}' must be an array")
1163
-
1212
+
1213
+ # Add see_also if specified
1214
+ if "see_also" in override:
1215
+ if isinstance(override["see_also"], list):
1216
+ new_property["see_also"] = override["see_also"]
1217
+ else:
1218
+ logger.warning(f"see_also for property '{prop_name}' must be an array")
1219
+
1164
1220
  # Add any other custom fields from override
1165
1221
  for key, value in override.items():
1166
1222
  if key not in ["description", "type", "default", "config_scope", "version",
1167
1223
  "example", "example_file", "example_yaml", "related_topics",
1168
- "is_deprecated", "visibility", "exclude_from_docs", "category",
1169
- "accepted_values", "_comment"]:
1224
+ "see_also", "is_deprecated", "visibility", "exclude_from_docs",
1225
+ "category", "accepted_values", "_comment"]:
1170
1226
  new_property[key] = value
1171
1227
 
1172
1228
  # Add exclude_from_docs if specified
@@ -3143,6 +3199,9 @@ def main():
3143
3199
  # 7. Clean private fields from definitions (keep JSON output clean)
3144
3200
  filtered_enhanced_definitions = clean_private_fields_from_definitions(filtered_enhanced_definitions)
3145
3201
 
3202
+ # 8. Drop tooling-only spans: this JSON is the published attachment.
3203
+ enhanced_properties = strip_tooling_only_fields(enhanced_properties)
3204
+
3146
3205
  # Generate enhanced properties JSON (with overrides)
3147
3206
  enhanced_properties_and_definitions = merge_properties_and_definitions(
3148
3207
  enhanced_properties, filtered_enhanced_definitions
@@ -167,78 +167,66 @@ endif::[]
167
167
  |
168
168
  {{{example}}}
169
169
  {{/if}}
170
- {{#if related_topics}}
171
- {{#with (allTopicsConditional related_topics) as |sectionType|}}
170
+ {{#with (seeAlsoView .) as |seeAlso|}}
171
+ {{#if seeAlso.items.length}}
172
172
 
173
- {{#if (eq sectionType "cloud")}}
173
+ {{#if (eq seeAlso.sectionType "cloud")}}
174
174
  ifdef::env-cloud[]
175
175
  | Related topics
176
- |{{#if (gt (length ../related_topics) 1)}}{{#each ../related_topics}}
177
- {{#with (parseRelatedTopic this)}}
178
- * {{{value}}}
179
- {{/with}}
176
+ |{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
177
+ * {{{content}}}
180
178
  {{/each}}
181
179
  {{else}}
182
- {{#each ../related_topics}}
183
- {{#with (parseRelatedTopic this)}}
184
- {{{value}}}
185
- {{/with}}
180
+ {{#each seeAlso.items}}
181
+ {{{content}}}
186
182
  {{/each}}
187
183
  {{/if}}
188
184
  endif::[]
189
- {{else if (eq sectionType "self-managed")}}
185
+ {{else if (eq seeAlso.sectionType "self-managed")}}
190
186
  ifndef::env-cloud[]
191
187
  | Related topics
192
- |{{#if (gt (length ../related_topics) 1)}}{{#each ../related_topics}}
193
- {{#with (parseRelatedTopic this)}}
194
- * {{{value}}}
195
- {{/with}}
188
+ |{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
189
+ * {{{content}}}
196
190
  {{/each}}
197
191
  {{else}}
198
- {{#each ../related_topics}}
199
- {{#with (parseRelatedTopic this)}}
200
- {{{value}}}
201
- {{/with}}
192
+ {{#each seeAlso.items}}
193
+ {{{content}}}
202
194
  {{/each}}
203
195
  {{/if}}
204
196
  endif::[]
205
197
  {{else}}
206
198
  | Related topics
207
- |{{#if (gt (length ../related_topics) 1)}}{{#each ../related_topics}}
208
- {{#with (parseRelatedTopic this)}}
209
- {{#if (eq type "cloud")}}
199
+ |{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
200
+ {{#if cloudOnly}}
210
201
  ifdef::env-cloud[]
211
- * {{{value}}}
202
+ * {{{content}}}
212
203
  endif::[]
213
- {{else if (eq type "self-managed")}}
204
+ {{else if selfHostedOnly}}
214
205
  ifndef::env-cloud[]
215
- * {{{value}}}
206
+ * {{{content}}}
216
207
  endif::[]
217
208
  {{else}}
218
- * {{{value}}}
209
+ * {{{content}}}
219
210
  {{/if}}
220
- {{/with}}
221
211
  {{/each}}
222
212
  {{else}}
223
- {{#each ../related_topics}}
224
- {{#with (parseRelatedTopic this)}}
225
- {{#if (eq type "cloud")}}
213
+ {{#each seeAlso.items}}
214
+ {{#if cloudOnly}}
226
215
  ifdef::env-cloud[]
227
- {{{value}}}
216
+ {{{content}}}
228
217
  endif::[]
229
- {{else if (eq type "self-managed")}}
218
+ {{else if selfHostedOnly}}
230
219
  ifndef::env-cloud[]
231
- {{{value}}}
220
+ {{{content}}}
232
221
  endif::[]
233
222
  {{else}}
234
- {{{value}}}
223
+ {{{content}}}
235
224
  {{/if}}
236
- {{/with}}
237
225
  {{/each}}
238
226
  {{/if}}
239
227
  {{/if}}
240
- {{/with}}
241
228
  {{/if}}
229
+ {{/with}}
242
230
 
243
231
  |===
244
232
 
@@ -130,78 +130,66 @@ endif::[]
130
130
  |
131
131
  {{{example}}}
132
132
  {{/if}}
133
- {{#if related_topics}}
134
- {{#with (allTopicsConditional related_topics) as |sectionType|}}
133
+ {{#with (seeAlsoView .) as |seeAlso|}}
134
+ {{#if seeAlso.items.length}}
135
135
 
136
- {{#if (eq sectionType "cloud")}}
136
+ {{#if (eq seeAlso.sectionType "cloud")}}
137
137
  ifdef::env-cloud[]
138
138
  | Related topics
139
- |{{#if (gt (length ../related_topics) 1)}}{{#each ../related_topics}}
140
- {{#with (parseRelatedTopic this)}}
141
- * {{{value}}}
142
- {{/with}}
139
+ |{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
140
+ * {{{content}}}
143
141
  {{/each}}
144
142
  {{else}}
145
- {{#each ../related_topics}}
146
- {{#with (parseRelatedTopic this)}}
147
- {{{value}}}
148
- {{/with}}
143
+ {{#each seeAlso.items}}
144
+ {{{content}}}
149
145
  {{/each}}
150
146
  {{/if}}
151
147
  endif::[]
152
- {{else if (eq sectionType "self-managed")}}
148
+ {{else if (eq seeAlso.sectionType "self-managed")}}
153
149
  ifndef::env-cloud[]
154
150
  | Related topics
155
- |{{#if (gt (length ../related_topics) 1)}}{{#each ../related_topics}}
156
- {{#with (parseRelatedTopic this)}}
157
- * {{{value}}}
158
- {{/with}}
151
+ |{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
152
+ * {{{content}}}
159
153
  {{/each}}
160
154
  {{else}}
161
- {{#each ../related_topics}}
162
- {{#with (parseRelatedTopic this)}}
163
- {{{value}}}
164
- {{/with}}
155
+ {{#each seeAlso.items}}
156
+ {{{content}}}
165
157
  {{/each}}
166
158
  {{/if}}
167
159
  endif::[]
168
160
  {{else}}
169
161
  | Related topics
170
- |{{#if (gt (length ../related_topics) 1)}}{{#each ../related_topics}}
171
- {{#with (parseRelatedTopic this)}}
172
- {{#if (eq type "cloud")}}
162
+ |{{#if (gt seeAlso.items.length 1)}}{{#each seeAlso.items}}
163
+ {{#if cloudOnly}}
173
164
  ifdef::env-cloud[]
174
- * {{{value}}}
165
+ * {{{content}}}
175
166
  endif::[]
176
- {{else if (eq type "self-managed")}}
167
+ {{else if selfHostedOnly}}
177
168
  ifndef::env-cloud[]
178
- * {{{value}}}
169
+ * {{{content}}}
179
170
  endif::[]
180
171
  {{else}}
181
- * {{{value}}}
172
+ * {{{content}}}
182
173
  {{/if}}
183
- {{/with}}
184
174
  {{/each}}
185
175
  {{else}}
186
- {{#each ../related_topics}}
187
- {{#with (parseRelatedTopic this)}}
188
- {{#if (eq type "cloud")}}
176
+ {{#each seeAlso.items}}
177
+ {{#if cloudOnly}}
189
178
  ifdef::env-cloud[]
190
- {{{value}}}
179
+ {{{content}}}
191
180
  endif::[]
192
- {{else if (eq type "self-managed")}}
181
+ {{else if selfHostedOnly}}
193
182
  ifndef::env-cloud[]
194
- {{{value}}}
183
+ {{{content}}}
195
184
  endif::[]
196
185
  {{else}}
197
- {{{value}}}
186
+ {{{content}}}
198
187
  {{/if}}
199
- {{/with}}
200
188
  {{/each}}
201
189
  {{/if}}
202
190
  {{/if}}
203
- {{/with}}
204
191
  {{/if}}
192
+ {{/with}}
205
193
  {{#if aliases}}
206
194
 
207
195
  ifndef::env-cloud[]
@@ -655,8 +655,10 @@ class BasicInfoTransformer:
655
655
  EXPECTED FINAL RESULT:
656
656
  {
657
657
  "name": "property_name", # Clean identifier for JSON keys
658
- "description": "Human readable...", # Description for documentation
659
- "defined_in": "src/v/config/..." # Normalized file path
658
+ "description": "Human readable...", # Description for documentation
659
+ "defined_in": "src/v/config/...", # Normalized file path
660
+ "line_start": 1852, # First line of the declaration (1-indexed)
661
+ "line_end": 1858 # Last line of the declaration (inclusive)
660
662
  }
661
663
 
662
664
  DOWNSTREAM USAGE:
@@ -724,6 +726,17 @@ class BasicInfoTransformer:
724
726
  property["defined_in"] = re.sub(
725
727
  r"^.*src/", "src/", str(file_pair.implementation)
726
728
  )
729
+
730
+ # --- Step 4: line span (additive) ---
731
+ # 1-indexed, inclusive lines of the full member-initializer entry in
732
+ # the implementation file named by defined_in. Recorded by the parser
733
+ # (parse_cpp_source) from the tree-sitter field_initializer node.
734
+ # Spans the whole declaration, not just the description literal, so
735
+ # diff-anchored tooling can match clang-format-wrapped strings.
736
+ if info.get("line_start") is not None:
737
+ property["line_start"] = info["line_start"]
738
+ property["line_end"] = info.get("line_end", info["line_start"])
739
+
727
740
  return property
728
741
 
729
742
  @debug_transformer
@@ -0,0 +1,120 @@
1
+ 'use strict'
2
+
3
+ const Ajv2020 = require('ajv/dist/2020')
4
+ const fs = require('fs')
5
+ const path = require('path')
6
+ const { ValidationResult } = require('../../cli-utils/validation-result')
7
+
8
+ /**
9
+ * Load and compile the JSON Schema for validation
10
+ * @param {string} [overridesPath] - Path to the overrides file; the schema is
11
+ * looked up as a sibling (same directory) before falling back to the
12
+ * package-relative path.
13
+ * @returns {Function} Compiled schema validator
14
+ */
15
+ function loadSchemaValidator(overridesPath) {
16
+ // Primary: look for the schema next to the overrides file (docs repo's docs-data/).
17
+ if (overridesPath) {
18
+ const siblingPath = path.join(path.dirname(path.resolve(overridesPath)), 'property-overrides.schema.json')
19
+ if (fs.existsSync(siblingPath)) {
20
+ const schema = JSON.parse(fs.readFileSync(siblingPath, 'utf8'))
21
+ const ajv = new Ajv2020({ allErrors: true, verbose: true })
22
+ return ajv.compile(schema)
23
+ }
24
+ }
25
+
26
+ // Fallback: package-relative path (legacy / monorepo usage).
27
+ const schemaPath = path.resolve(__dirname, '../../docs-data/property-overrides.schema.json')
28
+
29
+ if (!fs.existsSync(schemaPath)) {
30
+ throw new Error(`Schema file not found: ${schemaPath}`)
31
+ }
32
+
33
+ const schema = JSON.parse(fs.readFileSync(schemaPath, 'utf8'))
34
+ const ajv = new Ajv2020({ allErrors: true, verbose: true })
35
+ return ajv.compile(schema)
36
+ }
37
+
38
+ /**
39
+ * Validate overrides against the JSON Schema
40
+ * @param {Object} overrides - Overrides object to validate
41
+ * @param {string} [overridesPath] - Path to the overrides file (used to locate the schema)
42
+ * @returns {ValidationResult}
43
+ */
44
+ function validateSchema(overrides, overridesPath) {
45
+ const result = new ValidationResult()
46
+
47
+ try {
48
+ const validate = loadSchemaValidator(overridesPath)
49
+ const valid = validate(overrides)
50
+
51
+ if (!valid) {
52
+ for (const error of validate.errors || []) {
53
+ const instancePath = error.instancePath || '/'
54
+ result.addError(error.message, instancePath)
55
+ }
56
+ }
57
+ } catch (err) {
58
+ result.addError(`Schema validation failed: ${err.message}`)
59
+ }
60
+
61
+ return result
62
+ }
63
+
64
+ /**
65
+ * Run all validations on a property-overrides object.
66
+ * @param {Object} overrides - Overrides object to validate
67
+ * @param {string} [overridesPath] - Path to the overrides file (used to locate the schema)
68
+ * @returns {ValidationResult}
69
+ */
70
+ function validateOverrides(overrides, overridesPath = null) {
71
+ const result = new ValidationResult()
72
+
73
+ if (!overrides || typeof overrides !== 'object') {
74
+ result.addError('Overrides must be a non-null object')
75
+ return result
76
+ }
77
+
78
+ result.merge(validateSchema(overrides, overridesPath))
79
+
80
+ return result
81
+ }
82
+
83
+ /**
84
+ * Load and validate a property-overrides.json file.
85
+ * @param {string} overridesPath - Path to overrides JSON file
86
+ * @returns {{ overrides: Object|null, validation: ValidationResult }}
87
+ */
88
+ function loadAndValidateOverrides(overridesPath) {
89
+ const validation = new ValidationResult()
90
+
91
+ if (!overridesPath) {
92
+ return { overrides: null, validation }
93
+ }
94
+
95
+ if (!fs.existsSync(overridesPath)) {
96
+ validation.addWarning(`Overrides file not found: ${overridesPath}`)
97
+ return { overrides: null, validation }
98
+ }
99
+
100
+ let overrides
101
+ try {
102
+ const content = fs.readFileSync(overridesPath, 'utf8')
103
+ overrides = JSON.parse(content)
104
+ } catch (err) {
105
+ validation.addError(`Failed to parse overrides JSON: ${err.message}`, overridesPath)
106
+ return { overrides: null, validation }
107
+ }
108
+
109
+ validation.merge(validateOverrides(overrides, overridesPath))
110
+
111
+ return { overrides, validation }
112
+ }
113
+
114
+ module.exports = {
115
+ ValidationResult,
116
+ loadSchemaValidator,
117
+ validateSchema,
118
+ validateOverrides,
119
+ loadAndValidateOverrides,
120
+ }
@@ -47,6 +47,7 @@
47
47
 
48
48
  const fs = require('fs')
49
49
  const path = require('path')
50
+ const { maskComments } = require('../lint-strings/go-source')
50
51
 
51
52
  /** GOOS values recognized in filename suffixes and build tags */
52
53
  const KNOWN_GOOS = new Set([
@@ -248,7 +249,13 @@ function commandNameFromConstructor(funcName) {
248
249
  * @param {string} content - Go file content
249
250
  * @returns {Object<string, {useName: string|null, excluded: boolean}>}
250
251
  */
251
- function parseConstructors(content) {
252
+ function parseConstructors(rawContent) {
253
+ // Mask comments first. Scanning raw text read a `Use: "..."` out of a
254
+ // commented-out or example command and reported it as the real one; the
255
+ // masker blanks comment bodies while preserving offsets, line breaks and
256
+ // every string literal, so the regexes below see only live code. Shared with
257
+ // the lint-strings Go surfaces rather than reimplemented here.
258
+ const content = maskComments(rawContent)
252
259
  const constructors = {}
253
260
  const funcRe = /^func\s+(\w+)\s*\([^)]*\)\s*\*cobra\.Command\s*\{/gm
254
261
  const matches = []
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
+ const { maskComments, findBalancedClose } = require('../lint-strings/go-source');
10
11
 
11
12
  /**
12
13
  * Scan a Go file for cobra command definitions with deprecation/hidden status
@@ -17,31 +18,27 @@ function scanGoFile(filePath) {
17
18
  const content = fs.readFileSync(filePath, 'utf8');
18
19
 
19
20
  // Look for NewCommand function that returns *cobra.Command
20
- const newCommandMatch = content.match(/func NewCommand\([^)]*\)\s*\*cobra\.Command\s*\{/);
21
+ // Mask comments before any scanning. The hand-rolled brace counter this
22
+ // replaced was comment- and string-blind, so a `{` inside a comment
23
+ // unbalanced it and truncated the cobra.Command block early, and the field
24
+ // regexes then read a `Use:` string out of a commented-out example. Both
25
+ // helpers are string-aware and already shared by the lint-strings Go
26
+ // surfaces; the masker preserves offsets and every string literal.
27
+ const masked = maskComments(content);
28
+
29
+ const newCommandMatch = masked.match(/func NewCommand\([^)]*\)\s*\*cobra\.Command\s*\{/);
21
30
  if (!newCommandMatch) {
22
31
  return null;
23
32
  }
24
33
 
25
34
  // Extract the cobra.Command struct definition
26
- // Handle nested braces and multiline strings
27
- let cmdBlock = '';
28
- let braceCount = 0;
29
- let startIndex = content.indexOf('&cobra.Command{');
30
-
31
- if (startIndex === -1) {
35
+ const structIndex = masked.indexOf('&cobra.Command{');
36
+ if (structIndex === -1) {
32
37
  return null;
33
38
  }
34
-
35
- startIndex += '&cobra.Command{'.length;
36
- for (let i = startIndex; i < content.length; i++) {
37
- const char = content[i];
38
- if (char === '{') braceCount++;
39
- if (char === '}') {
40
- if (braceCount === 0) break;
41
- braceCount--;
42
- }
43
- cmdBlock += char;
44
- }
39
+ const openIndex = structIndex + '&cobra.Command'.length;
40
+ const closeIndex = findBalancedClose(masked, openIndex, '{', '}');
41
+ const cmdBlock = masked.slice(openIndex + 1, closeIndex === -1 ? masked.length : closeIndex);
45
42
 
46
43
  // Extract fields - handle multiline strings with backticks or quotes
47
44
  const useMatch = cmdBlock.match(/Use:\s*"([^"]+)"/);
@@ -3,6 +3,7 @@
3
3
  const Ajv2020 = require('ajv/dist/2020')
4
4
  const fs = require('fs')
5
5
  const path = require('path')
6
+ const { ValidationResult } = require('../../cli-utils/validation-result')
6
7
 
7
8
  /**
8
9
  * Valid admonition locations for notes, warnings, tips, cautions, importants
@@ -50,51 +51,6 @@ const VALID_SECTION_POSITIONS = [
50
51
  'end'
51
52
  ]
52
53
 
53
- /**
54
- * Validation result object
55
- */
56
- class ValidationResult {
57
- constructor() {
58
- this.valid = true
59
- this.errors = []
60
- this.warnings = []
61
- }
62
-
63
- addError(message, context = null) {
64
- this.valid = false
65
- this.errors.push({ message, context })
66
- }
67
-
68
- addWarning(message, context = null) {
69
- this.warnings.push({ message, context })
70
- }
71
-
72
- merge(other) {
73
- if (!other.valid) this.valid = false
74
- this.errors.push(...other.errors)
75
- this.warnings.push(...other.warnings)
76
- }
77
-
78
- format() {
79
- const lines = []
80
- if (this.errors.length > 0) {
81
- lines.push('ERRORS:')
82
- for (const err of this.errors) {
83
- lines.push(` ✗ ${err.message}`)
84
- if (err.context) lines.push(` at: ${err.context}`)
85
- }
86
- }
87
- if (this.warnings.length > 0) {
88
- lines.push('WARNINGS:')
89
- for (const warn of this.warnings) {
90
- lines.push(` ⚠ ${warn.message}`)
91
- if (warn.context) lines.push(` at: ${warn.context}`)
92
- }
93
- }
94
- return lines.join('\n')
95
- }
96
- }
97
-
98
54
  /**
99
55
  * Normalize a command path (trim whitespace, collapse multiple spaces)
100
56
  * @param {string} cmdPath - Command path to normalize
@@ -1,19 +0,0 @@
1
- // Returns 'cloud' if all topics are cloud-only, 'self-managed' if all are self-managed-only, else 'normal'
2
- module.exports = function allTopicsConditional(related_topics) {
3
- if (!Array.isArray(related_topics) || related_topics.length === 0) return null;
4
- let allCloud = true;
5
- let allSelfManaged = true;
6
- for (const t of related_topics) {
7
- if (typeof t !== 'string') {
8
- allCloud = false;
9
- allSelfManaged = false;
10
- break;
11
- }
12
- const trimmed = t.trim();
13
- if (!trimmed.startsWith('cloud-only:')) allCloud = false;
14
- if (!trimmed.startsWith('self-managed-only:')) allSelfManaged = false;
15
- }
16
- if (allCloud) return 'cloud';
17
- if (allSelfManaged) return 'self-managed';
18
- return 'normal';
19
- };
@@ -1,13 +0,0 @@
1
- // Returns an object with type and value for a related topic
2
- // type: 'cloud', 'self-managed', or 'normal'
3
- module.exports = function parseRelatedTopic(topic) {
4
- if (typeof topic !== 'string') return { type: 'normal', value: topic };
5
- const trimmed = topic.trim();
6
- if (trimmed.startsWith('cloud-only:')) {
7
- return { type: 'cloud', value: trimmed.replace(/^cloud-only:/, '').trim() };
8
- }
9
- if (trimmed.startsWith('self-managed-only:')) {
10
- return { type: 'self-managed', value: trimmed.replace(/^self-managed-only:/, '').trim() };
11
- }
12
- return { type: 'normal', value: trimmed };
13
- };