Skip to content

Commit f70fd6c

Browse files
committed
feat(require-description-complete-sentence): add abbreviations option; fixes #424
1 parent 3c78408 commit f70fd6c

File tree

4 files changed

+500
-13
lines changed

4 files changed

+500
-13
lines changed

.README/rules/require-description-complete-sentence.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ tag descriptions are written in complete sentences, i.e.,
1010
character must be preceded by a line ending with a period.
1111
* A colon or semi-colon followed by two line breaks is still part of the
1212
containing paragraph (unlike normal dual line breaks).
13+
* Text within inline tags `{...}` are not checked for sentence divisions.
14+
* Periods after items within the `abbreviations` option array are not treated
15+
as sentence endings.
1316

1417
#### Options
1518

@@ -34,10 +37,16 @@ its "description" (e.g., for `@returns {someType} some description`, the
3437
description is `some description` while for `@some-tag xyz`, the description
3538
is `xyz`).
3639

40+
##### `abbreviations`
41+
42+
You can provide an `abbreviations` options array to avoid such strings of text
43+
being treated as sentence endings when followed by dots. The `.` is not
44+
necessary at the end of the array items.
45+
3746
|||
3847
|---|---|
3948
|Context|everywhere|
4049
|Tags|doc block, `param`, `returns`, `description`, `property`, `summary`, `file`, `classdesc`, `todo`, `deprecated`, `throws`, 'yields' and others added by `tags`|
4150
|Aliases|`arg`, `argument`, `return`, `desc`, `prop`, `fileoverview`, `overview`, `exception`, `yield`|
42-
|Options|`tags`|
51+
|Options|`tags`, `abbreviations`|
4352
<!-- assertions requireDescriptionCompleteSentence -->

README.md

