Skip to content

Commit b2ece9f

Browse files
authored
feat(multiline-comment-style): add support for exclamation comments (#876)
1 parent bac5f3f commit b2ece9f

File tree

4 files changed

+206
-7
lines changed

4 files changed

+206
-7
lines changed

packages/eslint-plugin/rules/multiline-comment-style/README.md

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,64 @@ Many style guides require a particular style for comments that span multiple lin
1111

1212
This rule aims to enforce a particular style for multiline comments.
1313

14+
Other than regular JavaScript comments, the following types of comments are recognized by this rule:
15+
16+
1. **[JSDoc comments](https://jsdoc.app/)**
17+
18+
These are comments which start with a `/**` sequence. They are used to explain the functionality of functions, classes, methods, and other code elements, making the code self-documenting.
19+
20+
Example:
21+
22+
```js
23+
/**
24+
* Represents a book.
25+
* @constructor
26+
* @param {string} title - The title of the book.
27+
* @param {string} author - The author of the book.
28+
*/
29+
```
30+
31+
2. **Exclamation comments**
32+
33+
These are comments which start with a `/*!` sequence. They are conventionally used in JavaScript to indicate a _preserved comment_ or _important comment_. An exclamation comment signals its consumer that the comment should be preserved during processing.
34+
35+
Exclamation comments are often used for including copyright information, attribution, or other important metadata that needs to remain in the code.
36+
37+
Example:
38+
39+
```js
40+
/*!
41+
* My JavaScript Library
42+
* Copyright (c) 2025 John Doe
43+
*
44+
* Licensed under the MIT license.
45+
*/
46+
```
47+
48+
3. **Directive comments**
49+
50+
These are special comments that convey instructions or metadata to tools like linters, compilers (e.g., TypeScript), or build systems. They are not standard JavaScript comments meant for human readability, but rather specific syntax understood by particular tools to alter their behavior.
51+
52+
Example:
53+
54+
```js
55+
// @ts-ignore
56+
or
57+
/* eslint-disable */
58+
```
59+
1460
## Options
1561

1662
This rule has a string option, which can have one of the following values:
1763

1864
- `"starred-block"` (default): Disallows consecutive line comments in favor of block comments. Additionally, requires block comments to have an aligned `*` character before each line.
19-
- `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. This option ignores JSDoc comments.
20-
- `"separate-lines"`: Disallows block comments in favor of consecutive line comments. By default, this option ignores JSDoc comments. To also apply this rule to JSDoc comments, set the `checkJSDoc` option to `true`.
65+
- `"bare-block"`: Disallows consecutive line comments in favor of block comments, and disallows block comments from having a `"*"` character before each line. This option ignores JSDoc and Exclamation comments.
66+
- `"separate-lines"`: Disallows block comments in favor of consecutive line comments. By default, this option ignores JSDoc and Exclamation comments. To also apply this rule to JSDoc comments and or Exclamation comments, set the `checkJSDoc` and or `checkExclamation` option to `true`.
2167

2268
The rule always ignores directive comments such as `/* eslint-disable */`.
2369

70+
## Examples
71+
2472
Examples of **incorrect** code for this rule with the default `"starred-block"` option:
2573

2674
::: incorrect
@@ -179,6 +227,24 @@ foo();
179227

180228
:::
181229

230+
Examples of **incorrect** code for this rule with the `"separate-lines"` option and `checkExclamation` set to `true`:
231+
232+
::: incorrect
233+
234+
```js
235+
236+
/* eslint @stylistic/multiline-comment-style: ["error", "separate-lines", { "checkExclamation": true }] */
237+
238+
/*!
239+
* I am an exclamation comment
240+
* and I'm not allowed
241+
*/
242+
foo();
243+
244+
```
245+
246+
:::
247+
182248
## When Not To Use It
183249

184250
If you don't want to enforce a particular style for multiline comments, you can disable the rule.

packages/eslint-plugin/rules/multiline-comment-style/multiline-comment-style.test.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ run<RuleOptions, MessageIds>({
2222
* a JSDoc comment
2323
*/
2424
`,
25+
`
26+
/*!
27+
* this is
28+
* an exclamation comment
29+
*/
30+
`,
31+
`
32+
/*! this is a single line exclamation comment */
33+
`,
2534
`
2635
/* eslint semi: [
2736
"error"
@@ -234,6 +243,33 @@ run<RuleOptions, MessageIds>({
234243
`,
235244
options: ['bare-block'],
236245
},
246+
{
247+
code: `
248+
/*!
249+
* This is
250+
* an exclamation comment
251+
*/
252+
`,
253+
options: ['separate-lines'],
254+
},
255+
{
256+
code: `
257+
/*!
258+
* This is
259+
* an exclamation comment
260+
*/
261+
`,
262+
options: ['starred-block'],
263+
},
264+
{
265+
code: `
266+
/*!
267+
* This is
268+
* an exclamation comment
269+
*/
270+
`,
271+
options: ['bare-block'],
272+
},
237273
{
238274
code: `
239275
/* This is
@@ -318,6 +354,39 @@ run<RuleOptions, MessageIds>({
318354
`,
319355
options: ['separate-lines'],
320356
},
357+
{
358+
code: `
359+
/*!
360+
* Exclamation blocks
361+
* are
362+
* ignored
363+
* !
364+
*/
365+
`,
366+
options: ['bare-block'],
367+
},
368+
{
369+
code: `
370+
/*!
371+
* Exclamation blocks
372+
* are
373+
* ignored
374+
* !
375+
*/
376+
`,
377+
options: ['starred-block'],
378+
},
379+
{
380+
code: `
381+
/*!
382+
* Exclamation blocks
383+
* are
384+
* ignored
385+
* !
386+
*/
387+
`,
388+
options: ['separate-lines'],
389+
},
321390
{
322391
code: `
323392
/*
@@ -451,6 +520,34 @@ run<RuleOptions, MessageIds>({
451520
`,
452521
errors: [{ messageId: 'startNewline', line: 2 }],
453522
},
523+
{
524+
code: `
525+
/*! this Exclamation comment
526+
* is missing a newline
527+
* at the start
528+
*/
529+
`,
530+
output: `
531+
/*!
532+
* this Exclamation comment
533+
* is missing a newline
534+
* at the start
535+
*/
536+
`,
537+
errors: [{ messageId: 'startNewline', line: 2 }],
538+
},
539+
{
540+
code: `
541+
/*! this Exclamation comment is missing a newline at the start
542+
*/
543+
`,
544+
output: `
545+
/*!
546+
* this Exclamation comment is missing a newline at the start
547+
*/
548+
`,
549+
errors: [{ messageId: 'startNewline', line: 2 }],
550+
},
454551
{
455552
code: `
456553
/*
@@ -632,6 +729,20 @@ run<RuleOptions, MessageIds>({
632729
options: ['separate-lines', { checkJSDoc: true }],
633730
errors: [{ messageId: 'expectedLines', line: 2 }],
634731
},
732+
{
733+
code: `
734+
/*!
735+
* Exclamation
736+
* Comment
737+
*/
738+
`,
739+
output: `
740+
// Exclamation
741+
// Comment
742+
`,
743+
options: ['separate-lines', { checkExclamation: true }],
744+
errors: [{ messageId: 'expectedLines', line: 2 }],
745+
},
635746
{
636747
code: `
637748
/* foo

packages/eslint-plugin/rules/multiline-comment-style/multiline-comment-style.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ export default createRule<RuleOptions, MessageIds>({
4343
checkJSDoc: {
4444
type: 'boolean',
4545
},
46+
checkExclamation: {
47+
type: 'boolean',
48+
},
4649
},
4750
additionalProperties: false,
4851
},
@@ -67,6 +70,7 @@ export default createRule<RuleOptions, MessageIds>({
6770
const option = context.options[0] || 'starred-block'
6871
const params = context.options[1] || {}
6972
const checkJSDoc = !!params.checkJSDoc
73+
const checkExclamation = !!params.checkExclamation
7074

7175
// ----------------------------------------------------------------------
7276
// Helpers
@@ -112,6 +116,22 @@ export default createRule<RuleOptions, MessageIds>({
112116
&& isWhiteSpaces(lines.at(-1)!)
113117
}
114118

119+
/**
120+
* Checks if a comment group is in exclamation form.
121+
* @param firstComment A group of comments, containing either multiple line comments or a single block comment.
122+
* @returns Whether or not the comment group is in exclamation form.
123+
*/
124+
function isExclamationComment([firstComment]: Token[]): boolean {
125+
if (firstComment.type !== 'Block')
126+
return false
127+
128+
const lines = firstComment.value.split(LINEBREAK_MATCHER)
129+
130+
return /^!\s*$/u.test(lines[0])
131+
&& lines.slice(1, -1).every(line => /^\s* /u.test(line))
132+
&& isWhiteSpaces(lines.at(-1)!)
133+
}
134+
115135
/**
116136
* Processes a comment group that is currently in separate-line form, calculating the offset for each line.
117137
* @param commentGroup A group of comments containing multiple line comments.
@@ -275,8 +295,8 @@ export default createRule<RuleOptions, MessageIds>({
275295
const expectedLeadingWhitespace = getInitialOffset(firstComment)
276296
const expectedLinePrefix = `${expectedLeadingWhitespace} *`
277297

278-
if (!/^\*?\s*$/u.test(lines[0])) {
279-
const start = firstComment.value.startsWith('*') ? firstComment.range[0] + 1 : firstComment.range[0]
298+
if (!/^[*!]?\s*$/u.test(lines[0])) {
299+
const start = /^[*!]/.test(firstComment.value) ? firstComment.range[0] + 1 : firstComment.range[0]
280300

281301
context.report({
282302
loc: {
@@ -352,13 +372,14 @@ export default createRule<RuleOptions, MessageIds>({
352372
const [firstComment] = commentGroup
353373

354374
const isJSDoc = isJSDocComment(commentGroup)
375+
const isExclamation = isExclamationComment(commentGroup)
355376

356-
if (firstComment.type !== 'Block' || (!checkJSDoc && isJSDoc))
377+
if (firstComment.type !== 'Block' || (!checkJSDoc && isJSDoc) || (!checkExclamation && isExclamation))
357378
return
358379

359380
let commentLines = getCommentLines(commentGroup)
360381

361-
if (isJSDoc)
382+
if (isJSDoc || isExclamation)
362383
commentLines = commentLines.slice(1, commentLines.length - 1)
363384

364385
const tokenAfter = sourceCode.getTokenAfter(firstComment, { includeComments: true })
@@ -378,7 +399,7 @@ export default createRule<RuleOptions, MessageIds>({
378399
})
379400
},
380401
'bare-block': function (commentGroup: Token[]) {
381-
if (isJSDocComment(commentGroup))
402+
if (isJSDocComment(commentGroup) || isExclamationComment(commentGroup))
382403
return
383404

384405
const [firstComment] = commentGroup

packages/eslint-plugin/rules/multiline-comment-style/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export type MultilineCommentStyleSchema0
1111
'separate-lines',
1212
{
1313
checkJSDoc?: boolean
14+
checkExclamation?: boolean
1415
},
1516
]
1617

0 commit comments

Comments
 (0)