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.
- package/README.md +8 -0
- package/dist/cjs/jsdocUtils.d.ts +8 -0
- package/dist/cjs/rules/tsMethodSignatureStyle.d.ts +2 -0
- package/dist/cjs/rules/tsNoEmptyObjectType.d.ts +2 -0
- package/dist/cjs/rules/tsNoUnnecessaryTemplateExpression.d.ts +2 -0
- package/dist/cjs/rules/tsPreferFunctionType.d.ts +2 -0
- package/dist/index-cjs.cjs +12 -0
- package/dist/index-cjs.cjs.map +1 -1
- package/dist/index.cjs +12 -0
- package/dist/index.cjs.map +1 -1
- package/dist/jsdocUtils.cjs +158 -1
- package/dist/jsdocUtils.cjs.map +1 -1
- package/dist/jsdocUtils.d.ts +8 -0
- package/dist/rules/tsMethodSignatureStyle.cjs +240 -0
- package/dist/rules/tsMethodSignatureStyle.cjs.map +1 -0
- package/dist/rules/tsMethodSignatureStyle.d.ts +3 -0
- package/dist/rules/tsNoEmptyObjectType.cjs +62 -0
- package/dist/rules/tsNoEmptyObjectType.cjs.map +1 -0
- package/dist/rules/tsNoEmptyObjectType.d.ts +3 -0
- package/dist/rules/tsNoUnnecessaryTemplateExpression.cjs +104 -0
- package/dist/rules/tsNoUnnecessaryTemplateExpression.cjs.map +1 -0
- package/dist/rules/tsNoUnnecessaryTemplateExpression.d.ts +3 -0
- package/dist/rules/tsPreferFunctionType.cjs +110 -0
- package/dist/rules/tsPreferFunctionType.cjs.map +1 -0
- package/dist/rules/tsPreferFunctionType.d.ts +3 -0
- package/dist/rules/typeFormatting.cjs +2 -128
- package/dist/rules/typeFormatting.cjs.map +1 -1
- package/dist/rules.d.ts +41 -0
- package/package.json +3 -3
- package/src/index-cjs.js +12 -0
- package/src/index.js +12 -0
- package/src/jsdocUtils.js +181 -0
- package/src/rules/tsMethodSignatureStyle.js +300 -0
- package/src/rules/tsNoEmptyObjectType.js +61 -0
- package/src/rules/tsNoUnnecessaryTemplateExpression.js +130 -0
- package/src/rules/tsPreferFunctionType.js +127 -0
- package/src/rules/typeFormatting.js +4 -150
- 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
|
+
});
|