code-auditor-mcp 3.4.12 → 3.4.13

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 (245) hide show
  1. package/CHANGELOG.md +46 -1
  2. package/dist/analyzers/__verify_self_probe.d.ts +2 -0
  3. package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
  4. package/dist/analyzers/__verify_self_probe.js +5 -0
  5. package/dist/analyzers/__verify_self_probe.js.map +1 -0
  6. package/dist/analyzers/analyzerUtils.d.ts +34 -1
  7. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  8. package/dist/analyzers/analyzerUtils.js +61 -22
  9. package/dist/analyzers/analyzerUtils.js.map +1 -1
  10. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
  11. package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
  12. package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
  13. package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
  14. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
  15. package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
  16. package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
  17. package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
  18. package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
  19. package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
  20. package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
  21. package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
  22. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
  23. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  24. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
  25. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  26. package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
  27. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  28. package/dist/analyzers/documentationAnalyzer.js +259 -198
  29. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  30. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
  31. package/dist/analyzers/invariantsAnalyzer.js +57 -50
  32. package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
  33. package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
  34. package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
  35. package/dist/analyzers/orm/adapterRegistry.js +10 -0
  36. package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
  37. package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
  38. package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
  39. package/dist/analyzers/orm/drizzleAdapter.js +42 -44
  40. package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
  41. package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
  42. package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
  43. package/dist/analyzers/orm/prismaAdapter.js +30 -0
  44. package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
  45. package/dist/analyzers/provenance.d.ts +62 -2
  46. package/dist/analyzers/provenance.d.ts.map +1 -1
  47. package/dist/analyzers/provenance.js +443 -349
  48. package/dist/analyzers/provenance.js.map +1 -1
  49. package/dist/analyzers/reactAnalyzer.d.ts +11 -0
  50. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  51. package/dist/analyzers/reactAnalyzer.js +188 -125
  52. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  53. package/dist/analyzers/ruleRegistry.d.ts +110 -1
  54. package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
  55. package/dist/analyzers/ruleRegistry.js +1714 -124
  56. package/dist/analyzers/ruleRegistry.js.map +1 -1
  57. package/dist/analyzers/ruleTiming.d.ts +54 -0
  58. package/dist/analyzers/ruleTiming.d.ts.map +1 -0
  59. package/dist/analyzers/ruleTiming.js +71 -0
  60. package/dist/analyzers/ruleTiming.js.map +1 -0
  61. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
  62. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  63. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
  64. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  65. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
  66. package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
  67. package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
  68. package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
  69. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +17 -202
  70. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  71. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1073 -1121
  72. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  73. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
  74. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
  75. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
  76. package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
  77. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
  78. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
  79. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
  80. package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
  81. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -214
  82. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  83. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1521
  84. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  85. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
  86. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  87. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -480
  88. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  89. package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
  90. package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
  91. package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
  92. package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
  93. package/dist/analyzers/universal/schema/config.d.ts +31 -0
  94. package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
  95. package/dist/analyzers/universal/schema/config.js +57 -0
  96. package/dist/analyzers/universal/schema/config.js.map +1 -0
  97. package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
  98. package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
  99. package/dist/analyzers/universal/schema/discovery.js +616 -0
  100. package/dist/analyzers/universal/schema/discovery.js.map +1 -0
  101. package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
  102. package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
  103. package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
  104. package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
  105. package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
  106. package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
  107. package/dist/analyzers/universal/schema/migrations.js +238 -0
  108. package/dist/analyzers/universal/schema/migrations.js.map +1 -0
  109. package/dist/analyzers/universal/schema/types.d.ts +131 -0
  110. package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
  111. package/dist/analyzers/universal/schema/types.js +10 -0
  112. package/dist/analyzers/universal/schema/types.js.map +1 -0
  113. package/dist/analyzers/universal/schema/violations.d.ts +44 -0
  114. package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
  115. package/dist/analyzers/universal/schema/violations.js +54 -0
  116. package/dist/analyzers/universal/schema/violations.js.map +1 -0
  117. package/dist/auditRunner.d.ts.map +1 -1
  118. package/dist/auditRunner.js +89 -13
  119. package/dist/auditRunner.js.map +1 -1
  120. package/dist/cli.js +315 -40
  121. package/dist/cli.js.map +1 -1
  122. package/dist/config/configLoader.d.ts.map +1 -1
  123. package/dist/config/configLoader.js +9 -7
  124. package/dist/config/configLoader.js.map +1 -1
  125. package/dist/config/defaults.d.ts +12 -3
  126. package/dist/config/defaults.d.ts.map +1 -1
  127. package/dist/config/defaults.js +15 -5
  128. package/dist/config/defaults.js.map +1 -1
  129. package/dist/config/effectiveConfig.d.ts +75 -0
  130. package/dist/config/effectiveConfig.d.ts.map +1 -0
  131. package/dist/config/effectiveConfig.js +213 -0
  132. package/dist/config/effectiveConfig.js.map +1 -0
  133. package/dist/config/pathProfiles.d.ts +7 -4
  134. package/dist/config/pathProfiles.d.ts.map +1 -1
  135. package/dist/config/pathProfiles.js +8 -10
  136. package/dist/config/pathProfiles.js.map +1 -1
  137. package/dist/config/thresholdRationales.d.ts +45 -0
  138. package/dist/config/thresholdRationales.d.ts.map +1 -0
  139. package/dist/config/thresholdRationales.js +67 -0
  140. package/dist/config/thresholdRationales.js.map +1 -0
  141. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  142. package/dist/conventions/conventionMiner.js +6 -2
  143. package/dist/conventions/conventionMiner.js.map +1 -1
  144. package/dist/enforcement/diffGate.d.ts +55 -0
  145. package/dist/enforcement/diffGate.d.ts.map +1 -0
  146. package/dist/enforcement/diffGate.js +136 -0
  147. package/dist/enforcement/diffGate.js.map +1 -0
  148. package/dist/enforcement/gate.d.ts +36 -0
  149. package/dist/enforcement/gate.d.ts.map +1 -0
  150. package/dist/enforcement/gate.js +72 -0
  151. package/dist/enforcement/gate.js.map +1 -0
  152. package/dist/enforcement/suppressions.d.ts +41 -0
  153. package/dist/enforcement/suppressions.d.ts.map +1 -0
  154. package/dist/enforcement/suppressions.js +170 -0
  155. package/dist/enforcement/suppressions.js.map +1 -0
  156. package/dist/fingerprint.d.ts.map +1 -1
  157. package/dist/fingerprint.js +4 -1
  158. package/dist/fingerprint.js.map +1 -1
  159. package/dist/invariants/ruleEngine.d.ts +3 -4
  160. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  161. package/dist/invariants/ruleEngine.js +2 -2
  162. package/dist/invariants/ruleEngine.js.map +1 -1
  163. package/dist/languages/LanguageOrchestrator.d.ts +14 -0
  164. package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
  165. package/dist/languages/LanguageOrchestrator.js +93 -55
  166. package/dist/languages/LanguageOrchestrator.js.map +1 -1
  167. package/dist/languages/LanguageRegistry.d.ts +8 -0
  168. package/dist/languages/LanguageRegistry.d.ts.map +1 -1
  169. package/dist/languages/LanguageRegistry.js +8 -0
  170. package/dist/languages/LanguageRegistry.js.map +1 -1
  171. package/dist/languages/RuntimeManager.d.ts +82 -60
  172. package/dist/languages/RuntimeManager.d.ts.map +1 -1
  173. package/dist/languages/RuntimeManager.js +404 -349
  174. package/dist/languages/RuntimeManager.js.map +1 -1
  175. package/dist/languages/UniversalAnalyzer.d.ts +51 -6
  176. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  177. package/dist/languages/UniversalAnalyzer.js +127 -88
  178. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  179. package/dist/languages/adapterBridge.d.ts +36 -0
  180. package/dist/languages/adapterBridge.d.ts.map +1 -1
  181. package/dist/languages/adapterBridge.js +89 -35
  182. package/dist/languages/adapterBridge.js.map +1 -1
  183. package/dist/languages/go/GoAdapter.d.ts +66 -23
  184. package/dist/languages/go/GoAdapter.d.ts.map +1 -1
  185. package/dist/languages/go/GoAdapter.js +553 -474
  186. package/dist/languages/go/GoAdapter.js.map +1 -1
  187. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +33 -4
  188. package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
  189. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +101 -66
  190. package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
  191. package/dist/languages/tree-sitter/converter.d.ts +18 -0
  192. package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
  193. package/dist/languages/tree-sitter/converter.js +18 -0
  194. package/dist/languages/tree-sitter/converter.js.map +1 -1
  195. package/dist/languages/tree-sitter/parser.d.ts +39 -2
  196. package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
  197. package/dist/languages/tree-sitter/parser.js +93 -12
  198. package/dist/languages/tree-sitter/parser.js.map +1 -1
  199. package/dist/languages/types.d.ts +67 -2
  200. package/dist/languages/types.d.ts.map +1 -1
  201. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -72
  202. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  203. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1419 -923
  204. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  205. package/dist/mcp-tools-shared.d.ts.map +1 -1
  206. package/dist/mcp-tools-shared.js.map +1 -1
  207. package/dist/pipeline.d.ts +13 -5
  208. package/dist/pipeline.d.ts.map +1 -1
  209. package/dist/pipeline.js +381 -200
  210. package/dist/pipeline.js.map +1 -1
  211. package/dist/pipelineAdapters.d.ts +3 -8
  212. package/dist/pipelineAdapters.d.ts.map +1 -1
  213. package/dist/pipelineAdapters.js +149 -94
  214. package/dist/pipelineAdapters.js.map +1 -1
  215. package/dist/presets/presets.d.ts +54 -0
  216. package/dist/presets/presets.d.ts.map +1 -0
  217. package/dist/presets/presets.js +181 -0
  218. package/dist/presets/presets.js.map +1 -0
  219. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  220. package/dist/reporting/sarifReportGenerator.js +0 -2
  221. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  222. package/dist/ruleAliases.d.ts +67 -0
  223. package/dist/ruleAliases.d.ts.map +1 -0
  224. package/dist/ruleAliases.js +79 -0
  225. package/dist/ruleAliases.js.map +1 -0
  226. package/dist/styles/cssAstExtractor.d.ts.map +1 -1
  227. package/dist/styles/cssAstExtractor.js +78 -18
  228. package/dist/styles/cssAstExtractor.js.map +1 -1
  229. package/dist/styles/styleIndexer.js +10 -1
  230. package/dist/styles/styleIndexer.js.map +1 -1
  231. package/dist/styles/tailwindConfigLoader.d.ts +8 -6
  232. package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
  233. package/dist/styles/tailwindConfigLoader.js +61 -15
  234. package/dist/styles/tailwindConfigLoader.js.map +1 -1
  235. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  236. package/dist/styles/tailwindUtilityExpander.js +13 -1
  237. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  238. package/dist/types.d.ts +181 -1
  239. package/dist/types.d.ts.map +1 -1
  240. package/dist/types.js +8 -0
  241. package/dist/types.js.map +1 -1
  242. package/package.json +5 -3
  243. package/plugin/hooks/hooks.json +1 -1
  244. package/plugin/scripts/hook-audit.sh +25 -7
  245. package/plugin/skills/code-auditor/SKILL.md +5 -5
