eslint-plugin-jsdoc 61.0.1 → 61.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/README.md +8 -0
  2. package/dist/cjs/jsdocUtils.d.ts +8 -0
  3. package/dist/cjs/rules/tsMethodSignatureStyle.d.ts +2 -0
  4. package/dist/cjs/rules/tsNoEmptyObjectType.d.ts +2 -0
  5. package/dist/cjs/rules/tsNoUnnecessaryTemplateExpression.d.ts +2 -0
  6. package/dist/cjs/rules/tsPreferFunctionType.d.ts +2 -0
  7. package/dist/index-cjs.cjs +12 -0
  8. package/dist/index-cjs.cjs.map +1 -1
  9. package/dist/index.cjs +12 -0
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/jsdocUtils.cjs +158 -1
  12. package/dist/jsdocUtils.cjs.map +1 -1
  13. package/dist/jsdocUtils.d.ts +8 -0
  14. package/dist/rules/tsMethodSignatureStyle.cjs +240 -0
  15. package/dist/rules/tsMethodSignatureStyle.cjs.map +1 -0
  16. package/dist/rules/tsMethodSignatureStyle.d.ts +3 -0
  17. package/dist/rules/tsNoEmptyObjectType.cjs +62 -0
  18. package/dist/rules/tsNoEmptyObjectType.cjs.map +1 -0
  19. package/dist/rules/tsNoEmptyObjectType.d.ts +3 -0
  20. package/dist/rules/tsNoUnnecessaryTemplateExpression.cjs +104 -0
  21. package/dist/rules/tsNoUnnecessaryTemplateExpression.cjs.map +1 -0
  22. package/dist/rules/tsNoUnnecessaryTemplateExpression.d.ts +3 -0
  23. package/dist/rules/tsPreferFunctionType.cjs +110 -0
  24. package/dist/rules/tsPreferFunctionType.cjs.map +1 -0
  25. package/dist/rules/tsPreferFunctionType.d.ts +3 -0
  26. package/dist/rules/typeFormatting.cjs +2 -128
  27. package/dist/rules/typeFormatting.cjs.map +1 -1
  28. package/dist/rules/validTypes.cjs +1 -1
  29. package/dist/rules/validTypes.cjs.map +1 -1
  30. package/dist/rules.d.ts +41 -0
  31. package/package.json +3 -3
  32. package/src/index-cjs.js +12 -0
  33. package/src/index.js +12 -0
  34. package/src/jsdocUtils.js +181 -0
  35. package/src/rules/tsMethodSignatureStyle.js +300 -0
  36. package/src/rules/tsNoEmptyObjectType.js +61 -0
  37. package/src/rules/tsNoUnnecessaryTemplateExpression.js +130 -0
  38. package/src/rules/tsPreferFunctionType.js +127 -0
  39. package/src/rules/typeFormatting.js +4 -150
  40. package/src/rules/validTypes.js +1 -1
  41. package/src/rules.d.ts +41 -0
