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.
- 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/validTypes.cjs +1 -1
- package/dist/rules/validTypes.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/validTypes.js +1 -1
- package/src/rules.d.ts +41 -0
package/src/index-cjs.js
CHANGED
|
@@ -66,6 +66,10 @@ import requireYieldsCheck from './rules/requireYieldsCheck.js';
|
|
|
66
66
|
import sortTags from './rules/sortTags.js';
|
|
67
67
|
import tagLines from './rules/tagLines.js';
|
|
68
68
|
import textEscaping from './rules/textEscaping.js';
|
|
69
|
+
import tsMethodSignatureStyle from './rules/tsMethodSignatureStyle.js';
|
|
70
|
+
import tsNoEmptyObjectType from './rules/tsNoEmptyObjectType.js';
|
|
71
|
+
import tsNoUnnecessaryTemplateExpression from './rules/tsNoUnnecessaryTemplateExpression.js';
|
|
72
|
+
import tsPreferFunctionType from './rules/tsPreferFunctionType.js';
|
|
69
73
|
import typeFormatting from './rules/typeFormatting.js';
|
|
70
74
|
import validTypes from './rules/validTypes.js';
|
|
71
75
|
|
|
@@ -251,6 +255,10 @@ index.rules = {
|
|
|
251
255
|
'sort-tags': sortTags,
|
|
252
256
|
'tag-lines': tagLines,
|
|
253
257
|
'text-escaping': textEscaping,
|
|
258
|
+
'ts-method-signature-style': tsMethodSignatureStyle,
|
|
259
|
+
'ts-no-empty-object-type': tsNoEmptyObjectType,
|
|
260
|
+
'ts-no-unnecessary-template-expression': tsNoUnnecessaryTemplateExpression,
|
|
261
|
+
'ts-prefer-function-type': tsPreferFunctionType,
|
|
254
262
|
'type-formatting': typeFormatting,
|
|
255
263
|
'valid-types': validTypes,
|
|
256
264
|
};
|
|
@@ -341,6 +349,10 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
|
|
|
341
349
|
'jsdoc/sort-tags': 'off',
|
|
342
350
|
'jsdoc/tag-lines': warnOrError,
|
|
343
351
|
'jsdoc/text-escaping': 'off',
|
|
352
|
+
'jsdoc/ts-method-signature-style': 'off',
|
|
353
|
+
'jsdoc/ts-no-empty-object-type': warnOrError,
|
|
354
|
+
'jsdoc/ts-no-unnecessary-template-expression': 'off',
|
|
355
|
+
'jsdoc/ts-prefer-function-type': 'off',
|
|
344
356
|
'jsdoc/type-formatting': 'off',
|
|
345
357
|
'jsdoc/valid-types': warnOrError,
|
|
346
358
|
},
|
package/src/index.js
CHANGED
|
@@ -72,6 +72,10 @@ import requireYieldsCheck from './rules/requireYieldsCheck.js';
|
|
|
72
72
|
import sortTags from './rules/sortTags.js';
|
|
73
73
|
import tagLines from './rules/tagLines.js';
|
|
74
74
|
import textEscaping from './rules/textEscaping.js';
|
|
75
|
+
import tsMethodSignatureStyle from './rules/tsMethodSignatureStyle.js';
|
|
76
|
+
import tsNoEmptyObjectType from './rules/tsNoEmptyObjectType.js';
|
|
77
|
+
import tsNoUnnecessaryTemplateExpression from './rules/tsNoUnnecessaryTemplateExpression.js';
|
|
78
|
+
import tsPreferFunctionType from './rules/tsPreferFunctionType.js';
|
|
75
79
|
import typeFormatting from './rules/typeFormatting.js';
|
|
76
80
|
import validTypes from './rules/validTypes.js';
|
|
77
81
|
|
|
@@ -257,6 +261,10 @@ index.rules = {
|
|
|
257
261
|
'sort-tags': sortTags,
|
|
258
262
|
'tag-lines': tagLines,
|
|
259
263
|
'text-escaping': textEscaping,
|
|
264
|
+
'ts-method-signature-style': tsMethodSignatureStyle,
|
|
265
|
+
'ts-no-empty-object-type': tsNoEmptyObjectType,
|
|
266
|
+
'ts-no-unnecessary-template-expression': tsNoUnnecessaryTemplateExpression,
|
|
267
|
+
'ts-prefer-function-type': tsPreferFunctionType,
|
|
260
268
|
'type-formatting': typeFormatting,
|
|
261
269
|
'valid-types': validTypes,
|
|
262
270
|
};
|
|
@@ -347,6 +355,10 @@ const createRecommendedRuleset = (warnOrError, flatName) => {
|
|
|
347
355
|
'jsdoc/sort-tags': 'off',
|
|
348
356
|
'jsdoc/tag-lines': warnOrError,
|
|
349
357
|
'jsdoc/text-escaping': 'off',
|
|
358
|
+
'jsdoc/ts-method-signature-style': 'off',
|
|
359
|
+
'jsdoc/ts-no-empty-object-type': warnOrError,
|
|
360
|
+
'jsdoc/ts-no-unnecessary-template-expression': 'off',
|
|
361
|
+
'jsdoc/ts-prefer-function-type': 'off',
|
|
350
362
|
'jsdoc/type-formatting': 'off',
|
|
351
363
|
'jsdoc/valid-types': warnOrError,
|
|
352
364
|
},
|
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
|
+
});
|