Skip to content

Commit 77e1691

Browse files
committed
feat(type-formatting): check type and typedef tags and add typeBracketSpacing option
1 parent cc7f253 commit 77e1691

File tree

4 files changed

+113
-11
lines changed

4 files changed

+113
-11
lines changed

.README/rules/type-formatting.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ property-value field. Defaults to the empty string.
5757
Whether to apply the `objectFieldSeparator` when there is only one
5858
property-value object field present. Defaults to `false`.
5959

60+
### `typeBracketSpacing`
61+
62+
A string of spaces that will be added immediately after the type's initial
63+
curly bracket and immediately before its ending curly bracket. Defaults
64+
to the empty string.
65+
6066
### `unionSpacing`
6167

6268
Determines the spacing to add to unions (`|`). Defaults to a single space.
@@ -67,7 +73,7 @@ Determines the spacing to add to unions (`|`). Defaults to a single space.
6773
|Tags|``|
6874
|Recommended|false|
6975
|Settings||
70-
|Options|`arrayBrackets`, `enableFixer`, `genericDot`, `objectFieldIndent`, `objectFieldQuote`, `objectFieldSeparator`, `propertyQuotes`, `separatorForSingleObjectField`, `stringQuotes`, `unionSpacing`|
76+
|Options|`arrayBrackets`, `enableFixer`, `genericDot`, `objectFieldIndent`, `objectFieldQuote`, `objectFieldSeparator`, `propertyQuotes`, `separatorForSingleObjectField`, `stringQuotes`, `typeBracketSpacing`, `unionSpacing`|
7177

7278
## Failing examples
7379

docs/rules/type-formatting.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ property-value field. Defaults to the empty string.
7979
Whether to apply the `objectFieldSeparator` when there is only one
8080
property-value object field present. Defaults to `false`.
8181

82+
<a name="user-content-type-formatting-options-typebracketspacing"></a>
83+
<a name="type-formatting-options-typebracketspacing"></a>
84+
### <code>typeBracketSpacing</code>
85+
86+
A string of spaces that will be added immediately after the type's initial
87+
curly bracket and immediately before its ending curly bracket. Defaults
88+
to the empty string.
89+
8290
<a name="user-content-type-formatting-options-unionspacing"></a>
8391
<a name="type-formatting-options-unionspacing"></a>
8492
### <code>unionSpacing</code>
@@ -91,7 +99,7 @@ Determines the spacing to add to unions (`|`). Defaults to a single space.
9199
|Tags|``|
92100
|Recommended|false|
93101
|Settings||
94-
|Options|`arrayBrackets`, `enableFixer`, `genericDot`, `objectFieldIndent`, `objectFieldQuote`, `objectFieldSeparator`, `propertyQuotes`, `separatorForSingleObjectField`, `stringQuotes`, `unionSpacing`|
102+
|Options|`arrayBrackets`, `enableFixer`, `genericDot`, `objectFieldIndent`, `objectFieldQuote`, `objectFieldSeparator`, `propertyQuotes`, `separatorForSingleObjectField`, `stringQuotes`, `typeBracketSpacing`, `unionSpacing`|
95103

96104
<a name="user-content-type-formatting-failing-examples"></a>
97105
<a name="type-formatting-failing-examples"></a>
@@ -221,6 +229,18 @@ The following patterns are considered problems:
221229
*/
222230
// "jsdoc/type-formatting": ["error"|"warn", {"objectFieldSeparator":"comma"}]
223231
// Message: There was an error with type formatting
232+
233+
/**
234+
* @type {string}
235+
*/
236+
// "jsdoc/type-formatting": ["error"|"warn", {"typeBracketSpacing":" "}]
237+
// Message: Must have initial and final " " spacing
238+
239+
/**
240+
* @type { string }
241+
*/
242+
// "jsdoc/type-formatting": ["error"|"warn", {"typeBracketSpacing":""}]
243+
// Message: Must have no initial spacing
224244
````
225245

226246