Lines changed: 170 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5226,6 +5226,9 @@ tag descriptions are written in complete sentences, i.e.,
52265226
character must be preceded by a line ending with a period.
52275227
* A colon or semi-colon followed by two line breaks is still part of the
52285228
containing paragraph (unlike normal dual line breaks).
5229+
* Text within inline tags `{...}` are not checked for sentence divisions.
5230+
* Periods after items within the `abbreviations` option array are not treated
5231+
as sentence endings.
52295232
52305233
<a name="eslint-plugin-jsdoc-rules-require-description-complete-sentence-options-11"></a>
52315234
#### Options
@@ -5252,12 +5255,19 @@ its "description" (e.g., for `@returns {someType} some description`, the
52525255
description is `some description` while for `@some-tag xyz`, the description
52535256
is `xyz`).
52545257
5258+
<a name="eslint-plugin-jsdoc-rules-require-description-complete-sentence-options-11-abbreviations"></a>
5259+
##### <code>abbreviations</code>
5260+
5261+
You can provide an `abbreviations` options array to avoid such strings of text
5262+
being treated as sentence endings when followed by dots. The `.` is not
5263+
necessary at the end of the array items.
5264+
52555265
|||
52565266
|---|---|
52575267
|Context|everywhere|
52585268
|Tags|doc block, `param`, `returns`, `description`, `property`, `summary`, `file`, `classdesc`, `todo`, `deprecated`, `throws`, 'yields' and others added by `tags`|
52595269
|Aliases|`arg`, `argument`, `return`, `desc`, `prop`, `fileoverview`, `overview`, `exception`, `yield`|
5260-
|Options|`tags`|
5270+
|Options|`tags`, `abbreviations`|
52615271
The following patterns are considered problems:
52625272
52635273
````js
@@ -5471,6 +5481,86 @@ function quux (foo) {
54715481
// Settings: {"jsdoc":{"tagNamePreference":{"description":false}}}
54725482
// Options: [{"tags":["param"]}]
54735483
// Message: Sentence must end with a period.
5484+
5485+
/**
5486+
* Sorry, but this isn't a complete sentence, Mr.
5487+
*/
5488+
function quux () {
5489+
5490+
}
5491+
// Options: [{"abbreviations":["Mr"]}]
5492+
// Message: Sentence must end with a period.
5493+
5494+
/**
5495+
* Sorry, but this isn't a complete sentence Mr.
5496+
*/
5497+
function quux () {
5498+
5499+
}
5500+
// Options: [{"abbreviations":["Mr."]}]
5501+
// Message: Sentence must end with a period.
5502+
5503+
/**
5504+
* Sorry, but this isn't a complete sentence Mr.
5505+
*/
5506+
function quux () {
5507+
5508+
}
5509+
// Options: [{"abbreviations":["Mr"]}]
5510+
// Message: Sentence must end with a period.
5511+
5512+
/**
5513+
* Sorry, but this isn't a complete sentence Mr. and Mrs.
5514+
*/
5515+
function quux () {
5516+
5517+
}
5518+
// Options: [{"abbreviations":["Mr","Mrs"]}]
5519+
// Message: Sentence must end with a period.
5520+
5521+
/**
5522+
* This is a complete sentence. But this isn't, Mr.
5523+
*/
5524+
function quux () {
5525+
5526+
}
5527+
// Options: [{"abbreviations":["Mr"]}]
5528+
// Message: Sentence must end with a period.
5529+
5530+
/**
5531+
* This is a complete Mr. sentence. But this isn't, Mr.
5532+
*/
5533+
function quux () {
5534+
5535+
}
5536+
// Options: [{"abbreviations":["Mr"]}]
5537+
// Message: Sentence must end with a period.
5538+
5539+
/**
5540+
* This is a complete Mr. sentence.
5541+
*/
5542+
function quux () {
5543+
5544+
}
5545+
// Message: Sentence should start with an uppercase character.
5546+
5547+
/**
5548+
* This is fun, i.e. enjoyable, but not superlatively so, e.g. not
5549+
* super, wonderful, etc..
5550+
*/
5551+
function quux () {
5552+
5553+
}
5554+
// Message: Sentence should start with an uppercase character.
5555+
5556+
/**
5557+
* Do not have dynamic content; e.g. homepage. Here a simple unique id
5558+
* suffices.
5559+
*/
5560+
function quux () {
5561+
5562+
}
5563+
// Message: Sentence should start with an uppercase character.
54745564
````
54755565
54765566
The following patterns are not considered problems:
@@ -5704,6 +5794,85 @@ function quux () {
57045794
function quux () {
57055795
57065796
}
5797+
5798+
/**
5799+
* Sorry, but this isn't a complete sentence, Mr.
5800+
*/
5801+
function quux () {
5802+
5803+
}
5804+
5805+
/**
5806+
* Sorry, but this isn't a complete sentence Mr..
5807+
*/
5808+
function quux () {
5809+
5810+
}
5811+
// Options: [{"abbreviations":["Mr."]}]
5812+
5813+
/**
5814+
* Sorry, but this isn't a complete sentence Mr.
5815+
*/
5816+
function quux () {
5817+
5818+
}
5819+
5820+
/**
5821+
* Sorry, but this isn't a complete sentence Mr. and Mrs..
5822+
*/
5823+
function quux () {
5824+
5825+
}
5826+
// Options: [{"abbreviations":["Mr","Mrs"]}]
5827+
5828+
/**
5829+
* This is a complete sentence aMr.
5830+
*/
5831+
function quux () {
5832+
5833+
}
5834+
// Options: [{"abbreviations":["Mr"]}]
5835+
5836+
/**
5837+
* This is a complete sentence. But this isn't, Mr.
5838+
*/
5839+
function quux () {
5840+
5841+
}
5842+
5843+
/**
5844+
* This is a complete Mr. Sentence. But this isn't, Mr.
5845+
*/
5846+
function quux () {
5847+
5848+
}
5849+
5850+
/**
5851+
* This is a complete Mr. sentence.
5852+
*/
5853+
function quux () {
5854+
5855+
}
5856+
// Options: [{"abbreviations":["Mr"]}]
5857+
5858+
/**
5859+
* This is fun, i.e. enjoyable, but not superlatively so, e.g. not
5860+
* super, wonderful, etc..
5861+
*/
5862+
function quux () {
5863+
5864+
}
5865+
// Options: [{"abbreviations":["etc","e.g.","i.e."]}]
5866+
5867+
5868+
**
5869+
* Do not have dynamic content; e.g. homepage. Here a simple unique id
5870+
* suffices.
5871+
*/
5872+
function quux () {
5873+
5874+
}
5875+
// Options: [{"abbreviations":["etc","e.g.","i.e."]}]
57075876
````
57085877
57095878

src/rules/requireDescriptionCompleteSentence.js

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@ const extractParagraphs = (text) => {
1818
}).reverse();
1919
};
2020

