Skip to content

Commit 096f0be

Browse files
authored
chore: improve lint:docs script (#6625)
1 parent f8894da commit 096f0be

File tree

1 file changed

+73
-121
lines changed

1 file changed

+73
-121
lines changed

_tools/check_docs.ts

+73-121
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function assert(
5252
condition: boolean,
5353
message: string,
5454
document: { location: Location },
55-
) {
55+
): asserts condition {
5656
if (!condition) {
5757
diagnostics.push(new DocumentError(message, document));
5858
}
@@ -77,18 +77,17 @@ function isVoid(returnType: TsTypeDef) {
7777

7878
function assertHasReturnTag(document: { jsDoc: JsDoc; location: Location }) {
7979
const tag = document.jsDoc.tags?.find((tag) => tag.kind === "return");
80-
if (tag === undefined) {
81-
diagnostics.push(
82-
new DocumentError("Symbol must have a @return or @returns tag", document),
83-
);
84-
} else {
85-
assert(
86-
// @ts-ignore doc is defined
87-
tag.doc !== undefined,
88-
"@return tag must have a description",
89-
document,
90-
);
91-
}
80+
assert(
81+
tag !== undefined,
82+
"Symbol must have a @return or @returns tag",
83+
document,
84+
);
85+
if (tag === undefined) return;
86+
assert(
87+
tag.doc !== undefined,
88+
"@return tag must have a description",
89+
document,
90+
);
9291
}
9392

9493
/**
@@ -111,14 +110,11 @@ function assertHasParamDefinition(
111110
return false;
112111
});
113112

114-
if (!paramDoc) {
115-
diagnostics.push(
116-
new DocumentError(
117-
`@param ${param.name} must have a corresponding named function parameter definition.`,
118-
document,
119-
),
120-
);
121-
}
113+
assert(
114+
paramDoc !== undefined,
115+
`@param ${param.name} must have a corresponding function parameter definition.`,
116+
document,
117+
);
122118
}
123119

124120
function assertHasParamTag(
@@ -128,37 +124,31 @@ function assertHasParamTag(
128124
const tag = document.jsDoc.tags?.find((tag) =>
129125
tag.kind === "param" && tag.name === param
130126
);
131-
if (!tag) {
132-
diagnostics.push(
133-
new DocumentError(`Symbol must have a @param tag for ${param}`, document),
134-
);
135-
} else {
136-
assert(
137-
// @ts-ignore doc is defined
138-
tag.doc !== undefined,
139-
`@param tag for ${param} must have a description`,
140-
document,
141-
);
142-
}
127+
assert(
128+
tag !== undefined,
129+
`Symbol must have a @param tag for ${param}`,
130+
document,
131+
);
132+
if (tag === undefined) return;
133+
assert(
134+
// @ts-ignore doc is defined
135+
tag.doc !== undefined,
136+
`@param tag for ${param} must have a description`,
137+
document,
138+
);
143139
}
144140

145141
function assertHasSnippets(
146142
doc: string,
147143
document: { jsDoc: JsDoc; location: Location },
148-
required = true,
149144
) {
150145
const snippets = doc.match(TS_SNIPPET);
151-
if (snippets === null) {
152-
if (required) {
153-
diagnostics.push(
154-
new DocumentError(
155-
"@example tag must have a TypeScript code snippet",
156-
document,
157-
),
158-
);
159-
}
160-
return;
161-
}
146+
assert(
147+
snippets !== null,
148+
"@example tag must have a TypeScript code snippet",
149+
document,
150+
);
151+
if (snippets === null) return;
162152
for (let snippet of snippets) {
163153
const delim = snippet.split(NEWLINE)[0];
164154
// Trim the code block delimiters
@@ -179,17 +169,7 @@ function assertHasExampleTag(
179169
const exampleTags = document.jsDoc.tags?.filter((tag) =>
180170
tag.kind === "example"
181171
) as JsDocTagDocRequired[];
182-
const hasNoExampleTags = exampleTags === undefined ||
183-
exampleTags.length === 0;
184-
if (
185-
hasNoExampleTags &&
186-
!document.jsDoc.tags?.some((tag) => tag.kind === "private")
187-
) {
188-
diagnostics.push(
189-
new DocumentError("Symbol must have an @example tag", document),
190-
);
191-
return;
192-
}
172+
assert(exampleTags?.length > 0, "Symbol must have an @example tag", document);
193173
for (const tag of exampleTags) {
194174
assert(
195175
tag.doc !== undefined,
@@ -216,21 +196,17 @@ function assertHasTypeParamTags(
216196
const tag = document.jsDoc.tags?.find((tag) =>
217197
tag.kind === "template" && tag.name === typeParamName
218198
);
219-
if (tag === undefined) {
220-
diagnostics.push(
221-
new DocumentError(
222-
`Symbol must have a @typeParam tag for ${typeParamName}`,
223-
document,
224-
),
225-
);
226-
} else {
227-
assert(
228-
// @ts-ignore doc is defined
229-
tag.doc !== undefined,
230-
`@typeParam tag for ${typeParamName} must have a description`,
231-
document,
232-
);
233-
}
199+
assert(
200+
tag !== undefined,
201+
`Symbol must have a @typeParam tag for ${typeParamName}`,
202+
document,
203+
);
204+
assert(
205+
// @ts-ignore doc is defined
206+
tag.doc !== undefined,
207+
`@typeParam tag for ${typeParamName} must have a description`,
208+
document,
209+
);
234210
}
235211

236212
/**
@@ -245,7 +221,6 @@ function assertHasTypeParamTags(
245221
function assertFunctionDocs(
246222
document: DocNodeWithJsDoc<DocNodeFunction | ClassMethodDef>,
247223
) {
248-
assertHasSnippets(document.jsDoc.doc!, document, false);
249224
for (const param of document.functionDef.params) {
250225
if (param.kind === "identifier") {
251226
assertHasParamTag(document, param.name);
@@ -289,7 +264,6 @@ function assertFunctionDocs(
289264
* - Documentation on all properties, methods, and constructors.
290265
*/
291266
function assertClassDocs(document: DocNodeWithJsDoc<DocNodeClass>) {
292-
assertHasSnippets(document.jsDoc.doc!, document, false);
293267
for (const typeParam of document.classDef.typeParams) {
294268
assertHasTypeParamTags(document, typeParam.name);
295269
}
@@ -299,41 +273,31 @@ function assertClassDocs(document: DocNodeWithJsDoc<DocNodeClass>) {
299273

300274
for (const property of document.classDef.properties) {
301275
if (property.jsDoc === undefined) continue; // this is caught by `deno doc --lint`
302-
if (property.accessibility !== undefined) {
303-
diagnostics.push(
304-
new DocumentError(
305-
"Do not use `public`, `protected`, or `private` fields in classes",
306-
property,
307-
),
308-
);
309-
continue;
310-
}
276+
assert(
277+
property.accessibility === undefined,
278+
"Do not use `public`, `protected`, or `private` fields in classes",
279+
property,
280+
);
311281
assertClassPropertyDocs(
312282
property as DocNodeWithJsDoc<ClassPropertyDef>,
313283
);
314284
}
315285
for (const method of document.classDef.methods) {
316286
if (method.jsDoc === undefined) continue; // this is caught by `deno doc --lint`
317-
if (method.accessibility !== undefined) {
318-
diagnostics.push(
319-
new DocumentError(
320-
"Do not use `public`, `protected`, or `private` methods in classes",
321-
method,
322-
),
323-
);
324-
}
287+
assert(
288+
method.accessibility === undefined,
289+
"Do not use `public`, `protected`, or `private` methods in classes",
290+
document,
291+
);
325292
assertFunctionDocs(method as DocNodeWithJsDoc<ClassMethodDef>);
326293
}
327294
for (const constructor of document.classDef.constructors) {
328295
if (constructor.jsDoc === undefined) continue; // this is caught by `deno doc --lint`
329-
if (constructor.accessibility !== undefined) {
330-
diagnostics.push(
331-
new DocumentError(
332-
"Do not use `public`, `protected`, or `private` constructors in classes",
333-
constructor,
334-
),
335-
);
336-
}
296+
assert(
297+
constructor.accessibility === undefined,
298+
"Do not use `public`, `protected`, or `private` constructors in classes",
299+
constructor,
300+
);
337301
assertConstructorDocs(
338302
constructor as DocNodeWithJsDoc<ClassConstructorDef>,
339303
);
@@ -392,14 +356,11 @@ function assertModuleDoc(document: DocNodeWithJsDoc<DocNodeModuleDoc>) {
392356
function assertHasDefaultTags(document: DocNodeWithJsDoc<DocNodeInterface>) {
393357
for (const prop of document.interfaceDef.properties) {
394358
if (!prop.optional) continue;
395-
if (!prop.jsDoc?.tags?.find((tag) => tag.kind === "default")) {
396-
diagnostics.push(
397-
new DocumentError(
398-
"Optional interface properties should have default values",
399-
document,
400-
),
401-
);
402-
}
359+
assert(
360+
prop.jsDoc?.tags?.find((tag) => tag.kind === "default") !== undefined,
361+
"Optional interface properties should have default values",
362+
document,
363+
);
403364
}
404365
}
405366

@@ -417,14 +378,11 @@ function assertHasDeprecationDesc(document: DocNodeWithJsDoc<DocNode>) {
417378
if (!tags) return;
418379
for (const tag of tags) {
419380
if (tag.kind !== "deprecated") continue;
420-
if (tag.doc === undefined) {
421-
diagnostics.push(
422-
new DocumentError(
423-
"@deprecated tag must have a description",
424-
document,
425-
),
426-
);
427-
}
381+
assert(
382+
tag.doc !== undefined,
383+
"@deprecated tag must have a description",
384+
document,
385+
);
428386
}
429387
}
430388

@@ -434,30 +392,24 @@ async function assertDocs(specifiers: string[]) {
434392
if (d.jsDoc === undefined || d.declarationKind !== "export") continue; // this is caught by other checks
435393

436394
const document = d as DocNodeWithJsDoc<DocNode>;
395+
assertHasDeprecationDesc(document);
437396
switch (document.kind) {
438397
case "moduleDoc": {
439398
if (document.location.filename.endsWith("/mod.ts")) {
440399
assertModuleDoc(document);
441-
assertHasDeprecationDesc(document);
442400
}
443401
break;
444402
}
445403
case "function": {
446404
assertFunctionDocs(document);
447-
assertHasDeprecationDesc(document);
448405
break;
449406
}
450407
case "class": {
451408
assertClassDocs(document);
452-
assertHasDeprecationDesc(document);
453409
break;
454410
}
455411
case "interface":
456412
assertInterfaceDocs(document);
457-
assertHasDeprecationDesc(document);
458-
break;
459-
case "variable":
460-
assertHasDeprecationDesc(document);
461413
break;
462414
}
463415
}

0 commit comments

Comments
 (0)