@@ -55,12 +55,20 @@ const HEADER_SKIP_GLOBS_DEFAULT = [
55
55
  '**/*.config.*',
56
56
  '**/*.d.ts',
57
57
  ];
58
+ /**
59
+ * Languages whose documentation convention uses JSDoc @param/@returns tags.
60
+ * Other languages (e.g. Go) document parameters and return values in prose
61
+ * godoc comments, so the tag-based checks do not apply to them.
62
+ */
63
+ const JSDOC_LANGUAGES = new Set(['typescript', 'javascript']);
64
+ /**
65
+ * Universal documentation analyzer.
66
+ */
58
67
  export class UniversalDocumentationAnalyzer extends UniversalAnalyzer {
59
68
  name = 'documentation';
60
69
  description = 'Analyzes documentation quality across the codebase';
61
70
  category = 'documentation';
62
71
  async analyzeAST(ast, adapter, config, sourceCode) {
63
- const violations = [];
64
72
  const finalConfig = { ...DEFAULT_DOCUMENTATION_CONFIG, ...config };
65
73
  // Resolve scope: "public" is post-spec-17 default
66
74
  const scope = finalConfig.scope ?? 'public';
@@ -68,392 +76,458 @@ export class UniversalDocumentationAnalyzer extends UniversalAnalyzer {
68
76
  const fileHeaders = finalConfig.fileHeaders ?? finalConfig.requireFileDocs ?? false;
69
77
  // Check if file is exempt (name-based patterns)
70
78
  if (this.isExempt(ast.filePath, finalConfig.exemptPatterns)) {
71
- return violations;
79
+ return [];
72
80
  }
81
+ const violations = [];
73
82
  // File-level documentation header check — R1.5 (defaults OFF)
74
- if (fileHeaders) {
75
- const skipGlobs = finalConfig.headerSkipGlobs ?? HEADER_SKIP_GLOBS_DEFAULT;
76
- if (!this.matchesAnyGlob(ast.filePath, skipGlobs)) {
77
- const fileDoc = this.getFileDocumentation(ast, adapter);
78
- if (!fileDoc || fileDoc.length < finalConfig.minDescriptionLength) {
79
- violations.push(this.createViolation(ast.filePath, { line: 1, column: 1 }, 'File lacks proper documentation header', 'suggestion', 'file-documentation'));
80
- }
81
- }
82
- }
83
+ violations.push(...checkFileHeader(ast, adapter, finalConfig, fileHeaders));
84
+ // Per-file scan context for the section analyzers below (Spec 34 bundling).
85
+ const scan = { adapter, sourceCode, config: finalConfig, scope };
83
86
  // Function documentation — R1.1 through R1.4, R1.6
84
87
  if (finalConfig.requireFunctionDocs) {
85
- const functions = adapter.extractFunctions(ast);
86
- const docsMinLines = finalConfig.docsMinLines ?? 5;
87
- for (const func of functions) {
88
- // Find the AST node for this function
89
- const node = this.findNodeByLocation(ast.root, func.location.start);
90
- // R1.1 — Skip anonymous/inline callables
91
- if (node && this.isAnonymousOrCallback(node, adapter)) {
92
- continue;
93
- }
94
- // R1.2 — Scope filter (public API surface only)
95
- if (scope === 'public') {
96
- if (!func.isExported) {
97
- // If not exported and not a public method of exported class, skip
98
- if (!func.isMethod) {
99
- // Named function decl nested inside another function — skip at default scope
100
- if (node && this.isNestedFunction(node, adapter)) {
101
- continue;
102
- }
103
- // Non-exported top-level function — skip
104
- continue;
105
- }
106
- // It's a method — check visibility
107
- if (node && this.isNonPublicMethod(node, adapter, sourceCode)) {
108
- continue;
109
- }
110
- // Check if the enclosing class is exported
111
- if (node && !this.isMethodOfExportedClass(node, adapter, ast)) {
112
- continue;
113
- }
114
- }
115
- }
116
- // R1.3 — Minimum-size gate
117
- const bodyLines = (func.location.end.line - func.location.start.line) + 1;
118
- if (bodyLines < docsMinLines) {
119
- continue;
120
- }
121
- const doc = func.jsDoc || '';
122
- // R1.6 — Audience-reason message
123
- if (!doc || doc.length < finalConfig.minDescriptionLength) {
124
- const reason = func.isExported
125
- ? `exported function '${func.name}' lacks proper documentation`
126
- : func.isMethod && func.className
127
- ? `public method '${func.className}.${func.name}' lacks proper documentation`
128
- : `function '${func.name}' lacks proper documentation`;
129
- violations.push(this.createViolation(ast.filePath, func.location.start, reason, 'suggestion', 'function-documentation', undefined, func.name));
130
- }
131
- else {
132
- // Param docs
133
- if (finalConfig.requireParamDocs && func.parameters.length > 0) {
134
- const missingParamDocs = this.checkParameterDocumentation(doc, func.parameters.map(p => p.name));
135
- for (const param of missingParamDocs) {
136
- violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' missing documentation for parameter '${param}'`, 'suggestion', 'parameter-documentation', undefined, func.name));
137
- }
138
- }
139
- // Return docs
140
- if (finalConfig.requireReturnDocs &&
141
- func.returnType &&
142
- func.returnType !== 'void' &&
143
- !this.hasReturnDocumentation(doc)) {
144
- violations.push(this.createViolation(ast.filePath, func.location.start, `Function '${func.name}' missing return value documentation`, 'suggestion', 'return-documentation', undefined, func.name));
145
- }
146
- }
147
- }
88
+ violations.push(...analyzeFunctionDocumentation(ast, scan));
148
89
  }
149
90
  // Class documentation
150
91
  if (finalConfig.requireClassDocs) {
151
- const classes = adapter.extractClasses(ast);
152
- for (const cls of classes) {
153
- // Scope filter for classes
154
- if (scope === 'public' && !cls.isExported) {
155
- continue;
156
- }
157
- const doc = cls.jsDoc || '';
158
- if (!doc || doc.length < finalConfig.minDescriptionLength) {
159
- violations.push(this.createViolation(ast.filePath, cls.location.start, `Class '${cls.name}' lacks proper documentation`, 'suggestion', 'class-documentation', undefined, cls.name));
160
- }
161
- // Method documentation
162
- if (finalConfig.requireFunctionDocs) {
163
- for (const method of cls.methods) {
164
- // Method scope filter
165
- if (scope === 'public') {
166
- const methodNode = this.findNodeByLocation(ast.root, method.location.start);
167
- if (methodNode && this.isNonPublicMethod(methodNode, adapter, sourceCode)) {
168
- continue;
169
- }
170
- }
171
- const methodDoc = method.jsDoc || '';
172
- if (!methodDoc || methodDoc.length < finalConfig.minDescriptionLength) {
173
- violations.push(this.createViolation(ast.filePath, method.location.start, `public method '${cls.name}.${method.name}' lacks proper documentation`, 'suggestion', 'method-documentation', undefined, `${cls.name}.${method.name}`));
174
- }
175
- }
176
- }
177
- }
92
+ violations.push(...analyzeClassDocumentation(ast, scan));
178
93
  }
179
94
  return violations;
180
95
  }
181
- // ---------------------------------------------------------------------------
182
- // R1.1 — Anonymous / inline callable detection
183
- // ---------------------------------------------------------------------------
184
96
  /**
185
- * Returns true if the node is an anonymous arrow/function expression used as:
186
- * (a) a call argument (any callee — no name whitelist)
187
- * (b) a JSX attribute value
188
- * (c) an object-literal property value or array element passed as an argument
189
- * (d) an IIFE
97
+ * Check if a name or path matches any exempt regex patterns.
190
98
  */
191
- isAnonymousOrCallback(node, adapter) {
192
- const nodeType = adapter.getNodeType(node);
193
- const parent = adapter.getParent(node);
194
- if (!parent)
195
- return false;
196
- const parentType = adapter.getNodeType(parent);
197
- // Arrow functions and function expressions
198
- if (nodeType === 'arrow_function' ||
199
- nodeType === 'function_expression' ||
200
- nodeType === 'generator_function_expression') {
201
- // (a) Call argument — parent is 'arguments' (args to any call expression)
202
- if (parentType === 'arguments') {
203
- return true;
204
- }
205
- // (b) JSX attribute value (event handlers, render props)
206
- if (parentType === 'jsx_expression' ||
207
- parentType === 'jsx_attribute' ||
208
- parentType === 'jsx_self_closing_element' ||
209
- parentType === 'jsx_opening_element') {
210
- return true;
211
- }
212
- // (c) Object literal property value in call arguments
213
- // chain: arrow pair → object → arguments → call_expression
214
- if (parentType === 'pair') {
215
- const gp = adapter.getParent(parent);
216
- if (gp) {
217
- const gpType = adapter.getNodeType(gp);
218
- if (gpType === 'object' || gpType === 'object_pattern') {
219
- const ggp = adapter.getParent(gp);
220
- if (ggp && adapter.getNodeType(ggp) === 'arguments') {
221
- return true;
222
- }
223
- }
224
- }
225
- }
226
- // (c) Array element in call arguments
227
- if (parentType === 'array') {
228
- const gp = adapter.getParent(parent);
229
- if (gp && adapter.getNodeType(gp) === 'arguments') {
230
- return true;
231
- }
232
- }
233
- // (d) IIFE the function is the callee of a call expression
234
- if (parentType === 'call_expression') {
235
- // Check if this node is in the function/callee position (not in arguments)
236
- const fnChild = this.getFirstChildOfType(parent, [
237
- 'arrow_function',
238
- 'function_expression',
239
- 'function',
240
- 'identifier',
241
- 'member_expression',
242
- 'call_expression',
243
- ]);
244
- if (fnChild) {
245
- // If the first function-ish child is at the same location, this IS the callee
246
- if (fnChild.location.start.line === node.location.start.line &&
247
- fnChild.location.start.column === node.location.start.column) {
248
- return true;
249
- }
250
- }
251
- }
252
- }
99
+ isExempt(name, patterns) {
100
+ return patterns.some(pattern => {
101
+ const regex = new RegExp(pattern, 'i');
102
+ return regex.test(name);
103
+ });
104
+ }
105
+ }
106
+ function makeViolation(file, location, message, classification) {
107
+ const v = {
108
+ file,
109
+ line: location.line,
110
+ column: location.column,
111
+ severity: classification.severity,
112
+ message,
113
+ rule: classification.rule,
114
+ analyzer: 'documentation'
115
+ };
116
+ if (classification.symbol)
117
+ v.functionName = classification.symbol;
118
+ return v;
119
+ }
120
+ /** R1.5 file-level documentation header check (defaults OFF). */
121
+ function checkFileHeader(ast, adapter, config, fileHeaders) {
122
+ const violations = [];
123
+ if (!fileHeaders)
124
+ return violations;
125
+ const skipGlobs = config.headerSkipGlobs ?? HEADER_SKIP_GLOBS_DEFAULT;
126
+ if (matchesAnyGlob(ast.filePath, skipGlobs))
127
+ return violations;
128
+ const fileDoc = getFileDocumentation(ast, adapter);
129
+ if (!fileDoc || fileDoc.length < config.minDescriptionLength) {
130
+ violations.push(makeViolation(ast.filePath, { line: 1, column: 1 }, 'File lacks proper documentation header', { severity: 'suggestion', rule: 'file-documentation' }));
131
+ }
132
+ return violations;
133
+ }
134
+ /** R1.1 + R1.2 true when a function should be skipped before doc checks. */
135
+ function shouldSkipFunction(node, func, scan, ast) {
136
+ const { adapter, sourceCode, scope } = scan;
137
+ // R1.1 Skip anonymous/inline callables
138
+ if (node && isAnonymousOrCallback(node, adapter)) {
139
+ return true;
140
+ }
141
+ // R1.2Scope filter (public API surface only)
142
+ if (scope !== 'public' || func.isExported) {
253
143
  return false;
254
144
  }
255
- /**
256
- * Returns true if a function_declaration node is nested inside another function/method
257
- * (a helper by construction — R1.1).
258
- */
259
- isNestedFunction(node, adapter) {
260
- const nodeType = adapter.getNodeType(node);
261
- if (nodeType !== 'function_declaration' && nodeType !== 'generator_function_declaration') {
262
- return false;
145
+ if (!func.isMethod) {
146
+ // Named function decl nested inside another function skip at default scope
147
+ if (node && isNestedFunction(node, adapter)) {
148
+ return true;
149
+ }
150
+ // Non-exported top-level function — skip
151
+ return true;
152
+ }
153
+ // It's a method — check visibility
154
+ if (node && isNonPublicMethod(node, adapter, sourceCode)) {
155
+ return true;
156
+ }
157
+ // Check if the enclosing class is exported
158
+ return !!(node && !isMethodOfExportedClass(node, adapter, ast));
159
+ }
160
+ /** R1.4/R1.6 — emit function/parameter/return documentation violations for one function. */
161
+ function checkFunctionDocumentation(ast, adapter, config, func) {
162
+ const violations = [];
163
+ const doc = func.jsDoc || '';
164
+ if (!doc || doc.length < config.minDescriptionLength) {
165
+ // Methods lacking any documentation are reported by the class loop as
166
+ // method-documentation — skip here so a public method is not
167
+ // double-reported as both function-documentation and method-documentation.
168
+ if (func.isMethod) {
169
+ return violations;
170
+ }
171
+ // R1.6 — Audience-reason message
172
+ const reason = func.isExported
173
+ ? `exported function '${func.name}' lacks proper documentation`
174
+ : `function '${func.name}' lacks proper documentation`;
175
+ violations.push(makeViolation(ast.filePath, func.location.start, reason, { severity: 'suggestion', rule: 'function-documentation', symbol: func.name }));
176
+ return violations;
177
+ }
178
+ return checkFunctionDocTags(ast.filePath, adapter, config, func);
179
+ }
180
+ /** R1 — parameter and return documentation (JSDoc tags, JSDoc languages only). */
181
+ function checkFunctionDocTags(file, adapter, config, func) {
182
+ const violations = [];
183
+ const doc = func.jsDoc || '';
184
+ // Param docs — JSDoc @param tags only apply to JSDoc languages.
185
+ const checkJsDocTags = JSDOC_LANGUAGES.has(adapter.name);
186
+ if (checkJsDocTags && config.requireParamDocs && func.parameters.length > 0) {
187
+ const missingParamDocs = checkParameterDocumentation(doc, func.parameters.map(p => p.name));
188
+ for (const param of missingParamDocs) {
189
+ violations.push(makeViolation(file, func.location.start, `Function '${func.name}' missing documentation for parameter '${param}'`, { severity: 'suggestion', rule: 'parameter-documentation', symbol: func.name }));
263
190
  }
264
- let current = adapter.getParent(node);
265
- while (current) {
266
- const type = adapter.getNodeType(current);
267
- // Skip enclosing blocks/statement blocks
268
- if (type === 'statement_block' ||
269
- type === 'block' ||
270
- type === 'program' ||
271
- type === 'export_statement') {
272
- current = adapter.getParent(current);
191
+ }
192
+ // Return docs — JSDoc @returns tags only apply to JSDoc languages.
193
+ if (checkJsDocTags &&
194
+ config.requireReturnDocs &&
195
+ func.returnType &&
196
+ func.returnType !== 'void' &&
197
+ !hasReturnDocumentation(doc)) {
198
+ violations.push(makeViolation(file, func.location.start, `Function '${func.name}' missing return value documentation`, { severity: 'suggestion', rule: 'return-documentation', symbol: func.name }));
199
+ }
200
+ return violations;
201
+ }
202
+ /** R1.1–R1.4, R1.6 — function/parameter/return documentation. */
203
+ function analyzeFunctionDocumentation(ast, scan) {
204
+ const { adapter, config } = scan;
205
+ const violations = [];
206
+ const functions = adapter.extractFunctions(ast);
207
+ const docsMinLines = config.docsMinLines ?? 5;
208
+ for (const func of functions) {
209
+ // Find the AST node for this function
210
+ const node = findNodeByLocation(ast.root, func.location.start);
211
+ if (shouldSkipFunction(node, func, scan, ast)) {
212
+ continue;
213
+ }
214
+ // R1.3 — Minimum-size gate
215
+ const bodyLines = (func.location.end.line - func.location.start.line) + 1;
216
+ if (bodyLines < docsMinLines) {
217
+ continue;
218
+ }
219
+ violations.push(...checkFunctionDocumentation(ast, adapter, config, func));
220
+ }
221
+ return violations;
222
+ }
223
+ /** Class + method documentation. */
224
+ function analyzeClassDocumentation(ast, scan) {
225
+ const { adapter, config, scope } = scan;
226
+ const violations = [];
227
+ const classes = adapter.extractClasses(ast);
228
+ for (const cls of classes) {
229
+ // Scope filter for classes
230
+ if (scope === 'public' && !cls.isExported) {
231
+ continue;
232
+ }
233
+ const doc = cls.jsDoc || '';
234
+ if (!doc || doc.length < config.minDescriptionLength) {
235
+ violations.push(makeViolation(ast.filePath, cls.location.start, `Class '${cls.name}' lacks proper documentation`, { severity: 'suggestion', rule: 'class-documentation', symbol: cls.name }));
236
+ }
237
+ // Method documentation
238
+ violations.push(...checkClassMethodDocumentation(ast, cls, scan));
239
+ }
240
+ return violations;
241
+ }
242
+ /** R1.2 — method-documentation for each public method of a class. */
243
+ function checkClassMethodDocumentation(ast, cls, scan) {
244
+ const { adapter, config, sourceCode, scope } = scan;
245
+ const violations = [];
246
+ if (!config.requireFunctionDocs)
247
+ return violations;
248
+ for (const method of cls.methods) {
249
+ // Method scope filter
250
+ if (scope === 'public') {
251
+ const methodNode = findNodeByLocation(ast.root, method.location.start);
252
+ if (methodNode && isNonPublicMethod(methodNode, adapter, sourceCode)) {
273
253
  continue;
274
254
  }
275
- // Found enclosing function or method → this is nested
276
- if (adapter.isFunction(current) || adapter.isMethod(current)) {
277
- return true;
278
- }
279
- // Hit something else (class body, module, etc.) → not nested in a function
280
- break;
281
255
  }
256
+ const methodDoc = method.jsDoc || '';
257
+ if (!methodDoc || methodDoc.length < config.minDescriptionLength) {
258
+ violations.push(makeViolation(ast.filePath, method.location.start, `public method '${cls.name}.${method.name}' lacks proper documentation`, { severity: 'suggestion', rule: 'method-documentation', symbol: `${cls.name}.${method.name}` }));
259
+ }
260
+ }
261
+ return violations;
262
+ }
263
+ // ---------------------------------------------------------------------------
264
+ // R1.1 — Anonymous / inline callable detection
265
+ // ---------------------------------------------------------------------------
266
+ /**
267
+ * Returns true if the node is an anonymous arrow/function expression used as:
268
+ * (a) a call argument (any callee — no name whitelist)
269
+ * (b) a JSX attribute value
270
+ * (c) an object-literal property value or array element passed as an argument
271
+ * (d) an IIFE
272
+ */
273
+ function isAnonymousOrCallback(node, adapter) {
274
+ const nodeType = adapter.getNodeType(node);
275
+ const parent = adapter.getParent(node);
276
+ if (!parent)
282
277
  return false;
278
+ const parentType = adapter.getNodeType(parent);
279
+ // Arrow functions and function expressions
280
+ if (nodeType === 'arrow_function' ||
281
+ nodeType === 'function_expression' ||
282
+ nodeType === 'generator_function_expression') {
283
+ // (a)/(c) Call argument — direct, or via object/array literal container.
284
+ if (isInlineInCallArguments(parent, adapter))
285
+ return true;
286
+ // (b) JSX attribute value (event handlers, render props)
287
+ if (isJsxAttributeValue(parentType))
288
+ return true;
289
+ // (d) IIFE — the function is the callee of a call expression
290
+ if (parentType === 'call_expression' && isIifeCallee(node, parent, adapter))
291
+ return true;
283
292
  }
284
- // ---------------------------------------------------------------------------
285
- // R1.2 — Method visibility helpers
286
- // ---------------------------------------------------------------------------
287
- /**
288
- * A method is non-public if it is private, protected, #-named, or _-prefixed.
289
- */
290
- isNonPublicMethod(node, adapter, sourceCode) {
291
- const type = adapter.getNodeType(node);
292
- if (type !== 'method_definition' && type !== 'public_field_definition') {
293
- return false;
293
+ return false;
294
+ }
295
+ /** (a)/(c) — true when `parent` positions the inline callable as a call argument. */
296
+ function isInlineInCallArguments(parent, adapter) {
297
+ const parentType = adapter.getNodeType(parent);
298
+ if (parentType === 'arguments')
299
+ return true;
300
+ // Object literal property value in call arguments: arrow → pair → object → arguments
301
+ if (parentType === 'pair') {
302
+ const gp = adapter.getParent(parent);
303
+ if (gp && (adapter.getNodeType(gp) === 'object' || adapter.getNodeType(gp) === 'object_pattern')) {
304
+ const ggp = adapter.getParent(gp);
305
+ return !!(ggp && adapter.getNodeType(ggp) === 'arguments');
294
306
  }
295
- // Check for tree-sitter accessibility modifiers in children
296
- if (node.children) {
297
- for (const child of node.children) {
298
- const childType = adapter.getNodeType(child);
299
- if (childType === 'accessibility_modifier' ||
300
- childType === 'private' ||
301
- childType === 'protected') {
302
- const text = adapter.getNodeText(child, sourceCode).trim();
303
- if (text === 'private' || text === 'protected') {
304
- return true;
305
- }
306
- }
307
- }
307
+ }
308
+ // Array element in call arguments: arrow → array → arguments
309
+ if (parentType === 'array') {
310
+ const gp = adapter.getParent(parent);
311
+ return !!(gp && adapter.getNodeType(gp) === 'arguments');
312
+ }
313
+ return false;
314
+ }
315
+ /** (b) true when the parent type is a JSX attribute/expression value. */
316
+ function isJsxAttributeValue(parentType) {
317
+ return (parentType === 'jsx_expression' ||
318
+ parentType === 'jsx_attribute' ||
319
+ parentType === 'jsx_self_closing_element' ||
320
+ parentType === 'jsx_opening_element');
321
+ }
322
+ /** (d) — true when `node` is the callee (not an argument) of the call expression. */
323
+ function isIifeCallee(node, parent, adapter) {
324
+ const fnChild = getFirstChildOfType(parent, [
325
+ 'arrow_function',
326
+ 'function_expression',
327
+ 'function',
328
+ 'identifier',
329
+ 'member_expression',
330
+ 'call_expression',
331
+ ]);
332
+ if (!fnChild)
333
+ return false;
334
+ return (fnChild.location.start.line === node.location.start.line &&
335
+ fnChild.location.start.column === node.location.start.column);
336
+ }
337
+ /**
338
+ * Returns true if a function_declaration node is nested inside another function/method
339
+ * (a helper by construction — R1.1).
340
+ */
341
+ function isNestedFunction(node, adapter) {
342
+ const nodeType = adapter.getNodeType(node);
343
+ if (nodeType !== 'function_declaration' && nodeType !== 'generator_function_declaration') {
344
+ return false;
345
+ }
346
+ let current = adapter.getParent(node);
347
+ while (current) {
348
+ const type = adapter.getNodeType(current);
349
+ // Skip enclosing blocks/statement blocks
350
+ if (type === 'statement_block' ||
351
+ type === 'block' ||
352
+ type === 'program' ||
353
+ type === 'export_statement') {
354
+ current = adapter.getParent(current);
355
+ continue;
308
356
  }
309
- // Check property/method name for #-prefix (JS private) or _-prefix (convention)
310
- const propName = this.getMethodName(node, adapter, sourceCode);
311
- if (propName && (propName.startsWith('#') || propName.startsWith('_'))) {
357
+ // Found enclosing function or method this is nested
358
+ if (adapter.isFunction(current) || adapter.isMethod(current)) {
312
359
  return true;
313
360
  }
361
+ // Hit something else (class body, module, etc.) → not nested in a function
362
+ break;
363
+ }
364
+ return false;
365
+ }
366
+ // ---------------------------------------------------------------------------
367
+ // R1.2 — Method visibility helpers
368
+ // ---------------------------------------------------------------------------
369
+ /**
370
+ * A method is non-public if it is private, protected, #-named, or _-prefixed.
371
+ */
372
+ function isNonPublicMethod(node, adapter, sourceCode) {
373
+ const type = adapter.getNodeType(node);
374
+ if (type !== 'method_definition' && type !== 'public_field_definition') {
314
375
  return false;
315
376
  }
316
- /**
317
- * Check if a method's enclosing class is exported.
318
- */
319
- isMethodOfExportedClass(node, adapter, ast) {
320
- let current = adapter.getParent(node);
321
- while (current) {
322
- if (adapter.isClass(current)) {
323
- // Check export by looking at parent of class node
324
- const classParent = adapter.getParent(current);
325
- if (classParent) {
326
- const classParentType = adapter.getNodeType(classParent);
327
- if (classParentType === 'export_statement') {
328
- return true;
329
- }
330
- // Also check if class declaration itself has export modifier
331
- const siblings = adapter.getChildren(classParent);
332
- for (const sib of siblings) {
333
- if (adapter.getNodeType(sib) === 'export' || adapter.getNodeType(sib) === 'export_statement') {
334
- // Verify this export wraps our class
335
- const exportChildren = adapter.getChildren(sib);
336
- for (const ec of exportChildren) {
337
- if (adapter.getNodeType(ec) === 'class_declaration' &&
338
- ec.location.start.line === current.location.start.line) {
339
- return true;
340
- }
341
- }
342
- }
343
- }
377
+ // Check for tree-sitter accessibility modifiers in children
378
+ if (node.children) {
379
+ for (const child of node.children) {
380
+ const childType = adapter.getNodeType(child);
381
+ if (childType === 'accessibility_modifier' ||
382
+ childType === 'private' ||
383
+ childType === 'protected') {
384
+ const text = adapter.getNodeText(child, sourceCode).trim();
385
+ if (text === 'private' || text === 'protected') {
386
+ return true;
344
387
  }
345
- // Use extractClasses to check isExported
346
- break;
347
388
  }
348
- current = adapter.getParent(current);
349
389
  }
350
- // Fallback: check extractClasses for isExported
351
- const classes = adapter.extractClasses(ast);
352
- for (const cls of classes) {
353
- // Find the class containing this method
354
- if (node.location.start.line >= cls.location.start.line &&
355
- node.location.start.line <= cls.location.end.line &&
356
- cls.isExported) {
390
+ }
391
+ // Check property/method name for #-prefix (JS private) or _-prefix (convention)
392
+ const propName = getMethodName(node, adapter, sourceCode);
393
+ if (propName && (propName.startsWith('#') || propName.startsWith('_'))) {
394
+ return true;
395
+ }
396
+ return false;
397
+ }
398
+ /**
399
+ * Check if a method's enclosing class is exported.
400
+ */
401
+ function isMethodOfExportedClass(node, adapter, ast) {
402
+ let current = adapter.getParent(node);
403
+ while (current) {
404
+ if (adapter.isClass(current)) {
405
+ if (isClassDirectlyExported(current, adapter))
357
406
  return true;
358
- }
407
+ break;
359
408
  }
360
- return false;
409
+ current = adapter.getParent(current);
361
410
  }
362
- // ---------------------------------------------------------------------------
363
- // Utility helpers
364
- // ---------------------------------------------------------------------------
365
- /**
366
- * Check if a file path matches any picomatch glob pattern.
367
- */
368
- matchesAnyGlob(filePath, globs) {
369
- for (const glob of globs) {
370
- if (picomatch.isMatch(filePath, glob)) {
411
+ // Fallback: check extractClasses for isExported
412
+ const classes = adapter.extractClasses(ast);
413
+ for (const cls of classes) {
414
+ // Find the class containing this method
415
+ if (node.location.start.line >= cls.location.start.line &&
416
+ node.location.start.line <= cls.location.end.line &&
417
+ cls.isExported) {
418
+ return true;
419
+ }
420
+ }
421
+ return false;
422
+ }
423
+ /** True when the class declaration node is directly wrapped in an export. */
424
+ function isClassDirectlyExported(classNode, adapter) {
425
+ const classParent = adapter.getParent(classNode);
426
+ if (!classParent)
427
+ return false;
428
+ if (adapter.getNodeType(classParent) === 'export_statement')
429
+ return true;
430
+ // Also check if the class declaration itself has an export modifier.
431
+ const siblings = adapter.getChildren(classParent);
432
+ for (const sib of siblings) {
433
+ const sibType = adapter.getNodeType(sib);
434
+ if (sibType !== 'export' && sibType !== 'export_statement')
435
+ continue;
436
+ const exportChildren = adapter.getChildren(sib);
437
+ for (const ec of exportChildren) {
438
+ if (adapter.getNodeType(ec) === 'class_declaration' &&
439
+ ec.location.start.line === classNode.location.start.line) {
371
440
  return true;
372
441
  }
373
442
  }
374
- return false;
375
- }
376
- /**
377
- * Check if a name or path matches any exempt regex patterns.
378
- */
379
- isExempt(name, patterns) {
380
- return patterns.some(pattern => {
381
- const regex = new RegExp(pattern, 'i');
382
- return regex.test(name);
383
- });
384
443
  }
385
- /**
386
- * Get the name of a method definition node.
387
- */
388
- getMethodName(node, adapter, sourceCode) {
389
- if (node.children) {
390
- for (const child of node.children) {
391
- const type = adapter.getNodeType(child);
392
- if (type === 'property_identifier' || type === 'identifier') {
393
- return adapter.getNodeText(child, sourceCode).trim();
394
- }
395
- }
444
+ return false;
445
+ }
446
+ // ---------------------------------------------------------------------------
447
+ // Utility helpers
448
+ // ---------------------------------------------------------------------------
449
+ /**
450
+ * Check if a file path matches any picomatch glob pattern.
451
+ */
452
+ function matchesAnyGlob(filePath, globs) {
453
+ for (const glob of globs) {
454
+ if (picomatch.isMatch(filePath, glob)) {
455
+ return true;
396
456
  }
397
- return null;
398
457
  }
399
- /**
400
- * Get the first child node matching one of the given types.
401
- */
402
- getFirstChildOfType(node, types) {
403
- if (!node.children)
404
- return null;
458
+ return false;
459
+ }
460
+ /**
461
+ * Get the name of a method definition node.
462
+ */
463
+ function getMethodName(node, adapter, sourceCode) {
464
+ if (node.children) {
405
465
  for (const child of node.children) {
406
- if (types.includes(child.type)) {
407
- return child;
466
+ const type = adapter.getNodeType(child);
467
+ if (type === 'property_identifier' || type === 'identifier') {
468
+ return adapter.getNodeText(child, sourceCode).trim();
408
469
  }
409
470
  }
410
- return null;
411
471
  }
412
- /**
413
- * Get file-level documentation (usually at the top).
414
- */
415
- getFileDocumentation(ast, adapter) {
416
- const firstChild = ast.root.children?.[0];
417
- if (firstChild) {
418
- return adapter.getDocumentation(firstChild);
419
- }
472
+ return null;
473
+ }
474
+ /**
475
+ * Get the first child node matching one of the given types.
476
+ */
477
+ function getFirstChildOfType(node, types) {
478
+ if (!node.children)
420
479
  return null;
421
- }
422
- /**
423
- * Find a node by its location via BFS.
424
- */
425
- findNodeByLocation(root, location) {
426
- const queue = [root];
427
- while (queue.length > 0) {
428
- const node = queue.shift();
429
- if (node.location.start.line === location.line &&
430
- node.location.start.column === location.column) {
431
- return node;
432
- }
433
- if (node.children) {
434
- queue.push(...node.children);
435
- }
480
+ for (const child of node.children) {
481
+ if (types.includes(child.type)) {
482
+ return child;
436
483
  }
437
- return null;
438
484
  }
439
- /**
440
- * Check which parameters are missing documentation.
441
- */
442
- checkParameterDocumentation(doc, paramNames) {
443
- const missingParams = [];
444
- for (const param of paramNames) {
445
- const paramRegex = new RegExp(`@param\\s+(?:\\{[^}]+\\}\\s+)?${param}\\b`, 'i');
446
- if (!paramRegex.test(doc)) {
447
- missingParams.push(param);
448
- }
485
+ return null;
486
+ }
487
+ /**
488
+ * Get file-level documentation (usually at the top).
489
+ */
490
+ function getFileDocumentation(ast, adapter) {
491
+ const firstChild = ast.root.children?.[0];
492
+ if (firstChild) {
493
+ return adapter.getDocumentation(firstChild);
494
+ }
495
+ return null;
496
+ }
497
+ /**
498
+ * Find a node by its location via BFS.
499
+ */
500
+ function findNodeByLocation(root, location) {
501
+ const queue = [root];
502
+ while (queue.length > 0) {
503
+ const node = queue.shift();
504
+ if (node.location.start.line === location.line &&
505
+ node.location.start.column === location.column) {
506
+ return node;
507
+ }
508
+ if (node.children) {
509
+ queue.push(...node.children);
449
510
  }
450
- return missingParams;
451
511
  }
452
- /**
453
- * Check if documentation contains return value documentation.
454
- */
455
- hasReturnDocumentation(doc) {
456
- return /@returns?\b/i.test(doc);
512
+ return null;
513
+ }
514
+ /**
515
+ * Check which parameters are missing documentation.
516
+ */
517
+ function checkParameterDocumentation(doc, paramNames) {
518
+ const missingParams = [];
519
+ for (const param of paramNames) {
520
+ const paramRegex = new RegExp(`@param\\s+(?:\\{[^}]+\\}\\s+)?${param}\\b`, 'i');
521
+ if (!paramRegex.test(doc)) {
522
+ missingParams.push(param);
523
+ }
457
524
  }
525
+ return missingParams;
526
+ }
527
+ /**
528
+ * Check if documentation contains return value documentation.
529
+ */
530
+ function hasReturnDocumentation(doc) {
531
+ return /@returns?\b/i.test(doc);
458
532
  }
459
533
  //# sourceMappingURL=UniversalDocumentationAnalyzer.js.map