21-
const extractSentences = (text) => {
21+
const extractSentences = (text, abbreviationsRegex) => {
2222
const txt = text
2323

2424
// Remove all {} tags.
25-
.replace(/\{[\s\S]*?\}\s*/gu, '');
25+
.replace(/\{[\s\S]*?\}\s*/gu, '')
26+
27+
// Remove custom abbreviations
28+
.replace(abbreviationsRegex, '');
2629

2730
const sentenceEndGrouping = /([.?!])(?:\s+|$)/u;
2831
const puncts = RegExtras(sentenceEndGrouping).map(txt, (punct) => {
@@ -67,15 +70,15 @@ const capitalize = (str) => {
6770
return str.charAt(0).toUpperCase() + str.slice(1);
6871
};
6972

70-
const validateDescription = (description, reportOrig, jsdocNode, sourceCode, tag) => {
73+
const validateDescription = (description, reportOrig, jsdocNode, abbreviationsRegex, sourceCode, tag) => {
7174
if (!description) {
7275
return false;
7376
}
7477

7578
const paragraphs = extractParagraphs(description);
7679

7780
return paragraphs.some((paragraph, parIdx) => {
78-
const sentences = extractSentences(paragraph);
81+
const sentences = extractSentences(paragraph, abbreviationsRegex);
7982

8083
const fix = (fixer) => {
8184
let text = sourceCode.getText(jsdocNode);
@@ -87,7 +90,8 @@ const validateDescription = (description, reportOrig, jsdocNode, sourceCode, tag
8790
}
8891

8992
for (const sentence of sentences.filter((sentence_) => {
90-
return !(/^\s*$/u).test(sentence_) && !isCapitalized(sentence_) && !isTable(sentence_);
93+
return !(/^\s*$/u).test(sentence_) && !isCapitalized(sentence_) &&
94+
!isTable(sentence_);
9195
})) {
9296
const beginning = sentence.split('\n')[0];
9397

@@ -119,13 +123,15 @@ const validateDescription = (description, reportOrig, jsdocNode, sourceCode, tag
119123
report('Sentence should start with an uppercase character.', fix, tag);
120124
}
121125

122-
if (!/[.!?|]$/u.test(paragraph)) {
126+
const paragraphNoAbbreviations = paragraph.replace(abbreviationsRegex, '');
127+
128+
if (!/[.!?|]$/u.test(paragraphNoAbbreviations)) {
123129
report('Sentence must end with a period.', fix, tag);
124130

125131
return true;
126132
}
127133

128-
if (!isNewLinePrecededByAPeriod(paragraph)) {
134+
if (!isNewLinePrecededByAPeriod(paragraphNoAbbreviations)) {
129135
report('A line of text is started with an uppercase character, but preceding line does not end the sentence.', null, tag);
130136

131137
return true;
@@ -137,13 +143,25 @@ const validateDescription = (description, reportOrig, jsdocNode, sourceCode, tag
137143

138144
export default iterateJsdoc(({
139145
sourceCode,
146+
context,
140147
jsdoc,
141148
report,
142149
jsdocNode,
143150
utils,
144151
}) => {
152+
const options = context.options[0] || {};
153+
const {
154+
abbreviations = [],
155+
} = options;
156+
157+
const abbreviationsRegex = abbreviations.length ?
158+
new RegExp('\\b' + abbreviations.map((abbreviation) => {
159+
return _.escapeRegExp(abbreviation.replace(/\.$/g, '') + '.');
160+
}).join('|') + '(?:$|\\s)', 'gu') :
161+
'';
162+
145163
if (!jsdoc.tags ||
146-
validateDescription(jsdoc.description, report, jsdocNode, sourceCode, {
164+
validateDescription(jsdoc.description, report, jsdocNode, abbreviationsRegex, sourceCode, {
147165
line: jsdoc.line + 1,
148166
})
149167
) {
@@ -152,7 +170,7 @@ export default iterateJsdoc(({
152170

153171
utils.forEachPreferredTag('description', (matchingJsdocTag) => {
154172
const description = `${matchingJsdocTag.name} ${matchingJsdocTag.description}`.trim();
155-
validateDescription(description, report, jsdocNode, sourceCode, matchingJsdocTag);
173+
validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, matchingJsdocTag);
156174
}, true);
157175

158176
const {tagsWithNames} = utils.getTagsByType(jsdoc.tags);
@@ -168,13 +186,13 @@ export default iterateJsdoc(({
168186
tagsWithNames.some((tag) => {
169187
const description = _.trimStart(tag.description, '- ');
170188

171-
return validateDescription(description, report, jsdocNode, sourceCode, tag);
189+
return validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, tag);
172190
});
173191

174192
tagsWithoutNames.some((tag) => {
175193
const description = `${tag.name} ${tag.description}`.trim();
176194

177-
return validateDescription(description, report, jsdocNode, sourceCode, tag);
195+
return validateDescription(description, report, jsdocNode, abbreviationsRegex, sourceCode, tag);
178196
});
179197
}, {
180198
iterateAllJsdocs: true,
@@ -184,6 +202,12 @@ export default iterateJsdoc(({
184202
{
185203
additionalProperties: false,
186204
properties: {
205+
abbreviations: {
206+
items: {
207+
type: 'string',
208+
},
209+
type: 'array',
210+
},
187211
tags: {
188212
items: {
189213
type: 'string',

0 commit comments

Comments
 (0)