src/rules/typeFormatting.js

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export default iterateJsdoc(({
2323
propertyQuotes = null,
2424
separatorForSingleObjectField = false,
2525
stringQuotes = 'single',
26+
typeBracketSpacing = '',
2627
unionSpacing = ' ',
2728
} = context.options[0] || {};
2829

@@ -55,7 +56,10 @@ export default iterateJsdoc(({
5556
return tokens.name || tokens.description;
5657
});
5758

58-
const nameAndDesc = tag.source.slice(beginNameOrDescIdx);
59+
const nameAndDesc = beginNameOrDescIdx === -1 ?
60+
null :
61+
tag.source.slice(beginNameOrDescIdx);
62+
5963
const initialNumber = tag.source[0].number;
6064
const src = [
6165
// Get inevitably present tag from first `tag.source`
@@ -70,7 +74,7 @@ export default iterateJsdoc(({
7074
postName: '',
7175
postType: '',
7276
} : {}),
73-
type: '{' + firstTypeLine + (!typeLines.length && lastTypeLine === undefined ? '}' : ''),
77+
type: '{' + typeBracketSpacing + firstTypeLine + (!typeLines.length && lastTypeLine === undefined ? typeBracketSpacing + '}' : ''),
7478
},
7579
},
7680
// Get any intervening type lines
@@ -98,14 +102,14 @@ export default iterateJsdoc(({
98102
// Merge any final type line and name and description
99103
if (
100104
// Name and description may be already included if present with the tag
101-
beginNameOrDescIdx > 0
105+
nameAndDesc && beginNameOrDescIdx > 0
102106
) {
103107
src.push({
104108
number: src.length + 1,
105109
source: '',
106110
tokens: {
107111
...nameAndDesc[0].tokens,
108-
type: lastTypeLine + '}',
112+
type: lastTypeLine + typeBracketSpacing + '}',
109113
},
110114
});
111115

@@ -126,7 +130,7 @@ export default iterateJsdoc(({
126130
}),
127131
);
128132
}
129-
} else {
133+
} else if (nameAndDesc) {
130134
if (lastTypeLine) {
131135
src.push({
132136
number: src.length + 1,
@@ -137,14 +141,14 @@ export default iterateJsdoc(({
137141
postTag: '',
138142
start: indent + ' ',
139143
tag: '',
140-
type: lastTypeLine + '}',
144+
type: lastTypeLine + typeBracketSpacing + '}',
141145
},
142146
});
143147
}
144148

145149
if (
146150
// Get any remaining description lines
147-
nameAndDesc.length > 1
151+
nameAndDesc && nameAndDesc.length > 1
148152
) {
149153
src.push(
150154
...nameAndDesc.slice(1).map(({
@@ -172,6 +176,14 @@ export default iterateJsdoc(({
172176
return tg;
173177
});
174178

179+
const initialEndSource = jsdoc.source.find(({
180+
tokens: {
181+
end,
182+
},
183+
}) => {
184+
return end;
185+
});
186+
175187
jsdoc.source = [
176188
...jsdoc.source.slice(0, firstTagIdx),
177189
...jsdoc.tags.flatMap(({
@@ -180,11 +192,21 @@ export default iterateJsdoc(({
180192
return source;
181193
}),
182194
];
195+
196+
if (initialEndSource && !jsdoc.source.at(-1)?.tokens?.end) {
197+
jsdoc.source.push(initialEndSource);
198+
}
183199
};
184200

185201
/** @type {string[]} */
186202
const errorMessages = [];
187203

204+
if (typeBracketSpacing && (!tag.type.startsWith(typeBracketSpacing) || !tag.type.endsWith(typeBracketSpacing))) {
205+
errorMessages.push(`Must have initial and final "${typeBracketSpacing}" spacing`);
206+
} else if (!typeBracketSpacing && ((/^\s/v).test(tag.type) || (/\s$/v).test(tag.type))) {
207+
errorMessages.push('Must have no initial spacing');
208+
}
209+
188210
// eslint-disable-next-line complexity -- Todo
189211
traverse(parsedType, (nde) => {
190212
let errorMessage = '';
@@ -279,7 +301,8 @@ export default iterateJsdoc(({
279301
}
280302
});
281303

282-
const differentResult = tag.type !== stringify(parsedType);
304+
const differentResult = tag.type !==
305+
typeBracketSpacing + stringify(parsedType) + typeBracketSpacing;
283306

284307
if (errorMessages.length && differentResult) {
285308
for (const errorMessage of errorMessages) {
@@ -299,9 +322,13 @@ export default iterateJsdoc(({
299322
const tags = utils.getPresentTags([
300323
'param',
301324
'returns',
325+
'type',
326+
'typedef',
302327
]);
303328
for (const tag of tags) {
304-
checkTypeFormats(tag);
329+
if (tag.type) {
330+
checkTypeFormats(tag);
331+
}
305332
}
306333
}, {
307334
iterateAllJsdocs: true,
@@ -362,6 +389,9 @@ export default iterateJsdoc(({
362389
'single',
363390
],
364391
},
392+
typeBracketSpacing: {
393+
type: 'string',
394+
},
365395
unionSpacing: {
366396
type: 'string',
367397
},

test/rules/assertions/typeFormatting.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,52 @@ export default {
480480
*/
481481
`,
482482
},
483+
{
484+
code: `
485+
/**
486+
* @type {string}
487+
*/
488+
`,
489+
errors: [
490+
{
491+
line: 3,
492+
message: 'Must have initial and final " " spacing',
493+
},
494+
],
495+
options: [
496+
{
497+
typeBracketSpacing: ' ',
498+
},
499+
],
500+
output: `
501+
/**
502+
* @type { string }
503+
*/
504+
`,
505+
},
506+
{
507+
code: `
508+
/**
509+
* @type { string }
510+
*/
511+
`,
512+
errors: [
513+
{
514+
line: 3,
515+
message: 'Must have no initial spacing',
516+
},
517+
],
518+
options: [
519+
{
520+
typeBracketSpacing: '',
521+
},
522+
],
523+
output: `
524+
/**
525+
* @type {string}
526+
*/
527+
`,
528+
},
483529
],
484530
valid: [
485531
{

0 commit comments

Comments
 (0)