@@ -314,3 +314,282 @@ test("passes for async rule", async () => {
314314
315315 expect ( report . valid ) . toBe ( true ) ;
316316} ) ;
317+
318+ test ( "returns position for type-enum error" , async ( ) => {
319+ const result = await lint ( "foo: some message" , {
320+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "feat" , "fix" ] ] ,
321+ } ) ;
322+ expect ( result . valid ) . toBe ( false ) ;
323+ expect ( result . errors ) . toHaveLength ( 1 ) ;
324+ expect ( result . errors [ 0 ] . name ) . toBe ( "type-enum" ) ;
325+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 1 , offset : 0 } ) ;
326+ expect ( result . errors [ 0 ] . end ) . toEqual ( { line : 1 , column : 4 , offset : 3 } ) ;
327+ } ) ;
328+
329+ test ( "returns position for type-case error" , async ( ) => {
330+ const result = await lint ( "FIX: some message" , {
331+ "type-case" : [ RuleConfigSeverity . Error , "always" , "lower-case" ] ,
332+ } ) ;
333+ expect ( result . valid ) . toBe ( false ) ;
334+ expect ( result . errors [ 0 ] . name ) . toBe ( "type-case" ) ;
335+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 1 , offset : 0 } ) ;
336+ expect ( result . errors [ 0 ] . end ) . toEqual ( { line : 1 , column : 4 , offset : 3 } ) ;
337+ } ) ;
338+
339+ test ( "returns position for type-max-length error" , async ( ) => {
340+ const longType = "toolongtype" ;
341+ const result = await lint ( `${ longType } : some message` , {
342+ "type-max-length" : [ RuleConfigSeverity . Error , "always" , 5 ] ,
343+ } ) ;
344+ expect ( result . valid ) . toBe ( false ) ;
345+ expect ( result . errors [ 0 ] . name ) . toBe ( "type-max-length" ) ;
346+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 1 , offset : 0 } ) ;
347+ expect ( result . errors [ 0 ] . end ) . toEqual ( {
348+ line : 1 ,
349+ column : longType . length + 1 ,
350+ offset : longType . length ,
351+ } ) ;
352+ } ) ;
353+
354+ test ( "returns position for scope-enum error" , async ( ) => {
355+ const result = await lint ( "feat(badscope): some message" , {
356+ "scope-enum" : [ RuleConfigSeverity . Error , "always" , [ "cli" , "core" ] ] ,
357+ } ) ;
358+ expect ( result . valid ) . toBe ( false ) ;
359+ expect ( result . errors [ 0 ] . name ) . toBe ( "scope-enum" ) ;
360+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 6 , offset : 5 } ) ;
361+ expect ( result . errors [ 0 ] . end ) . toEqual ( { line : 1 , column : 14 , offset : 13 } ) ;
362+ } ) ;
363+
364+ test ( "returns position for scope-case error" , async ( ) => {
365+ const result = await lint ( "feat(SCOPE): some message" , {
366+ "scope-case" : [ RuleConfigSeverity . Error , "always" , "lower-case" ] ,
367+ } ) ;
368+ expect ( result . valid ) . toBe ( false ) ;
369+ expect ( result . errors [ 0 ] . name ) . toBe ( "scope-case" ) ;
370+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 6 , offset : 5 } ) ;
371+ expect ( result . errors [ 0 ] . end ) . toEqual ( { line : 1 , column : 11 , offset : 10 } ) ;
372+ } ) ;
373+
374+ test ( "returns position for subject-max-length error" , async ( ) => {
375+ const longSubject =
376+ "this is a very long subject that exceeds the maximum allowed characters" ;
377+ const result = await lint ( `feat: ${ longSubject } ` , {
378+ "subject-max-length" : [ RuleConfigSeverity . Error , "always" , 20 ] ,
379+ } ) ;
380+ expect ( result . valid ) . toBe ( false ) ;
381+ expect ( result . errors [ 0 ] . name ) . toBe ( "subject-max-length" ) ;
382+ expect ( result . errors [ 0 ] . start ?. line ) . toBe ( 1 ) ;
383+ expect ( result . errors [ 0 ] . start ?. column ) . toBeGreaterThan ( 5 ) ;
384+ expect ( result . errors [ 0 ] . end ?. line ) . toBe ( 1 ) ;
385+ } ) ;
386+
387+ test ( "returns position for subject-full-stop error" , async ( ) => {
388+ const result = await lint ( "feat: some message." , {
389+ "subject-full-stop" : [ RuleConfigSeverity . Error , "never" , "." ] ,
390+ } ) ;
391+ expect ( result . valid ) . toBe ( false ) ;
392+ expect ( result . errors [ 0 ] . name ) . toBe ( "subject-full-stop" ) ;
393+ expect ( result . errors [ 0 ] . start ?. line ) . toBe ( 1 ) ;
394+ expect ( result . errors [ 0 ] . start ?. column ) . toBeGreaterThan ( 5 ) ;
395+ } ) ;
396+
397+ test ( "returns position for header-max-length error" , async ( ) => {
398+ const longHeader =
399+ "feat: this is a very long header that definitely exceeds the maximum allowed character limit for commit messages" ;
400+ const result = await lint ( longHeader , {
401+ "header-max-length" : [ RuleConfigSeverity . Error , "always" , 50 ] ,
402+ } ) ;
403+ expect ( result . valid ) . toBe ( false ) ;
404+ expect ( result . errors [ 0 ] . name ) . toBe ( "header-max-length" ) ;
405+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 1 , offset : 0 } ) ;
406+ expect ( result . errors [ 0 ] . end ) . toEqual ( {
407+ line : 1 ,
408+ column : longHeader . length + 1 ,
409+ offset : longHeader . length ,
410+ } ) ;
411+ } ) ;
412+
413+ test ( "returns position for body-max-line-length error" , async ( ) => {
414+ const longBodyLine =
415+ "this is a body line that is way too long and exceeds the maximum allowed character limit of one hundred characters for each line in the body" ;
416+ const result = await lint ( `feat: some message\n\n${ longBodyLine } ` , {
417+ "body-max-line-length" : [ RuleConfigSeverity . Error , "always" , 80 ] ,
418+ } ) ;
419+ expect ( result . valid ) . toBe ( false ) ;
420+ expect ( result . errors [ 0 ] . name ) . toBe ( "body-max-line-length" ) ;
421+ expect ( result . errors [ 0 ] . start ?. line ) . toBe ( 2 ) ;
422+ } ) ;
423+
424+ test ( "returns no position for rules without position support" , async ( ) => {
425+ const result = await lint ( "somehting #1" , {
426+ "references-empty" : [ RuleConfigSeverity . Error , "always" ] ,
427+ } ) ;
428+ expect ( result . valid ) . toBe ( false ) ;
429+ expect ( result . errors [ 0 ] . name ) . toBe ( "references-empty" ) ;
430+ expect ( result . errors [ 0 ] . start ) . toBeUndefined ( ) ;
431+ expect ( result . errors [ 0 ] . end ) . toBeUndefined ( ) ;
432+ } ) ;
433+
434+ test ( "returns correct position for valid commit (no position needed)" , async ( ) => {
435+ const result = await lint ( "feat: add new feature" , {
436+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "feat" , "fix" ] ] ,
437+ } ) ;
438+ expect ( result . valid ) . toBe ( true ) ;
439+ expect ( result . errors ) . toHaveLength ( 0 ) ;
440+ } ) ;
441+
442+ test ( "returns position for subject-full-stop error" , async ( ) => {
443+ const result = await lint ( "feat: some message." , {
444+ "subject-full-stop" : [ RuleConfigSeverity . Error , "never" , "." ] ,
445+ } ) ;
446+ expect ( result . valid ) . toBe ( false ) ;
447+ expect ( result . errors [ 0 ] . name ) . toBe ( "subject-full-stop" ) ;
448+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
449+ expect ( result . errors [ 0 ] . start ?. line ) . toBe ( 1 ) ;
450+ expect ( result . errors [ 0 ] . end ) . toBeDefined ( ) ;
451+ } ) ;
452+
453+ test ( "returns position for header-max-length error" , async ( ) => {
454+ const longHeader =
455+ "feat: this is a very long header that definitely exceeds the maximum allowed character limit for commit messages" ;
456+ const result = await lint ( longHeader , {
457+ "header-max-length" : [ RuleConfigSeverity . Error , "always" , 50 ] ,
458+ } ) ;
459+ expect ( result . valid ) . toBe ( false ) ;
460+ expect ( result . errors [ 0 ] . name ) . toBe ( "header-max-length" ) ;
461+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 1 , offset : 0 } ) ;
462+ expect ( result . errors [ 0 ] . end ) . toEqual ( {
463+ line : 1 ,
464+ column : longHeader . length + 1 ,
465+ offset : longHeader . length ,
466+ } ) ;
467+ } ) ;
468+
469+ test ( "returns position for body-max-line-length error" , async ( ) => {
470+ const longBodyLine =
471+ "this is a body line that is way too long and exceeds the maximum allowed character limit" ;
472+ const result = await lint ( `feat: some message\n\n${ longBodyLine } ` , {
473+ "body-max-line-length" : [ RuleConfigSeverity . Error , "always" , 50 ] ,
474+ } ) ;
475+ expect ( result . valid ) . toBe ( false ) ;
476+ expect ( result . errors [ 0 ] . name ) . toBe ( "body-max-line-length" ) ;
477+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
478+ expect ( result . errors [ 0 ] . start ?. line ) . toBe ( 2 ) ;
479+ } ) ;
480+
481+ test ( "returns position for header-max-length error" , async ( ) => {
482+ const longHeader =
483+ "feat: this is a very long header that definitely exceeds the maximum allowed character limit for commit messages" ;
484+ const result = await lint ( longHeader , {
485+ "header-max-length" : [ RuleConfigSeverity . Error , "always" , 50 ] ,
486+ } ) ;
487+ expect ( result . valid ) . toBe ( false ) ;
488+ expect ( result . errors [ 0 ] . name ) . toBe ( "header-max-length" ) ;
489+ expect ( result . errors [ 0 ] . start ) . toEqual ( { line : 1 , column : 1 , offset : 0 } ) ;
490+ expect ( result . errors [ 0 ] . end ) . toEqual ( {
491+ line : 1 ,
492+ column : longHeader . length + 1 ,
493+ offset : longHeader . length ,
494+ } ) ;
495+ } ) ;
496+
497+ test ( "returns position for body-max-line-length error" , async ( ) => {
498+ const longBodyLine =
499+ "this is a body line that is way too long and exceeds the maximum allowed character limit" ;
500+ const result = await lint ( `feat: some message\n\n${ longBodyLine } ` , {
501+ "body-max-line-length" : [ RuleConfigSeverity . Error , "always" , 50 ] ,
502+ } ) ;
503+ expect ( result . valid ) . toBe ( false ) ;
504+ expect ( result . errors [ 0 ] . name ) . toBe ( "body-max-line-length" ) ;
505+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
506+ expect ( result . errors [ 0 ] . start ?. line ) . toBe ( 2 ) ;
507+ } ) ;
508+
509+ test ( "returns no position for rules without position support" , async ( ) => {
510+ const result = await lint ( "somehting #1" , {
511+ "references-empty" : [ RuleConfigSeverity . Error , "always" ] ,
512+ } ) ;
513+ expect ( result . valid ) . toBe ( false ) ;
514+ expect ( result . errors [ 0 ] . name ) . toBe ( "references-empty" ) ;
515+ expect ( result . errors [ 0 ] . start ) . toBeUndefined ( ) ;
516+ expect ( result . errors [ 0 ] . end ) . toBeUndefined ( ) ;
517+ } ) ;
518+
519+ test ( "returns position when type is provided" , async ( ) => {
520+ const result = await lint ( "feat: some message" , {
521+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "bar" ] ] ,
522+ } ) ;
523+ expect ( result . valid ) . toBe ( false ) ;
524+ expect ( result . errors [ 0 ] . name ) . toBe ( "type-enum" ) ;
525+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
526+ } ) ;
527+
528+ test ( "returns position when scope is provided" , async ( ) => {
529+ const result = await lint ( "feat(myscope): some message" , {
530+ "scope-enum" : [ RuleConfigSeverity . Error , "always" , [ "other" ] ] ,
531+ } ) ;
532+ expect ( result . valid ) . toBe ( false ) ;
533+ expect ( result . errors [ 0 ] . name ) . toBe ( "scope-enum" ) ;
534+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
535+ } ) ;
536+
537+ test ( "handles duplicate text in message - uses first occurrence" , async ( ) => {
538+ const result = await lint ( "fix: test test" , {
539+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "feat" , "fix" ] ] ,
540+ } ) ;
541+ expect ( result . valid ) . toBe ( true ) ;
542+ expect ( result . errors ) . toHaveLength ( 0 ) ;
543+ } ) ;
544+
545+ test ( "returns correct position for valid commit (no position needed)" , async ( ) => {
546+ const result = await lint ( "feat: add new feature" , {
547+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "feat" , "fix" ] ] ,
548+ } ) ;
549+ expect ( result . valid ) . toBe ( true ) ;
550+ expect ( result . errors ) . toHaveLength ( 0 ) ;
551+ } ) ;
552+
553+ test ( "returns no position for rules without position support" , async ( ) => {
554+ const result = await lint ( "somehting #1" , {
555+ "references-empty" : [ RuleConfigSeverity . Error , "always" ] ,
556+ } ) ;
557+ expect ( result . valid ) . toBe ( false ) ;
558+ expect ( result . errors [ 0 ] . name ) . toBe ( "references-empty" ) ;
559+ expect ( result . errors [ 0 ] . start ) . toBeUndefined ( ) ;
560+ expect ( result . errors [ 0 ] . end ) . toBeUndefined ( ) ;
561+ } ) ;
562+
563+ test ( "returns position when type is provided" , async ( ) => {
564+ const result = await lint ( "feat: some message" , {
565+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "bar" ] ] ,
566+ } ) ;
567+ expect ( result . valid ) . toBe ( false ) ;
568+ expect ( result . errors [ 0 ] . name ) . toBe ( "type-enum" ) ;
569+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
570+ } ) ;
571+
572+ test ( "returns position when scope is provided" , async ( ) => {
573+ const result = await lint ( "feat(myscope): some message" , {
574+ "scope-enum" : [ RuleConfigSeverity . Error , "always" , [ "other" ] ] ,
575+ } ) ;
576+ expect ( result . valid ) . toBe ( false ) ;
577+ expect ( result . errors [ 0 ] . name ) . toBe ( "scope-enum" ) ;
578+ expect ( result . errors [ 0 ] . start ) . toBeDefined ( ) ;
579+ } ) ;
580+
581+ test ( "handles duplicate text in message - uses first occurrence" , async ( ) => {
582+ const result = await lint ( "fix: test test" , {
583+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "feat" , "fix" ] ] ,
584+ } ) ;
585+ expect ( result . valid ) . toBe ( true ) ;
586+ expect ( result . errors ) . toHaveLength ( 0 ) ;
587+ } ) ;
588+
589+ test ( "returns correct position for valid commit (no position needed)" , async ( ) => {
590+ const result = await lint ( "feat: add new feature" , {
591+ "type-enum" : [ RuleConfigSeverity . Error , "always" , [ "feat" , "fix" ] ] ,
592+ } ) ;
593+ expect ( result . valid ) . toBe ( true ) ;
594+ expect ( result . errors ) . toHaveLength ( 0 ) ;
595+ } ) ;
0 commit comments