@@ -0,0 +1,130 @@
1
+ import iterateJsdoc from '../iterateJsdoc.js';
2
+ import {
3
+ rewireByParsedType,
4
+ } from '../jsdocUtils.js';
5
+ import {
6
+ parse as parseType,
7
+ traverse,
8
+ } from '@es-joy/jsdoccomment';
9
+
10
+ export default iterateJsdoc(({
11
+ context,
12
+ indent,
13
+ jsdoc,
14
+ settings,
15
+ utils,
16
+ }) => {
17
+ if (settings.mode !== 'typescript') {
18
+ return;
19
+ }
20
+
21
+ const {
22
+ enableFixer = true,
23
+ } = context.options[0] ?? {};
24
+
25
+ /**
26
+ * @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} tag
27
+ */
28
+ const checkType = (tag) => {
29
+ const potentialType = tag.type;
30
+ /** @type {import('jsdoc-type-pratt-parser').RootResult} */
31
+ let parsedType;
32
+ try {
33
+ parsedType = parseType(
34
+ /** @type {string} */ (potentialType), 'typescript',
35
+ );
36
+ } catch {
37
+ return;
38
+ }
39
+
40
+ traverse(parsedType, (nde, parentNode, property, index) => {
41
+ switch (nde.type) {
42
+ case 'JsdocTypeTemplateLiteral': {
43
+ const stringInterpolationIndex = nde.interpolations.findIndex((interpolation) => {
44
+ return interpolation.type === 'JsdocTypeStringValue';
45
+ });
46
+ if (stringInterpolationIndex > -1) {
47
+ utils.reportJSDoc(
48
+ 'Found an unnecessary string literal within a template.',
49
+ tag,
50
+ enableFixer ? () => {
51
+ nde.literals.splice(
52
+ stringInterpolationIndex,
53
+ 2,
54
+ nde.literals[stringInterpolationIndex] +
55
+ /** @type {import('jsdoc-type-pratt-parser').StringValueResult} */
56
+ (nde.interpolations[stringInterpolationIndex]).value +
57
+ nde.literals[stringInterpolationIndex + 1],
58
+ );
59
+
60
+ nde.interpolations.splice(
61
+ stringInterpolationIndex, 1,
62
+ );
63
+
64
+ rewireByParsedType(jsdoc, tag, parsedType, indent);
65
+ } : null,
66
+ );
67
+ } else if (nde.literals.length === 2 && nde.literals[0] === '' &&
68
+ nde.literals[1] === ''
69
+ ) {
70
+ utils.reportJSDoc(
71
+ 'Found a lone template expression within a template.',
72
+ tag,
73
+ enableFixer ? () => {
74
+ const interpolation = nde.interpolations[0];
75
+
76
+ if (parentNode && property) {
77
+ if (typeof index === 'number') {
78
+ // @ts-expect-error Safe
79
+ parentNode[property][index] = interpolation;
80
+ } else {
81
+ // @ts-expect-error Safe
82
+ parentNode[property] = interpolation;
83
+ }
84
+ } else {
85
+ parsedType = interpolation;
86
+ }
87
+
88
+ rewireByParsedType(jsdoc, tag, parsedType, indent);
89
+ } : null,
90
+ );
91
+ }
92
+ }
93
+ }
94
+ });
95
+ };
96
+
97
+ const tags = utils.filterTags(({
98
+ tag,
99
+ }) => {
100
+ return Boolean(tag !== 'import' && utils.tagMightHaveTypePosition(tag));
101
+ });
102
+
103
+ for (const tag of tags) {
104
+ if (tag.type) {
105
+ checkType(tag);
106
+ }
107
+ }
108
+ }, {
109
+ iterateAllJsdocs: true,
110
+ meta: {
111
+ docs: {
112
+ description: 'Catches unnecessary template expressions such as string expressions within a template literal.',
113
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-no-unnecessary-template-expression.md#repos-sticky-header',
114
+ },
115
+ fixable: 'code',
116
+ schema: [
117
+ {
118
+ additionalProperties: false,
119
+ properties: {
120
+ enableFixer: {
121
+ description: 'Whether to enable the fixer. Defaults to `true`.',
122
+ type: 'boolean',
123
+ },
124
+ },
125
+ type: 'object',
126
+ },
127
+ ],
128
+ type: 'suggestion',
129
+ },
130
+ });
@@ -0,0 +1,127 @@
1
+ import iterateJsdoc from '../iterateJsdoc.js';
2
+ import {
3
+ rewireByParsedType,
4
+ } from '../jsdocUtils.js';
5
+ import {
6
+ parse as parseType,
7
+ traverse,
8
+ } from '@es-joy/jsdoccomment';
9
+
10
+ export default iterateJsdoc(({
11
+ context,
12
+ indent,
13
+ jsdoc,
14
+ utils,
15
+ }) => {
16
+ const {
17
+ enableFixer = true,
18
+ } = context.options[0] || {};
19
+
20
+ /**
21
+ * @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} tag
22
+ */
23
+ const checkType = (tag) => {
24
+ const potentialType = tag.type;
25
+
26
+ /** @type {import('jsdoc-type-pratt-parser').RootResult} */
27
+ let parsedType;
28
+ try {
29
+ parsedType = parseType(
30
+ /** @type {string} */ (potentialType), 'typescript',
31
+ );
32
+ } catch {
33
+ return;
34
+ }
35
+
36
+ traverse(parsedType, (nde, parentNode) => {
37
+ // @ts-expect-error Adding our own property for use below
38
+ nde.parentNode = parentNode;
39
+ });
40
+
41
+ traverse(parsedType, (nde, parentNode, property, index) => {
42
+ switch (nde.type) {
43
+ case 'JsdocTypeCallSignature': {
44
+ const object = /** @type {import('jsdoc-type-pratt-parser').ObjectResult} */ (
45
+ parentNode
46
+ );
47
+ if (typeof index === 'number' && object.elements.length === 1) {
48
+ utils.reportJSDoc(
49
+ 'Call signature found; function type preferred.',
50
+ tag,
51
+ enableFixer ? () => {
52
+ const func = /** @type {import('jsdoc-type-pratt-parser').FunctionResult} */ ({
53
+ arrow: true,
54
+ constructor: false,
55
+ meta: /** @type {Required<import('jsdoc-type-pratt-parser').MethodSignatureResult['meta']>} */ (
56
+ nde.meta
57
+ ),
58
+ parameters: nde.parameters,
59
+ parenthesis: true,
60
+ returnType: nde.returnType,
61
+ type: 'JsdocTypeFunction',
62
+ typeParameters: nde.typeParameters,
63
+ });
64
+
65
+ if (property && 'parentNode' in object && object.parentNode) {
66
+ if (typeof object.parentNode === 'object' &&
67
+ 'elements' in object.parentNode &&
68
+ Array.isArray(object.parentNode.elements)
69
+ ) {
70
+ const idx = object.parentNode.elements.indexOf(object);
71
+ object.parentNode.elements[idx] = func;
72
+ /* c8 ignore next 6 -- Guard */
73
+ } else {
74
+ throw new Error(
75
+ // @ts-expect-error Ok
76
+ `Rule currently unable to handle type ${object.parentNode.type}`,
77
+ );
78
+ }
79
+ } else {
80
+ parsedType = func;
81
+ }
82
+
83
+ rewireByParsedType(jsdoc, tag, parsedType, indent);
84
+ } : null,
85
+ );
86
+ }
87
+
88
+ break;
89
+ }
90
+ }
91
+ });
92
+ };
93
+
94
+ const tags = utils.filterTags(({
95
+ tag,
96
+ }) => {
97
+ return Boolean(tag !== 'import' && utils.tagMightHaveTypePosition(tag));
98
+ });
99
+
100
+ for (const tag of tags) {
101
+ if (tag.type) {
102
+ checkType(tag);
103
+ }
104
+ }
105
+ }, {
106
+ iterateAllJsdocs: true,
107
+ meta: {
108
+ docs: {
109
+ description: 'Prefers function types over call signatures when there are no other properties.',
110
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-prefer-function-type.md#repos-sticky-header',
111
+ },
112
+ fixable: 'code',
113
+ schema: [
114
+ {
115
+ additionalProperties: false,
116
+ properties: {
117
+ enableFixer: {
118
+ description: 'Whether to enable the fixer or not',
119
+ type: 'boolean',
120
+ },
121
+ },
122
+ type: 'object',
123
+ },
124
+ ],
125
+ type: 'suggestion',
126
+ },
127
+ });
@@ -1,4 +1,7 @@
1
1
  import iterateJsdoc from '../iterateJsdoc.js';
