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.
- package/CHANGELOG.md +46 -1
- package/dist/analyzers/__verify_self_probe.d.ts +2 -0
- package/dist/analyzers/__verify_self_probe.d.ts.map +1 -0
- package/dist/analyzers/__verify_self_probe.js +5 -0
- package/dist/analyzers/__verify_self_probe.js.map +1 -0
- package/dist/analyzers/analyzerUtils.d.ts +34 -1
- package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
- package/dist/analyzers/analyzerUtils.js +61 -22
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +18 -23
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +198 -186
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts +78 -55
- package/dist/analyzers/cross-language/DependencyGraphBuilder.d.ts.map +1 -1
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js +244 -243
- package/dist/analyzers/cross-language/DependencyGraphBuilder.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +27 -37
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +314 -275
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +17 -46
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +620 -523
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
- package/dist/analyzers/documentationAnalyzer.d.ts +7 -2
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +259 -198
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/invariantsAnalyzer.js +57 -50
- package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +10 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.js +10 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.d.ts +22 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/drizzleAdapter.js +42 -44
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.d.ts +30 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -1
- package/dist/analyzers/orm/prismaAdapter.js +30 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -1
- package/dist/analyzers/provenance.d.ts +62 -2
- package/dist/analyzers/provenance.d.ts.map +1 -1
- package/dist/analyzers/provenance.js +443 -349
- package/dist/analyzers/provenance.js.map +1 -1
- package/dist/analyzers/reactAnalyzer.d.ts +11 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +188 -125
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +110 -1
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -1
- package/dist/analyzers/ruleRegistry.js +1714 -124
- package/dist/analyzers/ruleRegistry.js.map +1 -1
- package/dist/analyzers/ruleTiming.d.ts +54 -0
- package/dist/analyzers/ruleTiming.d.ts.map +1 -0
- package/dist/analyzers/ruleTiming.js +71 -0
- package/dist/analyzers/ruleTiming.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +13 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +362 -283
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +26 -68
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +430 -391
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +17 -202
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +1073 -1121
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts +3 -49
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +414 -340
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts +48 -5
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +262 -134
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +23 -214
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +139 -1521
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +78 -79
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +613 -480
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts +307 -0
- package/dist/analyzers/universal/schema/codeAnalysis.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js +965 -0
- package/dist/analyzers/universal/schema/codeAnalysis.js.map +1 -0
- package/dist/analyzers/universal/schema/config.d.ts +31 -0
- package/dist/analyzers/universal/schema/config.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/config.js +57 -0
- package/dist/analyzers/universal/schema/config.js.map +1 -0
- package/dist/analyzers/universal/schema/discovery.d.ts +167 -0
- package/dist/analyzers/universal/schema/discovery.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/discovery.js +616 -0
- package/dist/analyzers/universal/schema/discovery.js.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts +27 -0
- package/dist/analyzers/universal/schema/jsonSchema.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/jsonSchema.js +368 -0
- package/dist/analyzers/universal/schema/jsonSchema.js.map +1 -0
- package/dist/analyzers/universal/schema/migrations.d.ts +90 -0
- package/dist/analyzers/universal/schema/migrations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/migrations.js +238 -0
- package/dist/analyzers/universal/schema/migrations.js.map +1 -0
- package/dist/analyzers/universal/schema/types.d.ts +131 -0
- package/dist/analyzers/universal/schema/types.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/types.js +10 -0
- package/dist/analyzers/universal/schema/types.js.map +1 -0
- package/dist/analyzers/universal/schema/violations.d.ts +44 -0
- package/dist/analyzers/universal/schema/violations.d.ts.map +1 -0
- package/dist/analyzers/universal/schema/violations.js +54 -0
- package/dist/analyzers/universal/schema/violations.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +89 -13
- package/dist/auditRunner.js.map +1 -1
- package/dist/cli.js +315 -40
- package/dist/cli.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +9 -7
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +12 -3
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +15 -5
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/effectiveConfig.d.ts +75 -0
- package/dist/config/effectiveConfig.d.ts.map +1 -0
- package/dist/config/effectiveConfig.js +213 -0
- package/dist/config/effectiveConfig.js.map +1 -0
- package/dist/config/pathProfiles.d.ts +7 -4
- package/dist/config/pathProfiles.d.ts.map +1 -1
- package/dist/config/pathProfiles.js +8 -10
- package/dist/config/pathProfiles.js.map +1 -1
- package/dist/config/thresholdRationales.d.ts +45 -0
- package/dist/config/thresholdRationales.d.ts.map +1 -0
- package/dist/config/thresholdRationales.js +67 -0
- package/dist/config/thresholdRationales.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -1
- package/dist/conventions/conventionMiner.js +6 -2
- package/dist/conventions/conventionMiner.js.map +1 -1
- package/dist/enforcement/diffGate.d.ts +55 -0
- package/dist/enforcement/diffGate.d.ts.map +1 -0
- package/dist/enforcement/diffGate.js +136 -0
- package/dist/enforcement/diffGate.js.map +1 -0
- package/dist/enforcement/gate.d.ts +36 -0
- package/dist/enforcement/gate.d.ts.map +1 -0
- package/dist/enforcement/gate.js +72 -0
- package/dist/enforcement/gate.js.map +1 -0
- package/dist/enforcement/suppressions.d.ts +41 -0
- package/dist/enforcement/suppressions.d.ts.map +1 -0
- package/dist/enforcement/suppressions.js +170 -0
- package/dist/enforcement/suppressions.js.map +1 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +4 -1
- package/dist/fingerprint.js.map +1 -1
- package/dist/invariants/ruleEngine.d.ts +3 -4
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +2 -2
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/languages/LanguageOrchestrator.d.ts +14 -0
- package/dist/languages/LanguageOrchestrator.d.ts.map +1 -1
- package/dist/languages/LanguageOrchestrator.js +93 -55
- package/dist/languages/LanguageOrchestrator.js.map +1 -1
- package/dist/languages/LanguageRegistry.d.ts +8 -0
- package/dist/languages/LanguageRegistry.d.ts.map +1 -1
- package/dist/languages/LanguageRegistry.js +8 -0
- package/dist/languages/LanguageRegistry.js.map +1 -1
- package/dist/languages/RuntimeManager.d.ts +82 -60
- package/dist/languages/RuntimeManager.d.ts.map +1 -1
- package/dist/languages/RuntimeManager.js +404 -349
- package/dist/languages/RuntimeManager.js.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +51 -6
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +127 -88
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/adapterBridge.d.ts +36 -0
- package/dist/languages/adapterBridge.d.ts.map +1 -1
- package/dist/languages/adapterBridge.js +89 -35
- package/dist/languages/adapterBridge.js.map +1 -1
- package/dist/languages/go/GoAdapter.d.ts +66 -23
- package/dist/languages/go/GoAdapter.d.ts.map +1 -1
- package/dist/languages/go/GoAdapter.js +553 -474
- package/dist/languages/go/GoAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +33 -4
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +101 -66
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -1
- package/dist/languages/tree-sitter/converter.d.ts +18 -0
- package/dist/languages/tree-sitter/converter.d.ts.map +1 -1
- package/dist/languages/tree-sitter/converter.js +18 -0
- package/dist/languages/tree-sitter/converter.js.map +1 -1
- package/dist/languages/tree-sitter/parser.d.ts +39 -2
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +93 -12
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/types.d.ts +67 -2
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +253 -72
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +1419 -923
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/mcp-tools-shared.d.ts.map +1 -1
- package/dist/mcp-tools-shared.js.map +1 -1
- package/dist/pipeline.d.ts +13 -5
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +381 -200
- package/dist/pipeline.js.map +1 -1
- package/dist/pipelineAdapters.d.ts +3 -8
- package/dist/pipelineAdapters.d.ts.map +1 -1
- package/dist/pipelineAdapters.js +149 -94
- package/dist/pipelineAdapters.js.map +1 -1
- package/dist/presets/presets.d.ts +54 -0
- package/dist/presets/presets.d.ts.map +1 -0
- package/dist/presets/presets.js +181 -0
- package/dist/presets/presets.js.map +1 -0
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +0 -2
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/ruleAliases.d.ts +67 -0
- package/dist/ruleAliases.d.ts.map +1 -0
- package/dist/ruleAliases.js +79 -0
- package/dist/ruleAliases.js.map +1 -0
- package/dist/styles/cssAstExtractor.d.ts.map +1 -1
- package/dist/styles/cssAstExtractor.js +78 -18
- package/dist/styles/cssAstExtractor.js.map +1 -1
- package/dist/styles/styleIndexer.js +10 -1
- package/dist/styles/styleIndexer.js.map +1 -1
- package/dist/styles/tailwindConfigLoader.d.ts +8 -6
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -1
- package/dist/styles/tailwindConfigLoader.js +61 -15
- package/dist/styles/tailwindConfigLoader.js.map +1 -1
- package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
- package/dist/styles/tailwindUtilityExpander.js +13 -1
- package/dist/styles/tailwindUtilityExpander.js.map +1 -1
- package/dist/types.d.ts +181 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/package.json +5 -3
- package/plugin/hooks/hooks.json +1 -1
- package/plugin/scripts/hook-audit.sh +25 -7
- 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
|
|
79
|
+
return [];
|
|
72
80
|
}
|
|
81
|
+
const violations = [];
|
|
73
82
|
// File-level documentation header check — R1.5 (defaults OFF)
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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.2 — Scope filter (public API surface only)
|
|
142
|
+
if (scope !== 'public' || func.isExported) {
|
|
253
143
|
return false;
|
|
254
144
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
//
|
|
310
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
|
|
409
|
+
current = adapter.getParent(current);
|
|
361
410
|
}
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
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
|
-
|
|
407
|
-
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|