(lines: Array<string>)
| 1032 | } |
| 1033 | if (content.at(-1)?.trim() === "" && tags.length > 0) { |
| 1034 | // exactly one blank before tags is validated by ensuring the last content line is blank and the previous is not blank |
| 1035 | if (content.length < 2 || content[content.length - 2]?.trim() === "") { |
| 1036 | diagnostics.push( |
| 1037 | diagnostic("invalid-spacing", "JSDoc tags must be separated from description content by exactly one blank line") |
| 1038 | ) |
| 1039 | } |
| 1040 | } else if (tags.length > 0 && content.length > 0) { |
| 1041 | diagnostics.push( |
| 1042 | diagnostic("invalid-spacing", "JSDoc tags must be separated from description content by exactly one blank line") |
| 1043 | ) |
| 1044 | } |
| 1045 | |
| 1046 | if (content[0]?.trim() === "") { |
| 1047 | diagnostics.push(diagnostic("leading-blank", "JSDoc must not start with a blank line")) |
| 1048 | } |
| 1049 | const contentWithoutTagBlank = tags.length > 0 && content.at(-1)?.trim() === "" ? content.slice(0, -1) : content |
| 1050 | if (contentWithoutTagBlank.at(-1)?.trim() === "") { |
| 1051 | diagnostics.push(diagnostic("trailing-blank", "JSDoc description must not end with a blank line")) |
| 1052 | } |
| 1053 | |
| 1054 | const parsedContent = parseDescriptionContent(contentWithoutTagBlank) |
| 1055 | diagnostics.push(...parsedContent.diagnostics) |
| 1056 | |
| 1057 | if (diagnostics.length > 0 || parsedContent.value === undefined) { |
| 1058 | return { _tag: "Failure", error: { diagnostics: uniqueDiagnostics(diagnostics) } } |
| 1059 | } |
| 1060 | return { |
| 1061 | _tag: "Success", |
| 1062 | value: { |
| 1063 | description: parsedContent.value.description, |
| 1064 | examples: parsedContent.value.examples, |
| 1065 | tags |
| 1066 | } |
| 1067 | } |
| 1068 | } |
| 1069 | |
| 1070 | function uniqueDiagnostics( |
| 1071 | diagnostics: ReadonlyArray<JSDocDiagnostic> |
| 1072 | ): ReadonlyArray<JSDocDiagnostic> { |
| 1073 | const seen = new Set<string>() |
| 1074 | return diagnostics.filter((item) => { |
| 1075 | const key = `${item.code}:${item.message}` |
| 1076 | if (seen.has(key)) { |
| 1077 | return false |
| 1078 | } |
| 1079 | seen.add(key) |
| 1080 | return true |
| 1081 | }) |
| 1082 | } |
| 1083 | |
| 1084 | const standardHeadings = ["**When to use**", "**Details**", "**Gotchas**"] as const |
| 1085 | |
| 1086 | type StandardHeading = typeof standardHeadings[number] |
| 1087 | |
| 1088 | const whenToUsePrefixes = ["Use to", "Use when", "Use as", "Use with"] as const |
| 1089 | |
| 1090 | interface ParsedContentResult { |
| 1091 | readonly value?: { |
no test coverage detected
searching dependent graphs…