@@ -206,8 +206,8 @@ function blockTag(
206
206
const tagName = aliasedTags . get ( blockTag . text ) || blockTag . text ;
207
207
208
208
let content : CommentDisplayPart [ ] ;
209
- if ( tagName === "@example" && config . jsDocCompatibility . exampleTag ) {
210
- content = exampleBlockContent ( comment , lexer , config , warning ) ;
209
+ if ( tagName === "@example" ) {
210
+ return exampleBlock ( comment , lexer , config , warning ) ;
211
211
} else if (
212
212
[ "@default" , "@defaultValue" ] . includes ( tagName ) &&
213
213
config . jsDocCompatibility . defaultTag
@@ -260,24 +260,73 @@ function defaultBlockContent(
260
260
/**
261
261
* The `@example` tag gets a special case because otherwise we will produce many warnings
262
262
* about unescaped/mismatched/missing braces in legacy JSDoc comments.
263
+ *
264
+ * In TSDoc, we also want to treat the first line of the block as the example name.
263
265
*/
264
- function exampleBlockContent (
266
+ function exampleBlock (
265
267
comment : Comment ,
266
268
lexer : LookaheadGenerator < Token > ,
267
269
config : CommentParserConfig ,
268
270
warning : ( msg : string , token : Token ) => void ,
269
- ) : CommentDisplayPart [ ] {
271
+ ) : CommentTag {
270
272
lexer . mark ( ) ;
271
273
const content = blockContent ( comment , lexer , config , ( ) => { } ) ;
272
274
const end = lexer . done ( ) || lexer . peek ( ) ;
273
275
lexer . release ( ) ;
274
276
275
277
if (
278
+ ! config . jsDocCompatibility . exampleTag ||
276
279
content . some (
277
280
( part ) => part . kind === "code" && part . text . startsWith ( "```" ) ,
278
281
)
279
282
) {
280
- return blockContent ( comment , lexer , config , warning ) ;
283
+ let exampleName = "" ;
284
+
285
+ // First line of @example block is the example name.
286
+ let warnedAboutRichNameContent = false ;
287
+ outer: while ( ( lexer . done ( ) || lexer . peek ( ) ) !== end ) {
288
+ const next = lexer . peek ( ) ;
289
+ switch ( next . kind ) {
290
+ case TokenSyntaxKind . NewLine :
291
+ lexer . take ( ) ;
292
+ break outer;
293
+ case TokenSyntaxKind . Text : {
294
+ const newline = next . text . indexOf ( "\n" ) ;
295
+ if ( newline !== - 1 ) {
296
+ exampleName += next . text . substring ( 0 , newline ) ;
297
+ next . pos += newline + 1 ;
298
+ break outer;
299
+ } else {
300
+ exampleName += lexer . take ( ) . text ;
301
+ }
302
+ break ;
303
+ }
304
+ case TokenSyntaxKind . Code :
305
+ case TokenSyntaxKind . Tag :
306
+ case TokenSyntaxKind . TypeAnnotation :
307
+ case TokenSyntaxKind . CloseBrace :
308
+ case TokenSyntaxKind . OpenBrace :
309
+ if ( ! warnedAboutRichNameContent ) {
310
+ warning (
311
+ "The first line of an example tag will be taken literally as" +
312
+ " the example name, and should only contain text." ,
313
+ lexer . peek ( ) ,
314
+ ) ;
315
+ warnedAboutRichNameContent = true ;
316
+ }
317
+ exampleName += lexer . take ( ) . text ;
318
+ break ;
319
+ default :
320
+ assertNever ( next . kind ) ;
321
+ }
322
+ }
323
+
324
+ const content = blockContent ( comment , lexer , config , warning ) ;
325
+ const tag = new CommentTag ( "@example" , content ) ;
326
+ if ( exampleName . trim ( ) ) {
327
+ tag . name = exampleName . trim ( ) ;
328
+ }
329
+ return tag ;
281
330
}
282
331
283
332
const tokens : Token [ ] = [ ] ;
@@ -293,23 +342,21 @@ function exampleBlockContent(
293
342
const caption = blockText . match ( / ^ \s * < c a p t i o n > ( .* ?) < \/ c a p t i o n > \s * ( \n | $ ) / ) ;
294
343
295
344
if ( caption ) {
296
- return [
297
- {
298
- kind : "text" ,
299
- text : caption [ 1 ] + "\n" ,
300
- } ,
345
+ const tag = new CommentTag ( "@example" , [
301
346
{
302
347
kind : "code" ,
303
348
text : makeCodeBlock ( blockText . slice ( caption [ 0 ] . length ) ) ,
304
349
} ,
305
- ] ;
350
+ ] ) ;
351
+ tag . name = caption [ 1 ] ;
352
+ return tag ;
306
353
} else {
307
- return [
354
+ return new CommentTag ( "@example" , [
308
355
{
309
356
kind : "code" ,
310
357
text : makeCodeBlock ( blockText ) ,
311
358
} ,
312
- ] ;
359
+ ] ) ;
313
360
}
314
361
}
315
362
0 commit comments