eslint-plugin-jsdoc 61.0.1 → 61.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) 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.d.ts +41 -0
  29. package/package.json +3 -3
  30. package/src/index-cjs.js +12 -0
  31. package/src/index.js +12 -0
  32. package/src/jsdocUtils.js +181 -0
  33. package/src/rules/tsMethodSignatureStyle.js +300 -0
  34. package/src/rules/tsNoEmptyObjectType.js +61 -0
  35. package/src/rules/tsNoUnnecessaryTemplateExpression.js +130 -0
  36. package/src/rules/tsPreferFunctionType.js +127 -0
  37. package/src/rules/typeFormatting.js +4 -150
  38. package/src/rules.d.ts +41 -0
package/src/jsdocUtils.js CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  } from './tagNames.js';
7
7
  import WarnSettings from './WarnSettings.js';
8
8
  import {
9
+ stringify,
9
10
  tryParse,
10
11
  } from '@es-joy/jsdoccomment';
11
12
 
@@ -1896,6 +1897,185 @@ const strictNativeTypes = [
1896
1897
  'RegExp',
1897
1898
  ];
1898
1899
 
1900
+ /**
1901
+ * @param {import('@es-joy/jsdoccomment').JsdocBlockWithInline} jsdoc
1902
+ * @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} tag
1903
+ * @param {import('jsdoc-type-pratt-parser').RootResult} parsedType
1904
+ * @param {string} indent
1905
+ * @param {string} typeBracketSpacing
1906
+ */
1907
+ const rewireByParsedType = (jsdoc, tag, parsedType, indent, typeBracketSpacing = '') => {
1908
+ const typeLines = stringify(parsedType).split('\n');
1909
+ const firstTypeLine = typeLines.shift();
1910
+ const lastTypeLine = typeLines.pop();
1911
+
1912
+ const beginNameOrDescIdx = tag.source.findIndex(({
1913
+ tokens,
1914
+ }) => {
1915
+ return tokens.name || tokens.description;
1916
+ });
1917
+
1918
+ const nameAndDesc = beginNameOrDescIdx === -1 ?
1919
+ null :
1920
+ tag.source.slice(beginNameOrDescIdx);
1921
+
1922
+ const initialNumber = tag.source[0].number;
1923
+
1924
+ const src = [
1925
+ // Get inevitably present tag from first `tag.source`
1926
+ {
1927
+ number: initialNumber,
1928
+ source: '',
1929
+ tokens: {
1930
+ ...tag.source[0].tokens,
1931
+ ...(typeLines.length || lastTypeLine ? {
1932
+ end: '',
1933
+ name: '',
1934
+ postName: '',
1935
+ postType: '',
1936
+ } : (nameAndDesc ? {
1937
+ name: nameAndDesc[0].tokens.name,
1938
+ postType: ' ',
1939
+ } : {})),
1940
+ type: '{' + typeBracketSpacing + firstTypeLine + (!typeLines.length && lastTypeLine === undefined ? typeBracketSpacing + '}' : ''),
1941
+ },
1942
+ },
1943
+ // Get any intervening type lines
1944
+ ...(typeLines.length ? typeLines.map((typeLine, idx) => {
1945
+ return {
1946
+ number: initialNumber + idx + 1,
1947
+ source: '',
1948
+ tokens: {
1949
+ // Grab any delimiter info from first item
1950
+ ...tag.source[0].tokens,
1951
+ delimiter: tag.source[0].tokens.delimiter === '/**' ? '*' : tag.source[0].tokens.delimiter,
1952
+ end: '',
1953
+ name: '',
1954
+ postName: '',
1955
+ postTag: '',
1956
+ postType: '',
1957
+ start: indent + ' ',
1958
+ tag: '',
1959
+ type: typeLine,
1960
+ },
1961
+ };
1962
+ }) : []),
1963
+ ];
1964
+
1965
+ // Merge any final type line and name and description
1966
+ if (
1967
+ // Name and description may be already included if present with the tag
1968
+ nameAndDesc && beginNameOrDescIdx > 0
1969
+ ) {
1970
+ if (typeLines.length || lastTypeLine !== undefined) {
1971
+ src.push({
1972
+ number: src.length + 1,
1973
+ source: '',
1974
+ tokens: {
1975
+ ...nameAndDesc[0].tokens,
1976
+ type: lastTypeLine + typeBracketSpacing + '}',
1977
+ },
1978
+ });
1979
+ }
1980
+
1981
+ if (
1982
+ // Get any remaining description lines
1983
+ nameAndDesc.length > 1
1984
+ ) {
1985
+ src.push(
1986
+ ...nameAndDesc.slice(1).map(({
1987
+ source,
1988
+ tokens,
1989
+ }, idx) => {
1990
+ return {
1991
+ number: src.length + idx + 2,
1992
+ source,
1993
+ tokens,
1994
+ };
1995
+ }),
1996
+ );
1997
+ }
1998
+ } else if (nameAndDesc) {
1999
+ if ((typeLines.length || lastTypeLine !== undefined) && lastTypeLine) {
2000
+ src.push({
2001
+ number: src.length + 1,
2002
+ source: '',
2003
+ tokens: {
2004
+ ...nameAndDesc[0].tokens,
2005
+ delimiter: nameAndDesc[0].tokens.delimiter === '/**' ? '*' : nameAndDesc[0].tokens.delimiter,
2006
+ postTag: '',
2007
+ start: indent + ' ',
2008
+ tag: '',
2009
+ type: lastTypeLine + typeBracketSpacing + '}',
2010
+ },
2011
+ });
2012
+ }
2013
+
2014
+ if (
2015
+ // Get any remaining description lines
2016
+ nameAndDesc.length > 1
2017
+ ) {
2018
+ src.push(
2019
+ ...nameAndDesc.slice(1).map(({
2020
+ source,
2021
+ tokens,
2022
+ }, idx) => {
2023
+ return {
2024
+ number: src.length + idx + 2,
2025
+ source,
2026
+ tokens,
2027
+ };
2028
+ }),
2029
+ );
2030
+ }
2031
+ } else if (lastTypeLine) {
2032
+ src.push({
2033
+ number: src.length + 1,
2034
+ source: '',
2035
+ tokens: {
2036
+ ...tag.source[0].tokens,
2037
+ delimiter: tag.source[0].tokens.delimiter === '/**' ? '*' : tag.source[0].tokens.delimiter,
2038
+ postTag: '',
2039
+ start: indent + ' ',
2040
+ tag: '',
2041
+ type: lastTypeLine + typeBracketSpacing + '}',
2042
+ },
2043
+ });
2044
+ }
2045
+
2046
+ tag.source = src;
2047
+
2048
+ // Properly rewire `jsdoc.source`
2049
+ const firstTagIdx = jsdoc.source.findIndex(({
2050
+ tokens: {
2051
+ tag: tg,
2052
+ },
2053
+ }) => {
2054
+ return tg;
2055
+ });
2056
+
2057
+ const initialEndSource = jsdoc.source.find(({
2058
+ tokens: {
2059
+ end,
2060
+ },
2061
+ }) => {
2062
+ return end;
2063
+ });
2064
+
2065
+ jsdoc.source = [
2066
+ ...jsdoc.source.slice(0, firstTagIdx),
2067
+ ...jsdoc.tags.flatMap(({
2068
+ source,
2069
+ }) => {
2070
+ return source;
2071
+ }),
2072
+ ];
2073
+
2074
+ if (initialEndSource && !jsdoc.source.at(-1)?.tokens?.end) {
2075
+ jsdoc.source.push(initialEndSource);
2076
+ }
2077
+ };
2078
+
1899
2079
  export {
1900
2080
  comparePaths,
1901
2081
  dropPathSegmentQuotes,
@@ -1935,6 +2115,7 @@ export {
1935
2115
  overrideTagStructure,
1936
2116
  parseClosureTemplateTag,
1937
2117
  pathDoesNotBeginWith,
2118
+ rewireByParsedType,
1938
2119
  setTagStructure,
1939
2120
  strictNativeTypes,
1940
2121
  tagMightHaveEitherTypeOrNamePosition,
@@ -0,0 +1,300 @@
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 functionType = context.options[0] ?? 'property';
17
+ const {
18
+ enableFixer = true,
19
+ } = context.options[1] ?? {};
20
+
21
+ /**
22
+ * @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} tag
23
+ */
24
+ const checkType = (tag) => {
25
+ const potentialType = tag.type;
26
+ let parsedType;
27
+ try {
28
+ parsedType = parseType(
29
+ /** @type {string} */ (potentialType), 'typescript',
30
+ );
31
+ } catch {
32
+ return;
33
+ }
34
+
35
+ traverse(parsedType, (nde, parentNode) => {
36
+ // @ts-expect-error Adding our own property for use below
37
+ nde.parentNode = parentNode;
38
+ });
39
+
40
+ traverse(parsedType, (nde, parentNode, property, idx) => {
41
+ switch (nde.type) {
42
+ case 'JsdocTypeFunction': {
43
+ if (functionType !== 'method') {
44
+ break;
45
+ }
46
+
47
+ if (parentNode?.type === 'JsdocTypeObjectField' &&
48
+ typeof parentNode.key === 'string'
49
+ ) {
50
+ utils.reportJSDoc(
51
+ 'Found function property; prefer method signature.',
52
+ tag,
53
+ enableFixer ? () => {
54
+ const objectField = parentNode;
55
+ const obj =
56
+ /**
57
+ * @type {import('jsdoc-type-pratt-parser').ObjectFieldResult & {
58
+ * parentNode: import('jsdoc-type-pratt-parser').ObjectResult
59
+ * }}
60
+ */
61
+ (objectField).parentNode;
62
+
63
+ const index = obj.elements.indexOf(parentNode);
64
+
65
+ obj.elements[index] = {
66
+ /* c8 ignore next 5 -- Guard */
67
+ meta: nde.meta ?
68
+ {
69
+ quote: objectField.meta.quote,
70
+ ...nde.meta,
71
+ } :
72
+ {
73
+ quote: objectField.meta.quote,
74
+ },
75
+ name: /** @type {string} */ (objectField.key),
76
+ parameters: nde.parameters,
77
+ returnType: /** @type {import('jsdoc-type-pratt-parser').RootResult} */ (
78
+ nde.returnType
79
+ ),
80
+ type: 'JsdocTypeMethodSignature',
81
+ typeParameters: nde.typeParameters,
82
+ };
83
+
84
+ rewireByParsedType(jsdoc, tag, parsedType, indent);
85
+ } : null,
86
+ );
87
+ break;
88
+ }
89
+
90
+ if (parentNode?.type === 'JsdocTypeParenthesis' &&
91
+ // @ts-expect-error Our own added API
92
+ parentNode.parentNode?.type === 'JsdocTypeIntersection' &&
93
+ // @ts-expect-error Our own added API
94
+ parentNode.parentNode.parentNode.type === 'JsdocTypeObjectField' &&
95
+ // @ts-expect-error Our own added API
96
+ typeof parentNode.parentNode.parentNode.key === 'string'
97
+ ) {
98
+ // @ts-expect-error Our own added API
99
+ const intersection = parentNode.parentNode;
100
+ const objectField = intersection.parentNode;
101
+ const object = objectField.parentNode;
102
+ // const objFieldIndex = object.elements.indexOf(objectField);
103
+
104
+ /**
105
+ * @param {import('jsdoc-type-pratt-parser').FunctionResult} func
106
+ */
107
+ const convertToMethod = (func) => {
108
+ return /** @type {import('jsdoc-type-pratt-parser').MethodSignatureResult} */ ({
109
+ /* c8 ignore next 5 -- Guard */
110
+ meta: func.meta ?
111
+ {
112
+ quote: objectField.meta.quote,
113
+ ...func.meta,
114
+ } :
115
+ {
116
+ quote: objectField.meta.quote,
117
+ },
118
+ name: /** @type {string} */ (objectField.key),
119
+ parameters: func.parameters,
120
+ returnType: /** @type {import('jsdoc-type-pratt-parser').RootResult} */ (
121
+ func.returnType
122
+ ),
123
+ type: 'JsdocTypeMethodSignature',
124
+ typeParameters: func.typeParameters,
125
+ });
126
+ };
127
+
128
+ /** @type {import('jsdoc-type-pratt-parser').MethodSignatureResult[]} */
129
+ const methods = [];
130
+ /** @type {number[]} */
131
+ const methodIndexes = [];
132
+ for (const [
133
+ index,
134
+ element,
135
+ ] of intersection.elements.entries()) {
136
+ if (
137
+ element.type !== 'JsdocTypeParenthesis' ||
138
+ element.element.type !== 'JsdocTypeFunction'
139
+ ) {
140
+ return;
141
+ }
142
+
143
+ methods.push(convertToMethod(element.element));
144
+ methodIndexes.push(index);
145
+ }
146
+
147
+ utils.reportJSDoc(
148
+ 'Found function property; prefer method signature.',
149
+ tag,
150
+ enableFixer ? () => {
151
+ for (const methodIndex of methodIndexes.toReversed()) {
152
+ object.elements.splice(methodIndex, 1);
153
+ }
154
+
155
+ object.elements.splice(methodIndexes[0], 0, ...methods);
156
+
157
+ rewireByParsedType(jsdoc, tag, parsedType, indent);
158
+ } : null,
159
+ );
160
+ }
161
+
162
+ break;
163
+ }
164
+
165
+ case 'JsdocTypeMethodSignature': {
166
+ if (functionType !== 'property') {
167
+ break;
168
+ }
169
+
170
+ /**
171
+ * @param {import('jsdoc-type-pratt-parser').MethodSignatureResult} node
172
+ */
173
+ const convertToFunction = (node) => {
174
+ return {
175
+ arrow: true,
176
+ constructor: false,
177
+ meta: /** @type {Required<import('jsdoc-type-pratt-parser').MethodSignatureResult['meta']>} */ (
178
+ node.meta
179
+ ),
180
+ parameters: node.parameters,
181
+ parenthesis: true,
182
+ returnType: node.returnType,
183
+ type: 'JsdocTypeFunction',
184
+ typeParameters: node.typeParameters,
185
+ };
186
+ };
187
+
188
+ utils.reportJSDoc(
189
+ 'Found method signature; prefer function property.',
190
+ tag,
191
+ enableFixer ? () => {
192
+ /* c8 ignore next 3 -- TS guard */
193
+ if (!parentNode || !property || typeof idx !== 'number') {
194
+ throw new Error('Unexpected lack of parent or property');
195
+ }
196
+
197
+ const object = /** @type {import('jsdoc-type-pratt-parser').ObjectResult} */ (
198
+ parentNode
199
+ );
200
+
201
+ const funcs = [];
202
+ const removals = [];
203
+
204
+ for (const [
205
+ index,
206
+ element,
207
+ ] of object.elements.entries()) {
208
+ if (element.type === 'JsdocTypeMethodSignature' &&
209
+ element.name === nde.name
210
+ ) {
211
+ funcs.push(convertToFunction(element));
212
+ if (index !== idx) {
213
+ removals.push(index);
214
+ }
215
+ }
216
+ }
217
+
218
+ if (funcs.length === 1) {
219
+ object.elements[idx] = /** @type {import('jsdoc-type-pratt-parser').ObjectFieldResult} */ ({
220
+ key: nde.name,
221
+ meta: nde.meta,
222
+ optional: false,
223
+ readonly: false,
224
+ right: funcs[0],
225
+ type: 'JsdocTypeObjectField',
226
+ });
227
+ } else {
228
+ for (const removal of removals.toReversed()) {
229
+ object.elements.splice(removal, 1);
230
+ }
231
+
232
+ object.elements[idx] = {
233
+ key: nde.name,
234
+ meta: nde.meta,
235
+ optional: false,
236
+ readonly: false,
237
+ right: {
238
+ elements: funcs.map((func) => {
239
+ return /** @type {import('jsdoc-type-pratt-parser').ParenthesisResult} */ ({
240
+ element: func,
241
+ type: 'JsdocTypeParenthesis',
242
+ });
243
+ }),
244
+ type: 'JsdocTypeIntersection',
245
+ },
246
+ type: 'JsdocTypeObjectField',
247
+ };
248
+ }
249
+
250
+ rewireByParsedType(jsdoc, tag, parsedType, indent);
251
+ } : null,
252
+ );
253
+ }
254
+
255
+ break;
256
+ }
257
+ });
258
+ };
259
+
260
+ const tags = utils.filterTags(({
261
+ tag,
262
+ }) => {
263
+ return Boolean(tag !== 'import' && utils.tagMightHaveTypePosition(tag));
264
+ });
265
+
266
+ for (const tag of tags) {
267
+ if (tag.type) {
268
+ checkType(tag);
269
+ }
270
+ }
271
+ }, {
272
+ iterateAllJsdocs: true,
273
+ meta: {
274
+ docs: {
275
+ description: 'Prefers either function properties or method signatures',
276
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-method-signature-style.md#repos-sticky-header',
277
+ },
278
+ fixable: 'code',
279
+ schema: [
280
+ {
281
+ enum: [
282
+ 'method',
283
+ 'property',
284
+ ],
285
+ type: 'string',
286
+ },
287
+ {
288
+ additionalProperties: false,
289
+ properties: {
290
+ enableFixer: {
291
+ description: 'Whether to enable the fixer. Defaults to `true`.',
292
+ type: 'boolean',
293
+ },
294
+ },
295
+ type: 'object',
296
+ },
297
+ ],
298
+ type: 'suggestion',
299
+ },
300
+ });
@@ -0,0 +1,61 @@
1
+ import iterateJsdoc from '../iterateJsdoc.js';
2
+ import {
3
+ parse as parseType,
4
+ traverse,
5
+ } from '@es-joy/jsdoccomment';
6
+
7
+ export default iterateJsdoc(({
8
+ settings,
9
+ utils,
10
+ }) => {
11
+ if (settings.mode !== 'typescript') {
12
+ return;
13
+ }
14
+
15
+ /**
16
+ * @param {import('@es-joy/jsdoccomment').JsdocTagWithInline} tag
17
+ */
18
+ const checkType = (tag) => {
19
+ const potentialType = tag.type;
20
+ let parsedType;
21
+ try {
22
+ parsedType = parseType(
23
+ /** @type {string} */ (potentialType), 'typescript',
24
+ );
25
+ } catch {
26
+ return;
27
+ }
28
+
29
+ traverse(parsedType, (nde) => {
30
+ switch (nde.type) {
31
+ case 'JsdocTypeObject': {
32
+ if (!nde.elements.length) {
33
+ utils.reportJSDoc('No empty object type.', tag);
34
+ }
35
+ }
36
+ }
37
+ });
38
+ };
39
+
40
+ const tags = utils.filterTags(({
41
+ tag,
42
+ }) => {
43
+ return Boolean(tag !== 'import' && utils.tagMightHaveTypePosition(tag));
44
+ });
45
+
46
+ for (const tag of tags) {
47
+ if (tag.type) {
48
+ checkType(tag);
49
+ }
50
+ }
51
+ }, {
52
+ iterateAllJsdocs: true,
53
+ meta: {
54
+ docs: {
55
+ description: 'Warns against use of the empty object type',
56
+ url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/ts-no-empty-object-type.md#repos-sticky-header',
57
+ },
58
+ schema: [],
59
+ type: 'suggestion',
60
+ },
61
+ });
@@ -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
+ });