@futdevpro/dynamo-eslint 1.15.22 → 1.15.28
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/.c8rc.json +26 -26
- package/.cursor/rules/__assistant_guide.mdc +30 -30
- package/.cursor/rules/__main.mdc +64 -64
- package/.cursor/rules/_ag_backend-structure.mdc +85 -85
- package/.cursor/rules/_ag_backend.mdc +16 -16
- package/.cursor/rules/_ag_debug.mdc +7 -7
- package/.cursor/rules/_ag_documentation_writing_rules.mdc +372 -372
- package/.cursor/rules/_ag_file-refactoring.mdc +113 -113
- package/.cursor/rules/_ag_fixes_rules.mdc +5 -5
- package/.cursor/rules/_ag_frontend-structure.mdc +86 -86
- package/.cursor/rules/_ag_frontend.mdc +39 -39
- package/.cursor/rules/_ag_import-rules.mdc +44 -44
- package/.cursor/rules/_ag_naming.mdc +115 -115
- package/.cursor/rules/_ag_running_commands.mdc +4 -4
- package/.cursor/rules/_ag_server-controller.mdc +5 -5
- package/.cursor/rules/_ag_should-be.mdc +6 -6
- package/.cursor/rules/_ag_swearing.mdc +47 -47
- package/.cursor/rules/ai_development_guide.md +60 -60
- package/.cursor/rules/ai_directives.md +114 -114
- package/.cursor/rules/cursor-rules.md +160 -160
- package/.cursor/rules/default-command.mdc +464 -464
- package/.cursor/rules/error_code_pattern.md +39 -39
- package/.cursor/rules/saved rule mcp server use.md +15 -15
- package/.dynamo/pipeline.cicd.config.json +184 -184
- package/.dynamo/version-bump.config.json +5 -5
- package/.eslintrc.json +4 -4
- package/.github/workflows/main.yml +436 -436
- package/.husky/pre-commit +2 -2
- package/.vscode/settings.json +11 -11
- package/README.md +319 -319
- package/__documentations/2026-04-18-ccap-review-integration.md +173 -173
- package/__documentations/2026-08-18-bfr-ctm-005-eslint9-validator-report.md +45 -0
- package/build/configs/base.d.ts +9 -9
- package/build/configs/base.js +150 -150
- package/build/configs/fsm.d.ts +1 -1
- package/build/configs/fsm.js +7 -7
- package/build/configs/ngx-package.d.ts +1 -1
- package/build/configs/ngx-package.js +7 -7
- package/build/configs/ngx.d.ts +4 -4
- package/build/configs/ngx.js +91 -91
- package/build/configs/nts-package.d.ts +1 -1
- package/build/configs/nts-package.js +17 -17
- package/build/configs/nts.d.ts +1 -1
- package/build/configs/nts.js +31 -31
- package/build/index.d.ts +1 -1
- package/build/index.js +2 -2
- package/build/plugin/index.d.ts +52 -52
- package/build/plugin/index.js +80 -80
- package/build/plugin/rules/angular/angular-for-track-logic.d.ts +7 -7
- package/build/plugin/rules/angular/angular-for-track-logic.js +81 -81
- package/build/plugin/rules/angular/effect-signal-loop.d.ts +9 -9
- package/build/plugin/rules/angular/effect-signal-loop.js +112 -112
- package/build/plugin/rules/angular/no-async-ngoninit.d.ts +8 -8
- package/build/plugin/rules/angular/no-async-ngoninit.js +42 -42
- package/build/plugin/rules/angular/no-component-direct-api.d.ts +9 -9
- package/build/plugin/rules/angular/no-component-direct-api.js +75 -75
- package/build/plugin/rules/angular/no-method-call-in-template.d.ts +8 -8
- package/build/plugin/rules/angular/no-method-call-in-template.js +79 -79
- package/build/plugin/rules/angular/no-ngfor-directive.d.ts +7 -7
- package/build/plugin/rules/angular/no-ngfor-directive.js +59 -59
- package/build/plugin/rules/angular/no-ngif-directive.d.ts +7 -7
- package/build/plugin/rules/angular/no-ngif-directive.js +59 -59
- package/build/plugin/rules/explicit-types.d.ts +3 -3
- package/build/plugin/rules/explicit-types.js +178 -178
- package/build/plugin/rules/import/import-order.d.ts +3 -3
- package/build/plugin/rules/import/import-order.js +154 -154
- package/build/plugin/rules/import/no-import-type.d.ts +3 -3
- package/build/plugin/rules/import/no-import-type.js +45 -45
- package/build/plugin/rules/import/no-js-import.d.ts +3 -3
- package/build/plugin/rules/import/no-js-import.js +40 -40
- package/build/plugin/rules/naming-patterns.d.ts +7 -7
- package/build/plugin/rules/naming-patterns.d.ts.map +1 -1
- package/build/plugin/rules/naming-patterns.js +366 -350
- package/build/plugin/rules/naming-patterns.js.map +1 -1
- package/build/plugin/rules/no-getter-logic.d.ts +3 -3
- package/build/plugin/rules/no-getter-logic.js +194 -194
- package/build/plugin/rules/prefer-enum-over-string-union.d.ts +3 -3
- package/build/plugin/rules/prefer-enum-over-string-union.js +289 -289
- package/build/plugin/rules/prefer-interface-over-duplicate-types.d.ts +3 -3
- package/build/plugin/rules/prefer-interface-over-duplicate-types.js +264 -264
- package/build/plugin/rules/require-jsdoc-description.d.ts +3 -3
- package/build/plugin/rules/require-jsdoc-description.js +496 -496
- package/build/plugin/rules/require-test-description-prefix.d.ts +3 -3
- package/build/plugin/rules/require-test-description-prefix.js +135 -135
- package/build/plugin/rules/ts/data-model-extends.d.ts +8 -8
- package/build/plugin/rules/ts/data-model-extends.js +94 -94
- package/build/plugin/rules/ts/no-any-type.d.ts +8 -8
- package/build/plugin/rules/ts/no-any-type.js +36 -36
- package/build/plugin/rules/ts/no-as-cast.d.ts +8 -8
- package/build/plugin/rules/ts/no-as-cast.js +49 -49
- package/build/plugin/rules/ts/no-dynamic-imports.d.ts +8 -8
- package/build/plugin/rules/ts/no-dynamic-imports.js +50 -50
- package/build/plugin/rules/ts/no-native-browser-dialogs.d.ts +8 -8
- package/build/plugin/rules/ts/no-native-browser-dialogs.js +78 -78
- package/build/plugin/rules/ts/no-plain-function-export.d.ts +9 -9
- package/build/plugin/rules/ts/no-plain-function-export.js +69 -69
- package/build/plugin/rules/ts/one-export-per-file.d.ts +8 -8
- package/build/plugin/rules/ts/one-export-per-file.js +73 -73
- package/build/scripts/dynamo-fix.d.ts +26 -26
- package/build/scripts/dynamo-fix.js +146 -146
- package/build/scripts/dynamo-fix.js.map +1 -1
- package/build/scripts/eslintrc-audit.d.ts +26 -26
- package/build/scripts/eslintrc-audit.js +100 -100
- package/build/scripts/fix-return-types.d.ts +74 -74
- package/build/scripts/fix-return-types.js +226 -226
- package/build/scripts/validate-imports.d.ts +26 -26
- package/build/scripts/validate-imports.js +157 -137
- package/build/scripts/validate-imports.js.map +1 -1
- package/build/scripts/validate-naming.d.ts +26 -26
- package/build/scripts/validate-naming.js +157 -123
- package/build/scripts/validate-naming.js.map +1 -1
- package/eslint.config.js +15 -15
- package/package.json +105 -105
- package/samples/.vscode/settings.json +13 -13
- package/samples/base/.eslintrc.json +6 -6
- package/samples/base/eslint.config.js +3 -3
- package/samples/fsm/.eslintrc.json +4 -4
- package/samples/fsm/eslint.config.js +3 -3
- package/samples/ngx/.eslintrc.json +6 -6
- package/samples/ngx/eslint.config.js +3 -3
- package/samples/ngx-package/.eslintrc.json +4 -4
- package/samples/ngx-package/eslint.config.js +3 -3
- package/samples/nts/.eslintrc.json +6 -6
- package/samples/nts/eslint.config.js +3 -3
- package/samples/nts-package/.eslintrc.json +4 -4
- package/samples/nts-package/eslint.config.js +3 -3
- package/samples/package.json.example +26 -26
- package/samples/poc-sample.ts +38 -38
- package/samples/poc-violations.ts +54 -54
- package/scripts/run-coverage-tests.js +24 -24
- package/spec/support/helpers/ts-node-helper.js +9 -9
- package/spec/support/jasmine.coverage.json +23 -23
- package/spec/support/jasmine.json +24 -24
- package/src/configs/base.ts +157 -157
- package/src/configs/fsm.ts +10 -10
- package/src/configs/ngx-package.ts +10 -10
- package/src/configs/ngx.ts +96 -96
- package/src/configs/nts-package.ts +20 -20
- package/src/configs/nts.ts +34 -34
- package/src/index.ts +4 -4
- package/src/plugin/index.ts +86 -86
- package/src/plugin/rules/angular/angular-for-track-logic.spec.ts +44 -44
- package/src/plugin/rules/angular/angular-for-track-logic.ts +88 -88
- package/src/plugin/rules/angular/effect-signal-loop.spec.ts +89 -89
- package/src/plugin/rules/angular/effect-signal-loop.ts +125 -125
- package/src/plugin/rules/angular/no-async-ngoninit.spec.ts +50 -50
- package/src/plugin/rules/angular/no-async-ngoninit.ts +43 -43
- package/src/plugin/rules/angular/no-component-direct-api.spec.ts +78 -78
- package/src/plugin/rules/angular/no-component-direct-api.ts +82 -82
- package/src/plugin/rules/angular/no-method-call-in-template.spec.ts +63 -63
- package/src/plugin/rules/angular/no-method-call-in-template.ts +89 -89
- package/src/plugin/rules/angular/no-ngfor-directive.spec.ts +44 -44
- package/src/plugin/rules/angular/no-ngfor-directive.ts +62 -62
- package/src/plugin/rules/angular/no-ngif-directive.spec.ts +44 -44
- package/src/plugin/rules/angular/no-ngif-directive.ts +62 -62
- package/src/plugin/rules/explicit-types.spec.ts +190 -190
- package/src/plugin/rules/explicit-types.ts +186 -186
- package/src/plugin/rules/import/import-order.spec.ts +219 -219
- package/src/plugin/rules/import/import-order.ts +197 -197
- package/src/plugin/rules/import/no-import-type.spec.ts +69 -69
- package/src/plugin/rules/import/no-import-type.ts +46 -46
- package/src/plugin/rules/import/no-js-import.spec.ts +82 -82
- package/src/plugin/rules/import/no-js-import.ts +45 -45
- package/src/plugin/rules/naming-patterns.spec.ts +189 -127
- package/src/plugin/rules/naming-patterns.ts +426 -408
- package/src/plugin/rules/no-getter-logic.spec.ts +591 -591
- package/src/plugin/rules/no-getter-logic.ts +220 -220
- package/src/plugin/rules/prefer-enum-over-string-union.spec.ts +583 -583
- package/src/plugin/rules/prefer-enum-over-string-union.ts +333 -333
- package/src/plugin/rules/prefer-interface-over-duplicate-types.spec.ts +404 -404
- package/src/plugin/rules/prefer-interface-over-duplicate-types.ts +314 -314
- package/src/plugin/rules/require-jsdoc-description.spec.ts +779 -779
- package/src/plugin/rules/require-jsdoc-description.ts +572 -572
- package/src/plugin/rules/require-test-description-prefix.spec.ts +459 -459
- package/src/plugin/rules/require-test-description-prefix.ts +154 -154
- package/src/plugin/rules/ts/data-model-extends.spec.ts +84 -84
- package/src/plugin/rules/ts/data-model-extends.ts +100 -100
- package/src/plugin/rules/ts/no-any-type.spec.ts +28 -28
- package/src/plugin/rules/ts/no-any-type.ts +36 -36
- package/src/plugin/rules/ts/no-as-cast.spec.ts +51 -51
- package/src/plugin/rules/ts/no-as-cast.ts +51 -51
- package/src/plugin/rules/ts/no-dynamic-imports.spec.ts +54 -54
- package/src/plugin/rules/ts/no-dynamic-imports.ts +49 -49
- package/src/plugin/rules/ts/no-native-browser-dialogs.spec.ts +54 -54
- package/src/plugin/rules/ts/no-native-browser-dialogs.ts +84 -84
- package/src/plugin/rules/ts/no-plain-function-export.spec.ts +72 -72
- package/src/plugin/rules/ts/no-plain-function-export.ts +73 -73
- package/src/plugin/rules/ts/one-export-per-file.spec.ts +64 -64
- package/src/plugin/rules/ts/one-export-per-file.ts +77 -77
- package/src/scripts/dynamo-fix.ts +172 -172
- package/src/scripts/eslintrc-audit.ts +116 -116
- package/src/scripts/fix-return-types.spec.ts +65 -65
- package/src/scripts/fix-return-types.ts +288 -288
- package/src/scripts/validate-imports.ts +278 -249
- package/src/scripts/validate-naming.ts +204 -176
- package/src/scripts/validator-cli.integration.spec.ts +96 -0
- package/tsconfig.json +31 -31
|
@@ -1,572 +1,572 @@
|
|
|
1
|
-
import { Rule } from 'eslint';
|
|
2
|
-
|
|
3
|
-
interface RuleOptions {
|
|
4
|
-
scope?: 'public' | 'all';
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
const rule: Rule.RuleModule = {
|
|
8
|
-
meta: {
|
|
9
|
-
type: 'suggestion',
|
|
10
|
-
docs: {
|
|
11
|
-
description: 'Require JSDoc comment blocks with description for functions, classes, interfaces, and enums',
|
|
12
|
-
recommended: true,
|
|
13
|
-
},
|
|
14
|
-
schema: [
|
|
15
|
-
{
|
|
16
|
-
type: 'object',
|
|
17
|
-
properties: {
|
|
18
|
-
scope: {
|
|
19
|
-
type: 'string',
|
|
20
|
-
enum: [ 'public', 'all' ],
|
|
21
|
-
default: 'public',
|
|
22
|
-
},
|
|
23
|
-
},
|
|
24
|
-
additionalProperties: false,
|
|
25
|
-
},
|
|
26
|
-
],
|
|
27
|
-
messages: {
|
|
28
|
-
missingJSDoc: '{{type}} "{{name}}" must have a JSDoc comment with description.',
|
|
29
|
-
emptyJSDoc: '{{type}} "{{name}}" JSDoc comment must contain description text.',
|
|
30
|
-
invalidJSDocPosition: '{{type}} "{{name}}" JSDoc comment must be on the line immediately before.',
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
create(context) {
|
|
34
|
-
const options: RuleOptions = context.options[0] || {};
|
|
35
|
-
const scope: string = options.scope || 'public';
|
|
36
|
-
const sourceCode: any = context.sourceCode;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Check if a node is public/exported (should be documented)
|
|
40
|
-
*/
|
|
41
|
-
function isPublic(node: any): boolean {
|
|
42
|
-
try {
|
|
43
|
-
// Check if node itself is exported
|
|
44
|
-
if (node.parent && node.parent.type === 'ExportNamedDeclaration') {
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (node.parent && node.parent.type === 'ExportDefaultDeclaration') {
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Check for export keyword in declaration
|
|
53
|
-
if (node.type === 'FunctionDeclaration' && node.parent &&
|
|
54
|
-
node.parent.type === 'Program' && node.id) {
|
|
55
|
-
// Top-level function - check if exported
|
|
56
|
-
const program: any = node.parent;
|
|
57
|
-
|
|
58
|
-
if (program.body && Array.isArray(program.body)) {
|
|
59
|
-
for (const statement of program.body) {
|
|
60
|
-
if (statement.type === 'ExportNamedDeclaration' &&
|
|
61
|
-
statement.declaration === node) {
|
|
62
|
-
return true;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// For class members, check accessibility modifiers
|
|
69
|
-
if (node.type === 'MethodDefinition' || node.type === 'PropertyDefinition') {
|
|
70
|
-
// If has private modifier, it's not public
|
|
71
|
-
if (node.accessibility === 'private') {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// If has protected modifier, it's not public
|
|
76
|
-
if (node.accessibility === 'protected') {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Default to public if no modifiers
|
|
81
|
-
return true;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// For top-level declarations, check if they're exported
|
|
85
|
-
if (node.parent && node.parent.type === 'Program') {
|
|
86
|
-
// Check if this declaration is exported
|
|
87
|
-
const program: any = node.parent;
|
|
88
|
-
|
|
89
|
-
if (program.body && Array.isArray(program.body)) {
|
|
90
|
-
for (const statement of program.body) {
|
|
91
|
-
if (statement.type === 'ExportNamedDeclaration' &&
|
|
92
|
-
statement.declaration === node) {
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (statement.type === 'ExportDefaultDeclaration' &&
|
|
97
|
-
statement.declaration === node) {
|
|
98
|
-
return true;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return false; // Not exported
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Default to public for other cases
|
|
107
|
-
return true;
|
|
108
|
-
} catch (error) {
|
|
109
|
-
console.error('[require-jsdoc-description] Error in isPublic:', error);
|
|
110
|
-
|
|
111
|
-
return true; // Default to requiring documentation
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Check if JSDoc comment is on the line immediately before the node
|
|
117
|
-
*/
|
|
118
|
-
function isJSDocOnPreviousLine(node: any, jsdocComment: any): boolean {
|
|
119
|
-
try {
|
|
120
|
-
const nodeStart: number | undefined = node.loc?.start?.line;
|
|
121
|
-
const commentEnd: number | undefined = jsdocComment.loc?.end?.line;
|
|
122
|
-
|
|
123
|
-
if (!nodeStart || !commentEnd) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// JSDoc should be on the line immediately before (no blank lines)
|
|
128
|
-
// For multi-line comments, check if the last line of the comment is immediately before the node
|
|
129
|
-
return commentEnd === nodeStart - 1;
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error('[require-jsdoc-description] Error in isJSDocOnPreviousLine:', error);
|
|
132
|
-
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Extract description text from JSDoc comment
|
|
139
|
-
*/
|
|
140
|
-
function extractDescription(jsdocComment: any): string {
|
|
141
|
-
try {
|
|
142
|
-
if (!jsdocComment.value) {
|
|
143
|
-
return '';
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// The comment.value contains the text between /** and */
|
|
147
|
-
// It may start with * and include newlines with * at the beginning of each line
|
|
148
|
-
const lines = jsdocComment.value.split('\n');
|
|
149
|
-
let description = '';
|
|
150
|
-
|
|
151
|
-
for (const line of lines) {
|
|
152
|
-
// Remove leading whitespace and * character (JSDoc format: " * text" or "* text")
|
|
153
|
-
// Handle both formats: " * text" and "* text" and " *text"
|
|
154
|
-
const cleanLine = line.replace(/^\s*\*+\s?/, '').trim();
|
|
155
|
-
|
|
156
|
-
// Skip empty lines (after removing * and whitespace)
|
|
157
|
-
if (!cleanLine) {
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Stop at @tags (JSDoc tags like @param, @returns, etc.)
|
|
162
|
-
if (cleanLine.startsWith('@')) {
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// Add to description with space separator
|
|
167
|
-
if (description) {
|
|
168
|
-
description += ' ';
|
|
169
|
-
}
|
|
170
|
-
description += cleanLine;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return description.trim();
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.error('[require-jsdoc-description] Error in extractDescription:', error);
|
|
176
|
-
|
|
177
|
-
return '';
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Egy node "tartalmi" leading-comment-jenek megtalalasahoz fel kell masznunk a
|
|
183
|
-
* szintaktikai burkokon, mert a JSDoc nem mindig a belso deklaracio ele kerul:
|
|
184
|
-
* - `export class X` / `export interface Y` / `export type Z` / `export enum E`:
|
|
185
|
-
* a JSDoc az ExportNamedDeclaration / ExportDefaultDeclaration ELE kerul, NEM
|
|
186
|
-
* a belso deklaracio-node ele.
|
|
187
|
-
* - arrow-const (`export const fn = () => {}`): a JSDoc a VariableDeclaration
|
|
188
|
-
* (ill. annak export-wrapper-e) ele kerul, nem a VariableDeclarator ele.
|
|
189
|
-
* - dekorator (pl. `@Component()`-tel annotalt class): a JSDoc a dekorator-stack
|
|
190
|
-
* fole kerul, igy a belso class-node kozvetlen elott nincs comment.
|
|
191
|
-
* Ez a helper visszaadja azokat a "horgony" node-okat, amik ele a
|
|
192
|
-
* getCommentsBefore-t erdemes hivni — sorrendben a legkulso burkolotol befele.
|
|
193
|
-
*/
|
|
194
|
-
function getCommentAnchors(node: any): any[] {
|
|
195
|
-
const anchors: any[] = [ node ];
|
|
196
|
-
|
|
197
|
-
try {
|
|
198
|
-
let climbed: any = node;
|
|
199
|
-
|
|
200
|
-
// arrow-const: VariableDeclarator → VariableDeclaration
|
|
201
|
-
if (climbed.parent && climbed.parent.type === 'VariableDeclaration') {
|
|
202
|
-
climbed = climbed.parent;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// export-wrapper: <decl> → ExportNamedDeclaration / ExportDefaultDeclaration
|
|
206
|
-
// (csak akkor maszunk fel, ha a wrapper tenyleg EZT a node-ot exportalja)
|
|
207
|
-
if (climbed.parent
|
|
208
|
-
&& (climbed.parent.type === 'ExportNamedDeclaration' || climbed.parent.type === 'ExportDefaultDeclaration')
|
|
209
|
-
&& climbed.parent.declaration === climbed) {
|
|
210
|
-
climbed = climbed.parent;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// a legkulso burkolo elore kerul (ott van a tartalmi JSDoc tipikusan)
|
|
214
|
-
if (climbed !== node) {
|
|
215
|
-
anchors.unshift(climbed);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// dekorator: a JSDoc a dekorator-stack fole is kerulhet
|
|
219
|
-
if (Array.isArray(node.decorators) && node.decorators.length > 0) {
|
|
220
|
-
anchors.push(node.decorators[0]);
|
|
221
|
-
}
|
|
222
|
-
} catch (error) {
|
|
223
|
-
console.error('[require-jsdoc-description] Error in getCommentAnchors:', error);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return anchors;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Az osszes szoba johheto "horgony" elott levo comment osszegyujtese. A
|
|
231
|
-
* getLeadingComments a getCommentsBefore-t MINDEN anchor-ra meghivja es
|
|
232
|
-
* osszefuzi az eredmenyt — a hasValidJSDoc utana az elso JSDoc-blokkot
|
|
233
|
-
* keresi ki belole, igy mindegy hogy melyik burok elott talalja meg.
|
|
234
|
-
*/
|
|
235
|
-
function getLeadingComments(node: any): any[] {
|
|
236
|
-
const all: any[] = [];
|
|
237
|
-
|
|
238
|
-
for (const anchor of getCommentAnchors(node)) {
|
|
239
|
-
const before: any[] = sourceCode.getCommentsBefore(anchor) || [];
|
|
240
|
-
|
|
241
|
-
for (const comment of before) {
|
|
242
|
-
all.push(comment);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
return all;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Check if node has valid JSDoc comment
|
|
251
|
-
*/
|
|
252
|
-
function hasValidJSDoc(node: any): { hasJSDoc: boolean; isValid: boolean; isOnPreviousLine: boolean } {
|
|
253
|
-
try {
|
|
254
|
-
const comments = getLeadingComments(node);
|
|
255
|
-
|
|
256
|
-
// Find JSDoc comment (/** ... */)
|
|
257
|
-
// JSDoc comments are Block comments where the value (content between /** and */) starts with *
|
|
258
|
-
const jsdocComment = comments.find((comment: any) => {
|
|
259
|
-
if (comment.type !== 'Block') {
|
|
260
|
-
return false;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const value = comment.value || '';
|
|
264
|
-
|
|
265
|
-
// JSDoc comment value should start with * (the first character after /**)
|
|
266
|
-
// Handle cases where there might be whitespace before the *
|
|
267
|
-
return value.trim().startsWith('*');
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
if (!jsdocComment) {
|
|
271
|
-
return { hasJSDoc: false, isValid: false, isOnPreviousLine: false };
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const isOnPreviousLine: boolean = isJSDocOnPreviousLine(node, jsdocComment);
|
|
275
|
-
const description: string = extractDescription(jsdocComment);
|
|
276
|
-
const isValid: boolean = description.length > 0;
|
|
277
|
-
|
|
278
|
-
return { hasJSDoc: true, isValid, isOnPreviousLine };
|
|
279
|
-
} catch (error) {
|
|
280
|
-
console.error('[require-jsdoc-description] Error in hasValidJSDoc:', error);
|
|
281
|
-
|
|
282
|
-
return { hasJSDoc: false, isValid: false, isOnPreviousLine: false };
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Get node name for error messages
|
|
288
|
-
*/
|
|
289
|
-
function getNodeName(node: any): string {
|
|
290
|
-
try {
|
|
291
|
-
if (node.id && node.id.name) {
|
|
292
|
-
return node.id.name;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (node.key && node.key.name) {
|
|
296
|
-
return node.key.name;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (node.name && node.name.name) {
|
|
300
|
-
return node.name.name;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return 'anonymous';
|
|
304
|
-
} catch (error) {
|
|
305
|
-
console.error('[require-jsdoc-description] Error in getNodeName:', error);
|
|
306
|
-
|
|
307
|
-
return 'unknown';
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
/**
|
|
312
|
-
* Get node name location for error reporting
|
|
313
|
-
*/
|
|
314
|
-
function getNodeNameLoc(node: any): { start: { line: number; column: number }; end: { line: number; column: number } } | null {
|
|
315
|
-
try {
|
|
316
|
-
// For MethodDefinition, use key.loc
|
|
317
|
-
if (node.key && node.key.loc) {
|
|
318
|
-
return node.key.loc;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// For declarations with id (FunctionDeclaration, ClassDeclaration, etc.)
|
|
322
|
-
if (node.id && node.id.loc) {
|
|
323
|
-
return node.id.loc;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// For VariableDeclarator
|
|
327
|
-
if (node.id && node.id.loc) {
|
|
328
|
-
return node.id.loc;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// Fallback to node location if name location not found
|
|
332
|
-
if (node.loc) {
|
|
333
|
-
return node.loc;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
return null;
|
|
337
|
-
} catch (error) {
|
|
338
|
-
console.error('[require-jsdoc-description] Error in getNodeNameLoc:', error);
|
|
339
|
-
|
|
340
|
-
return null;
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Get node type for error messages
|
|
346
|
-
*/
|
|
347
|
-
function getNodeType(node: any): string {
|
|
348
|
-
try {
|
|
349
|
-
// Check if it's a VariableDeclarator with function expression
|
|
350
|
-
if (node.type === 'VariableDeclarator' && node.init) {
|
|
351
|
-
if (node.init.type === 'FunctionExpression' || node.init.type === 'ArrowFunctionExpression') {
|
|
352
|
-
return 'Function';
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
switch (node.type) {
|
|
357
|
-
case 'FunctionDeclaration':
|
|
358
|
-
return 'Function';
|
|
359
|
-
case 'FunctionExpression':
|
|
360
|
-
return 'Function';
|
|
361
|
-
case 'ArrowFunctionExpression':
|
|
362
|
-
return 'Function';
|
|
363
|
-
case 'ClassDeclaration':
|
|
364
|
-
return 'Class';
|
|
365
|
-
case 'TSInterfaceDeclaration':
|
|
366
|
-
return 'Interface';
|
|
367
|
-
case 'TSEnumDeclaration':
|
|
368
|
-
return 'Enum';
|
|
369
|
-
case 'TSTypeAliasDeclaration':
|
|
370
|
-
return 'Type';
|
|
371
|
-
case 'MethodDefinition':
|
|
372
|
-
return 'Method';
|
|
373
|
-
case 'PropertyDefinition':
|
|
374
|
-
return 'Property';
|
|
375
|
-
|
|
376
|
-
default:
|
|
377
|
-
return 'Declaration';
|
|
378
|
-
}
|
|
379
|
-
} catch (error) {
|
|
380
|
-
console.error('[require-jsdoc-description] Error in getNodeType:', error);
|
|
381
|
-
|
|
382
|
-
return 'Declaration';
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Check if node should be documented based on scope
|
|
388
|
-
*/
|
|
389
|
-
function shouldBeDocumented(node: any): boolean {
|
|
390
|
-
try {
|
|
391
|
-
if (scope === 'all') {
|
|
392
|
-
return true;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
if (scope === 'public') {
|
|
396
|
-
return isPublic(node);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
return true; // Default to requiring documentation
|
|
400
|
-
} catch (error) {
|
|
401
|
-
console.error('[require-jsdoc-description] Error in shouldBeDocumented:', error);
|
|
402
|
-
|
|
403
|
-
return true;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Report missing or invalid JSDoc
|
|
409
|
-
*/
|
|
410
|
-
function reportJSDocIssue(
|
|
411
|
-
node: any,
|
|
412
|
-
jsdocResult: { hasJSDoc: boolean; isValid: boolean; isOnPreviousLine: boolean }
|
|
413
|
-
): void {
|
|
414
|
-
try {
|
|
415
|
-
const name = getNodeName(node);
|
|
416
|
-
const type = getNodeType(node);
|
|
417
|
-
const nameLoc = getNodeNameLoc(node);
|
|
418
|
-
|
|
419
|
-
// Fallback to node location if name location not found
|
|
420
|
-
const reportLoc = nameLoc || node.loc;
|
|
421
|
-
|
|
422
|
-
if (!jsdocResult.hasJSDoc) {
|
|
423
|
-
context.report({
|
|
424
|
-
loc: reportLoc,
|
|
425
|
-
messageId: 'missingJSDoc',
|
|
426
|
-
data: { type, name },
|
|
427
|
-
});
|
|
428
|
-
} else if (!jsdocResult.isOnPreviousLine) {
|
|
429
|
-
context.report({
|
|
430
|
-
loc: reportLoc,
|
|
431
|
-
messageId: 'invalidJSDocPosition',
|
|
432
|
-
data: { type, name },
|
|
433
|
-
});
|
|
434
|
-
} else if (!jsdocResult.isValid) {
|
|
435
|
-
context.report({
|
|
436
|
-
loc: reportLoc,
|
|
437
|
-
messageId: 'emptyJSDoc',
|
|
438
|
-
data: { type, name },
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
} catch (error) {
|
|
442
|
-
console.error('[require-jsdoc-description] Error in reportJSDocIssue:', error);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
// Function declarations
|
|
448
|
-
FunctionDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
449
|
-
try {
|
|
450
|
-
if (!shouldBeDocumented(node)) {
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
455
|
-
|
|
456
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
457
|
-
reportJSDocIssue(node, jsdocResult);
|
|
458
|
-
}
|
|
459
|
-
} catch (error) {
|
|
460
|
-
console.error('[require-jsdoc-description] Error in FunctionDeclaration visitor:', error);
|
|
461
|
-
}
|
|
462
|
-
},
|
|
463
|
-
|
|
464
|
-
// Function expressions (when assigned to variables)
|
|
465
|
-
VariableDeclarator(node: any, _context?: any, _sourceCode?: any): void {
|
|
466
|
-
try {
|
|
467
|
-
if (node.init &&
|
|
468
|
-
(node.init.type === 'FunctionExpression' || node.init.type === 'ArrowFunctionExpression')) {
|
|
469
|
-
if (!shouldBeDocumented(node)) {
|
|
470
|
-
return;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
474
|
-
|
|
475
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
476
|
-
reportJSDocIssue(node, jsdocResult);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
} catch (error) {
|
|
480
|
-
console.error('[require-jsdoc-description] Error in VariableDeclarator visitor:', error);
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
|
|
484
|
-
// Class declarations
|
|
485
|
-
ClassDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
486
|
-
try {
|
|
487
|
-
if (!shouldBeDocumented(node)) {
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
492
|
-
|
|
493
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
494
|
-
reportJSDocIssue(node, jsdocResult);
|
|
495
|
-
}
|
|
496
|
-
} catch (error) {
|
|
497
|
-
console.error('[require-jsdoc-description] Error in ClassDeclaration visitor:', error);
|
|
498
|
-
}
|
|
499
|
-
},
|
|
500
|
-
|
|
501
|
-
// Interface declarations
|
|
502
|
-
TSInterfaceDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
503
|
-
try {
|
|
504
|
-
if (!shouldBeDocumented(node)) {
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
509
|
-
|
|
510
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
511
|
-
reportJSDocIssue(node, jsdocResult);
|
|
512
|
-
}
|
|
513
|
-
} catch (error) {
|
|
514
|
-
console.error('[require-jsdoc-description] Error in TSInterfaceDeclaration visitor:', error);
|
|
515
|
-
}
|
|
516
|
-
},
|
|
517
|
-
|
|
518
|
-
// Enum declarations
|
|
519
|
-
TSEnumDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
520
|
-
try {
|
|
521
|
-
if (!shouldBeDocumented(node)) {
|
|
522
|
-
return;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
526
|
-
|
|
527
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
528
|
-
reportJSDocIssue(node, jsdocResult);
|
|
529
|
-
}
|
|
530
|
-
} catch (error) {
|
|
531
|
-
console.error('[require-jsdoc-description] Error in TSEnumDeclaration visitor:', error);
|
|
532
|
-
}
|
|
533
|
-
},
|
|
534
|
-
|
|
535
|
-
// Type alias declarations (CCAP-megerősítés: exportált `type X = …` kötelező JSDoc).
|
|
536
|
-
TSTypeAliasDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
537
|
-
try {
|
|
538
|
-
if (!shouldBeDocumented(node)) {
|
|
539
|
-
return;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
543
|
-
|
|
544
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
545
|
-
reportJSDocIssue(node, jsdocResult);
|
|
546
|
-
}
|
|
547
|
-
} catch (error) {
|
|
548
|
-
console.error('[require-jsdoc-description] Error in TSTypeAliasDeclaration visitor:', error);
|
|
549
|
-
}
|
|
550
|
-
},
|
|
551
|
-
|
|
552
|
-
// Class methods
|
|
553
|
-
MethodDefinition(node: any, _context?: any, _sourceCode?: any): void {
|
|
554
|
-
try {
|
|
555
|
-
if (!shouldBeDocumented(node)) {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
const jsdocResult = hasValidJSDoc(node);
|
|
560
|
-
|
|
561
|
-
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
562
|
-
reportJSDocIssue(node, jsdocResult);
|
|
563
|
-
}
|
|
564
|
-
} catch (error) {
|
|
565
|
-
console.error('[require-jsdoc-description] Error in MethodDefinition visitor:', error);
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
};
|
|
569
|
-
},
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
export default rule;
|
|
1
|
+
import { Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
interface RuleOptions {
|
|
4
|
+
scope?: 'public' | 'all';
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const rule: Rule.RuleModule = {
|
|
8
|
+
meta: {
|
|
9
|
+
type: 'suggestion',
|
|
10
|
+
docs: {
|
|
11
|
+
description: 'Require JSDoc comment blocks with description for functions, classes, interfaces, and enums',
|
|
12
|
+
recommended: true,
|
|
13
|
+
},
|
|
14
|
+
schema: [
|
|
15
|
+
{
|
|
16
|
+
type: 'object',
|
|
17
|
+
properties: {
|
|
18
|
+
scope: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
enum: [ 'public', 'all' ],
|
|
21
|
+
default: 'public',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
additionalProperties: false,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
messages: {
|
|
28
|
+
missingJSDoc: '{{type}} "{{name}}" must have a JSDoc comment with description.',
|
|
29
|
+
emptyJSDoc: '{{type}} "{{name}}" JSDoc comment must contain description text.',
|
|
30
|
+
invalidJSDocPosition: '{{type}} "{{name}}" JSDoc comment must be on the line immediately before.',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
create(context) {
|
|
34
|
+
const options: RuleOptions = context.options[0] || {};
|
|
35
|
+
const scope: string = options.scope || 'public';
|
|
36
|
+
const sourceCode: any = context.sourceCode;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a node is public/exported (should be documented)
|
|
40
|
+
*/
|
|
41
|
+
function isPublic(node: any): boolean {
|
|
42
|
+
try {
|
|
43
|
+
// Check if node itself is exported
|
|
44
|
+
if (node.parent && node.parent.type === 'ExportNamedDeclaration') {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (node.parent && node.parent.type === 'ExportDefaultDeclaration') {
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Check for export keyword in declaration
|
|
53
|
+
if (node.type === 'FunctionDeclaration' && node.parent &&
|
|
54
|
+
node.parent.type === 'Program' && node.id) {
|
|
55
|
+
// Top-level function - check if exported
|
|
56
|
+
const program: any = node.parent;
|
|
57
|
+
|
|
58
|
+
if (program.body && Array.isArray(program.body)) {
|
|
59
|
+
for (const statement of program.body) {
|
|
60
|
+
if (statement.type === 'ExportNamedDeclaration' &&
|
|
61
|
+
statement.declaration === node) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// For class members, check accessibility modifiers
|
|
69
|
+
if (node.type === 'MethodDefinition' || node.type === 'PropertyDefinition') {
|
|
70
|
+
// If has private modifier, it's not public
|
|
71
|
+
if (node.accessibility === 'private') {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// If has protected modifier, it's not public
|
|
76
|
+
if (node.accessibility === 'protected') {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Default to public if no modifiers
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// For top-level declarations, check if they're exported
|
|
85
|
+
if (node.parent && node.parent.type === 'Program') {
|
|
86
|
+
// Check if this declaration is exported
|
|
87
|
+
const program: any = node.parent;
|
|
88
|
+
|
|
89
|
+
if (program.body && Array.isArray(program.body)) {
|
|
90
|
+
for (const statement of program.body) {
|
|
91
|
+
if (statement.type === 'ExportNamedDeclaration' &&
|
|
92
|
+
statement.declaration === node) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (statement.type === 'ExportDefaultDeclaration' &&
|
|
97
|
+
statement.declaration === node) {
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return false; // Not exported
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Default to public for other cases
|
|
107
|
+
return true;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
console.error('[require-jsdoc-description] Error in isPublic:', error);
|
|
110
|
+
|
|
111
|
+
return true; // Default to requiring documentation
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if JSDoc comment is on the line immediately before the node
|
|
117
|
+
*/
|
|
118
|
+
function isJSDocOnPreviousLine(node: any, jsdocComment: any): boolean {
|
|
119
|
+
try {
|
|
120
|
+
const nodeStart: number | undefined = node.loc?.start?.line;
|
|
121
|
+
const commentEnd: number | undefined = jsdocComment.loc?.end?.line;
|
|
122
|
+
|
|
123
|
+
if (!nodeStart || !commentEnd) {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// JSDoc should be on the line immediately before (no blank lines)
|
|
128
|
+
// For multi-line comments, check if the last line of the comment is immediately before the node
|
|
129
|
+
return commentEnd === nodeStart - 1;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('[require-jsdoc-description] Error in isJSDocOnPreviousLine:', error);
|
|
132
|
+
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Extract description text from JSDoc comment
|
|
139
|
+
*/
|
|
140
|
+
function extractDescription(jsdocComment: any): string {
|
|
141
|
+
try {
|
|
142
|
+
if (!jsdocComment.value) {
|
|
143
|
+
return '';
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// The comment.value contains the text between /** and */
|
|
147
|
+
// It may start with * and include newlines with * at the beginning of each line
|
|
148
|
+
const lines = jsdocComment.value.split('\n');
|
|
149
|
+
let description = '';
|
|
150
|
+
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
// Remove leading whitespace and * character (JSDoc format: " * text" or "* text")
|
|
153
|
+
// Handle both formats: " * text" and "* text" and " *text"
|
|
154
|
+
const cleanLine = line.replace(/^\s*\*+\s?/, '').trim();
|
|
155
|
+
|
|
156
|
+
// Skip empty lines (after removing * and whitespace)
|
|
157
|
+
if (!cleanLine) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Stop at @tags (JSDoc tags like @param, @returns, etc.)
|
|
162
|
+
if (cleanLine.startsWith('@')) {
|
|
163
|
+
break;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Add to description with space separator
|
|
167
|
+
if (description) {
|
|
168
|
+
description += ' ';
|
|
169
|
+
}
|
|
170
|
+
description += cleanLine;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return description.trim();
|
|
174
|
+
} catch (error) {
|
|
175
|
+
console.error('[require-jsdoc-description] Error in extractDescription:', error);
|
|
176
|
+
|
|
177
|
+
return '';
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Egy node "tartalmi" leading-comment-jenek megtalalasahoz fel kell masznunk a
|
|
183
|
+
* szintaktikai burkokon, mert a JSDoc nem mindig a belso deklaracio ele kerul:
|
|
184
|
+
* - `export class X` / `export interface Y` / `export type Z` / `export enum E`:
|
|
185
|
+
* a JSDoc az ExportNamedDeclaration / ExportDefaultDeclaration ELE kerul, NEM
|
|
186
|
+
* a belso deklaracio-node ele.
|
|
187
|
+
* - arrow-const (`export const fn = () => {}`): a JSDoc a VariableDeclaration
|
|
188
|
+
* (ill. annak export-wrapper-e) ele kerul, nem a VariableDeclarator ele.
|
|
189
|
+
* - dekorator (pl. `@Component()`-tel annotalt class): a JSDoc a dekorator-stack
|
|
190
|
+
* fole kerul, igy a belso class-node kozvetlen elott nincs comment.
|
|
191
|
+
* Ez a helper visszaadja azokat a "horgony" node-okat, amik ele a
|
|
192
|
+
* getCommentsBefore-t erdemes hivni — sorrendben a legkulso burkolotol befele.
|
|
193
|
+
*/
|
|
194
|
+
function getCommentAnchors(node: any): any[] {
|
|
195
|
+
const anchors: any[] = [ node ];
|
|
196
|
+
|
|
197
|
+
try {
|
|
198
|
+
let climbed: any = node;
|
|
199
|
+
|
|
200
|
+
// arrow-const: VariableDeclarator → VariableDeclaration
|
|
201
|
+
if (climbed.parent && climbed.parent.type === 'VariableDeclaration') {
|
|
202
|
+
climbed = climbed.parent;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// export-wrapper: <decl> → ExportNamedDeclaration / ExportDefaultDeclaration
|
|
206
|
+
// (csak akkor maszunk fel, ha a wrapper tenyleg EZT a node-ot exportalja)
|
|
207
|
+
if (climbed.parent
|
|
208
|
+
&& (climbed.parent.type === 'ExportNamedDeclaration' || climbed.parent.type === 'ExportDefaultDeclaration')
|
|
209
|
+
&& climbed.parent.declaration === climbed) {
|
|
210
|
+
climbed = climbed.parent;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// a legkulso burkolo elore kerul (ott van a tartalmi JSDoc tipikusan)
|
|
214
|
+
if (climbed !== node) {
|
|
215
|
+
anchors.unshift(climbed);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// dekorator: a JSDoc a dekorator-stack fole is kerulhet
|
|
219
|
+
if (Array.isArray(node.decorators) && node.decorators.length > 0) {
|
|
220
|
+
anchors.push(node.decorators[0]);
|
|
221
|
+
}
|
|
222
|
+
} catch (error) {
|
|
223
|
+
console.error('[require-jsdoc-description] Error in getCommentAnchors:', error);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return anchors;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Az osszes szoba johheto "horgony" elott levo comment osszegyujtese. A
|
|
231
|
+
* getLeadingComments a getCommentsBefore-t MINDEN anchor-ra meghivja es
|
|
232
|
+
* osszefuzi az eredmenyt — a hasValidJSDoc utana az elso JSDoc-blokkot
|
|
233
|
+
* keresi ki belole, igy mindegy hogy melyik burok elott talalja meg.
|
|
234
|
+
*/
|
|
235
|
+
function getLeadingComments(node: any): any[] {
|
|
236
|
+
const all: any[] = [];
|
|
237
|
+
|
|
238
|
+
for (const anchor of getCommentAnchors(node)) {
|
|
239
|
+
const before: any[] = sourceCode.getCommentsBefore(anchor) || [];
|
|
240
|
+
|
|
241
|
+
for (const comment of before) {
|
|
242
|
+
all.push(comment);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return all;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Check if node has valid JSDoc comment
|
|
251
|
+
*/
|
|
252
|
+
function hasValidJSDoc(node: any): { hasJSDoc: boolean; isValid: boolean; isOnPreviousLine: boolean } {
|
|
253
|
+
try {
|
|
254
|
+
const comments = getLeadingComments(node);
|
|
255
|
+
|
|
256
|
+
// Find JSDoc comment (/** ... */)
|
|
257
|
+
// JSDoc comments are Block comments where the value (content between /** and */) starts with *
|
|
258
|
+
const jsdocComment = comments.find((comment: any) => {
|
|
259
|
+
if (comment.type !== 'Block') {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const value = comment.value || '';
|
|
264
|
+
|
|
265
|
+
// JSDoc comment value should start with * (the first character after /**)
|
|
266
|
+
// Handle cases where there might be whitespace before the *
|
|
267
|
+
return value.trim().startsWith('*');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
if (!jsdocComment) {
|
|
271
|
+
return { hasJSDoc: false, isValid: false, isOnPreviousLine: false };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const isOnPreviousLine: boolean = isJSDocOnPreviousLine(node, jsdocComment);
|
|
275
|
+
const description: string = extractDescription(jsdocComment);
|
|
276
|
+
const isValid: boolean = description.length > 0;
|
|
277
|
+
|
|
278
|
+
return { hasJSDoc: true, isValid, isOnPreviousLine };
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('[require-jsdoc-description] Error in hasValidJSDoc:', error);
|
|
281
|
+
|
|
282
|
+
return { hasJSDoc: false, isValid: false, isOnPreviousLine: false };
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get node name for error messages
|
|
288
|
+
*/
|
|
289
|
+
function getNodeName(node: any): string {
|
|
290
|
+
try {
|
|
291
|
+
if (node.id && node.id.name) {
|
|
292
|
+
return node.id.name;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (node.key && node.key.name) {
|
|
296
|
+
return node.key.name;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (node.name && node.name.name) {
|
|
300
|
+
return node.name.name;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return 'anonymous';
|
|
304
|
+
} catch (error) {
|
|
305
|
+
console.error('[require-jsdoc-description] Error in getNodeName:', error);
|
|
306
|
+
|
|
307
|
+
return 'unknown';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Get node name location for error reporting
|
|
313
|
+
*/
|
|
314
|
+
function getNodeNameLoc(node: any): { start: { line: number; column: number }; end: { line: number; column: number } } | null {
|
|
315
|
+
try {
|
|
316
|
+
// For MethodDefinition, use key.loc
|
|
317
|
+
if (node.key && node.key.loc) {
|
|
318
|
+
return node.key.loc;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// For declarations with id (FunctionDeclaration, ClassDeclaration, etc.)
|
|
322
|
+
if (node.id && node.id.loc) {
|
|
323
|
+
return node.id.loc;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// For VariableDeclarator
|
|
327
|
+
if (node.id && node.id.loc) {
|
|
328
|
+
return node.id.loc;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Fallback to node location if name location not found
|
|
332
|
+
if (node.loc) {
|
|
333
|
+
return node.loc;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return null;
|
|
337
|
+
} catch (error) {
|
|
338
|
+
console.error('[require-jsdoc-description] Error in getNodeNameLoc:', error);
|
|
339
|
+
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Get node type for error messages
|
|
346
|
+
*/
|
|
347
|
+
function getNodeType(node: any): string {
|
|
348
|
+
try {
|
|
349
|
+
// Check if it's a VariableDeclarator with function expression
|
|
350
|
+
if (node.type === 'VariableDeclarator' && node.init) {
|
|
351
|
+
if (node.init.type === 'FunctionExpression' || node.init.type === 'ArrowFunctionExpression') {
|
|
352
|
+
return 'Function';
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
switch (node.type) {
|
|
357
|
+
case 'FunctionDeclaration':
|
|
358
|
+
return 'Function';
|
|
359
|
+
case 'FunctionExpression':
|
|
360
|
+
return 'Function';
|
|
361
|
+
case 'ArrowFunctionExpression':
|
|
362
|
+
return 'Function';
|
|
363
|
+
case 'ClassDeclaration':
|
|
364
|
+
return 'Class';
|
|
365
|
+
case 'TSInterfaceDeclaration':
|
|
366
|
+
return 'Interface';
|
|
367
|
+
case 'TSEnumDeclaration':
|
|
368
|
+
return 'Enum';
|
|
369
|
+
case 'TSTypeAliasDeclaration':
|
|
370
|
+
return 'Type';
|
|
371
|
+
case 'MethodDefinition':
|
|
372
|
+
return 'Method';
|
|
373
|
+
case 'PropertyDefinition':
|
|
374
|
+
return 'Property';
|
|
375
|
+
|
|
376
|
+
default:
|
|
377
|
+
return 'Declaration';
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
console.error('[require-jsdoc-description] Error in getNodeType:', error);
|
|
381
|
+
|
|
382
|
+
return 'Declaration';
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Check if node should be documented based on scope
|
|
388
|
+
*/
|
|
389
|
+
function shouldBeDocumented(node: any): boolean {
|
|
390
|
+
try {
|
|
391
|
+
if (scope === 'all') {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
if (scope === 'public') {
|
|
396
|
+
return isPublic(node);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return true; // Default to requiring documentation
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.error('[require-jsdoc-description] Error in shouldBeDocumented:', error);
|
|
402
|
+
|
|
403
|
+
return true;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Report missing or invalid JSDoc
|
|
409
|
+
*/
|
|
410
|
+
function reportJSDocIssue(
|
|
411
|
+
node: any,
|
|
412
|
+
jsdocResult: { hasJSDoc: boolean; isValid: boolean; isOnPreviousLine: boolean }
|
|
413
|
+
): void {
|
|
414
|
+
try {
|
|
415
|
+
const name = getNodeName(node);
|
|
416
|
+
const type = getNodeType(node);
|
|
417
|
+
const nameLoc = getNodeNameLoc(node);
|
|
418
|
+
|
|
419
|
+
// Fallback to node location if name location not found
|
|
420
|
+
const reportLoc = nameLoc || node.loc;
|
|
421
|
+
|
|
422
|
+
if (!jsdocResult.hasJSDoc) {
|
|
423
|
+
context.report({
|
|
424
|
+
loc: reportLoc,
|
|
425
|
+
messageId: 'missingJSDoc',
|
|
426
|
+
data: { type, name },
|
|
427
|
+
});
|
|
428
|
+
} else if (!jsdocResult.isOnPreviousLine) {
|
|
429
|
+
context.report({
|
|
430
|
+
loc: reportLoc,
|
|
431
|
+
messageId: 'invalidJSDocPosition',
|
|
432
|
+
data: { type, name },
|
|
433
|
+
});
|
|
434
|
+
} else if (!jsdocResult.isValid) {
|
|
435
|
+
context.report({
|
|
436
|
+
loc: reportLoc,
|
|
437
|
+
messageId: 'emptyJSDoc',
|
|
438
|
+
data: { type, name },
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
} catch (error) {
|
|
442
|
+
console.error('[require-jsdoc-description] Error in reportJSDocIssue:', error);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
// Function declarations
|
|
448
|
+
FunctionDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
449
|
+
try {
|
|
450
|
+
if (!shouldBeDocumented(node)) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
455
|
+
|
|
456
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
457
|
+
reportJSDocIssue(node, jsdocResult);
|
|
458
|
+
}
|
|
459
|
+
} catch (error) {
|
|
460
|
+
console.error('[require-jsdoc-description] Error in FunctionDeclaration visitor:', error);
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
|
|
464
|
+
// Function expressions (when assigned to variables)
|
|
465
|
+
VariableDeclarator(node: any, _context?: any, _sourceCode?: any): void {
|
|
466
|
+
try {
|
|
467
|
+
if (node.init &&
|
|
468
|
+
(node.init.type === 'FunctionExpression' || node.init.type === 'ArrowFunctionExpression')) {
|
|
469
|
+
if (!shouldBeDocumented(node)) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
474
|
+
|
|
475
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
476
|
+
reportJSDocIssue(node, jsdocResult);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error('[require-jsdoc-description] Error in VariableDeclarator visitor:', error);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
|
|
484
|
+
// Class declarations
|
|
485
|
+
ClassDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
486
|
+
try {
|
|
487
|
+
if (!shouldBeDocumented(node)) {
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
492
|
+
|
|
493
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
494
|
+
reportJSDocIssue(node, jsdocResult);
|
|
495
|
+
}
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.error('[require-jsdoc-description] Error in ClassDeclaration visitor:', error);
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
|
|
501
|
+
// Interface declarations
|
|
502
|
+
TSInterfaceDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
503
|
+
try {
|
|
504
|
+
if (!shouldBeDocumented(node)) {
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
509
|
+
|
|
510
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
511
|
+
reportJSDocIssue(node, jsdocResult);
|
|
512
|
+
}
|
|
513
|
+
} catch (error) {
|
|
514
|
+
console.error('[require-jsdoc-description] Error in TSInterfaceDeclaration visitor:', error);
|
|
515
|
+
}
|
|
516
|
+
},
|
|
517
|
+
|
|
518
|
+
// Enum declarations
|
|
519
|
+
TSEnumDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
520
|
+
try {
|
|
521
|
+
if (!shouldBeDocumented(node)) {
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
526
|
+
|
|
527
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
528
|
+
reportJSDocIssue(node, jsdocResult);
|
|
529
|
+
}
|
|
530
|
+
} catch (error) {
|
|
531
|
+
console.error('[require-jsdoc-description] Error in TSEnumDeclaration visitor:', error);
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
// Type alias declarations (CCAP-megerősítés: exportált `type X = …` kötelező JSDoc).
|
|
536
|
+
TSTypeAliasDeclaration(node: any, _context?: any, _sourceCode?: any): void {
|
|
537
|
+
try {
|
|
538
|
+
if (!shouldBeDocumented(node)) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
543
|
+
|
|
544
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
545
|
+
reportJSDocIssue(node, jsdocResult);
|
|
546
|
+
}
|
|
547
|
+
} catch (error) {
|
|
548
|
+
console.error('[require-jsdoc-description] Error in TSTypeAliasDeclaration visitor:', error);
|
|
549
|
+
}
|
|
550
|
+
},
|
|
551
|
+
|
|
552
|
+
// Class methods
|
|
553
|
+
MethodDefinition(node: any, _context?: any, _sourceCode?: any): void {
|
|
554
|
+
try {
|
|
555
|
+
if (!shouldBeDocumented(node)) {
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const jsdocResult = hasValidJSDoc(node);
|
|
560
|
+
|
|
561
|
+
if (!jsdocResult.hasJSDoc || !jsdocResult.isValid || !jsdocResult.isOnPreviousLine) {
|
|
562
|
+
reportJSDocIssue(node, jsdocResult);
|
|
563
|
+
}
|
|
564
|
+
} catch (error) {
|
|
565
|
+
console.error('[require-jsdoc-description] Error in MethodDefinition visitor:', error);
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
};
|
|
569
|
+
},
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
export default rule;
|