2
+ import {
3
+ rewireByParsedType,
4
+ } from '../jsdocUtils.js';
2
5
  import {
3
6
  parse as parseType,
4
7
  stringify,
@@ -67,156 +70,7 @@ export default iterateJsdoc(({
67
70
  }
68
71
 
69
72
  const fix = () => {
70
- const typeLines = stringify(parsedType).split('\n');
71
- const firstTypeLine = typeLines.shift();
72
- const lastTypeLine = typeLines.pop();
73
-
74
- const beginNameOrDescIdx = tag.source.findIndex(({
75
- tokens,
76
- }) => {
77
- return tokens.name || tokens.description;
78
- });
79
-
80
- const nameAndDesc = beginNameOrDescIdx === -1 ?
81
- null :
82
- tag.source.slice(beginNameOrDescIdx);
83
-
84
- const initialNumber = tag.source[0].number;
85
- const src = [
86
- // Get inevitably present tag from first `tag.source`
87
- {
88
- number: initialNumber,
89
- source: '',
90
- tokens: {
91
- ...tag.source[0].tokens,
92
- ...(typeLines.length || lastTypeLine ? {
93
- end: '',
94
- name: '',
95
- postName: '',
96
- postType: '',
97
- } : {}),
98
- type: '{' + typeBracketSpacing + firstTypeLine + (!typeLines.length && lastTypeLine === undefined ? typeBracketSpacing + '}' : ''),
99
- },
100
- },
101
- // Get any intervening type lines
102
- ...(typeLines.length ? typeLines.map((typeLine, idx) => {
103
- return {
104
- number: initialNumber + idx + 1,
105
- source: '',
106
- tokens: {
107
- // Grab any delimiter info from first item
108
- ...tag.source[0].tokens,
109
- delimiter: tag.source[0].tokens.delimiter === '/**' ? '*' : tag.source[0].tokens.delimiter,
110
- end: '',
111
- name: '',
112
- postName: '',
113
- postTag: '',
114
- postType: '',
115
- start: indent + ' ',
116
- tag: '',
117
- type: typeLine,
118
- },
119
- };
120
- }) : []),
121
- ];
122
-
123
- // Merge any final type line and name and description
124
- if (
125
- // Name and description may be already included if present with the tag
126
- nameAndDesc && beginNameOrDescIdx > 0
127
- ) {
128
- src.push({
129
- number: src.length + 1,
130
- source: '',
131
- tokens: {
132
- ...nameAndDesc[0].tokens,
133
- type: lastTypeLine + typeBracketSpacing + '}',
134
- },
135
- });
136
-
137
- if (
138
- // Get any remaining description lines
139
- nameAndDesc.length > 1
140
- ) {
141
- src.push(
142
- ...nameAndDesc.slice(1).map(({
143
- source,
144
- tokens,
145
- }, idx) => {
146
- return {
147
- number: src.length + idx + 2,
148
- source,
149
- tokens,
150
- };
151
- }),
152
- );
153
- }
154
- } else if (nameAndDesc) {
155
- if (lastTypeLine) {
156
- src.push({
157
- number: src.length + 1,
158
- source: '',
159
- tokens: {
160
- ...nameAndDesc[0].tokens,
161
- delimiter: nameAndDesc[0].tokens.delimiter === '/**' ? '*' : nameAndDesc[0].tokens.delimiter,
162
- postTag: '',
163
- start: indent + ' ',
164
- tag: '',
165
- type: lastTypeLine + typeBracketSpacing + '}',
166
- },
167
- });
168
- }
169
-
170
- if (
171
- // Get any remaining description lines
172
- nameAndDesc.length > 1
173
- ) {
174
- src.push(
175
- ...nameAndDesc.slice(1).map(({
176
- source,
177
- tokens,
178
- }, idx) => {
179
- return {
180
- number: src.length + idx + 2,
181
- source,
182
- tokens,
183
- };
184
- }),
185
- );
186
- }
187
- }
188
-
189
- tag.source = src;
190
-
191
- // Properly rewire `jsdoc.source`
192
- const firstTagIdx = jsdoc.source.findIndex(({
193
- tokens: {
194
- tag: tg,
195
- },
196
- }) => {
197
- return tg;
198
- });
199
-
200
- const initialEndSource = jsdoc.source.find(({
201
- tokens: {
202
- end,
203
- },
204
- }) => {
205
- return end;
206
- });
207
-
208
- jsdoc.source = [
209
- ...jsdoc.source.slice(0, firstTagIdx),
210
- ...jsdoc.tags.flatMap(({
211
- source,
212
- }) => {
213
- return source;
214
- }),
215
- ];
216
-
217
- if (initialEndSource && !jsdoc.source.at(-1)?.tokens?.end) {
218
- jsdoc.source.push(initialEndSource);
219
- }
73
+ rewireByParsedType(jsdoc, tag, parsedType, indent, typeBracketSpacing);
220
74
  };
221
75
 
222
76
  /** @type {string[]} */
@@ -366,7 +366,7 @@ export default iterateJsdoc(({
366
366
 
367
367
  // VALID TYPE
368
368
  const hasTypePosition = mightHaveTypePosition === true && Boolean(tag.type);
369
- if (hasTypePosition) {
369
+ if (hasTypePosition && (tag.type !== 'const' || tag.tag !== 'type')) {
370
370
  validTypeParsing(tag.type);
371
371
  }
372
372
 
package/src/rules.d.ts CHANGED
@@ -2938,6 +2938,47 @@ export interface Rules {
2938
2938
  }
2939
2939
  ];
2940
2940
 
2941
+ /** Prefers either function properties or method signatures */
2942
+ "jsdoc/ts-method-signature-style":
2943
+ | []
2944
+ | ["method" | "property"]
2945
+ | [
2946
+ "method" | "property",
2947
+ {
2948
+ /**
2949
+ * Whether to enable the fixer. Defaults to `true`.
2950
+ */
2951
+ enableFixer?: boolean;
2952
+ }
2953
+ ];
2954
+
2955
+ /** Warns against use of the empty object type */
2956
+ "jsdoc/ts-no-empty-object-type": [];
2957
+
2958
+ /** Catches unnecessary template expressions such as string expressions within a template literal. */
2959
+ "jsdoc/ts-no-unnecessary-template-expression":
2960
+ | []
2961
+ | [
2962
+ {
2963
+ /**
2964
+ * Whether to enable the fixer. Defaults to `true`.
2965
+ */
2966
+ enableFixer?: boolean;
2967
+ }
2968
+ ];
2969
+
2970
+ /** Prefers function types over call signatures when there are no other properties. */
2971
+ "jsdoc/ts-prefer-function-type":
2972
+ | []
2973
+ | [
2974
+ {
2975
+ /**
2976
+ * Whether to enable the fixer or not
2977
+ */
2978
+ enableFixer?: boolean;
2979
+ }
2980
+ ];
2981
+
2941
2982
  /** Formats JSDoc type values. */
2942
2983
  "jsdoc/type-formatting":
2943
2984
  | []