@@ -28,6 +28,10 @@ interface ICommitRequirement {
2828 validate ( commit : IRawConventionalCommit , options ?: IConventionalCommitOptions ) : DiagnosticsMessage [ ] ;
2929}
3030
31+ function isNoun ( str : string ) : boolean {
32+ return ! str . trim ( ) . includes ( " " ) && ! / [ ^ a - z ] / i. test ( str . trim ( ) ) ;
33+ }
34+
3135function highlightString ( str : string , substring : string | string [ ] ) : string {
3236 // Ensure that we handle both single and multiple substrings equally
3337 if ( ! Array . isArray ( substring ) ) substring = [ substring ] ;
@@ -42,22 +46,37 @@ function createError(
4246 commit : IRawConventionalCommit ,
4347 description : string ,
4448 highlight : string | string [ ] ,
45- type : "type" | "scope" | "breaking" | "seperator" | "spacing" | "description"
49+ type : keyof IRawConventionalCommit ,
50+ whitespace = false
4651) : DiagnosticsMessage {
47- const data = commit [ type . toString ( ) as keyof IRawConventionalCommit ] as IConventionalCommitElement ;
52+ const element = commit [ type ] as IConventionalCommitElement ;
53+ let hintIndex = element . index ;
54+ let hintLength = element . value ?. trimEnd ( ) . length ?? 1 ;
55+
56+ if ( whitespace ) {
57+ let prevElement : IConventionalCommitElement | undefined = undefined ;
58+ for ( const [ _key , value ] of Object . entries ( commit ) ) {
59+ if ( value . index > ( prevElement ?. index ?? 0 ) && value . index < element . index ) {
60+ prevElement = value ;
61+ }
62+ }
63+
64+ hintIndex = prevElement ? prevElement . index + ( prevElement . value ?. trimEnd ( ) . length ?? 1 ) : 1 ;
65+ hintLength = ( prevElement ?. value ?. length ?? 1 ) - ( prevElement ?. value ?. trimEnd ( ) . length ?? 1 ) ;
66+ }
4867
4968 return DiagnosticsMessage . createError ( commit . commit . hash , {
5069 text : highlightString ( description , highlight ) ,
5170 linenumber : 1 ,
52- column : data . index ,
71+ column : hintIndex ,
5372 } )
5473 . setContext (
5574 1 ,
5675 commit . commit . body !== undefined && commit . commit . body . split ( "\n" ) . length >= 1
5776 ? [ commit . commit . subject , "" , ...commit . commit . body . split ( "\n" ) ]
5877 : [ commit . commit . subject ]
5978 )
60- . addFixitHint ( FixItHint . create ( { index : data . index , length : data . value ?. length ?? 1 } ) ) ;
79+ . addFixitHint ( FixItHint . create ( { index : hintIndex , length : hintLength === 0 ? 1 : hintLength } ) ) ;
6180}
6281
6382/**
@@ -69,44 +88,47 @@ class CC01 implements ICommitRequirement {
6988 description =
7089 "Commits MUST be prefixed with a type, which consists of a noun, feat, fix, etc., followed by the OPTIONAL scope, OPTIONAL !, and REQUIRED terminal colon and space." ;
7190
72- // eslint-disable-next-line @typescript-eslint/no-unused-vars
7391 validate ( commit : IRawConventionalCommit , _options ?: IConventionalCommitOptions ) : DiagnosticsMessage [ ] {
7492 const errors : DiagnosticsMessage [ ] = [ ] ;
7593
7694 // MUST be prefixed with a type
7795 if ( ! commit . type . value || commit . type . value . trim ( ) . length === 0 ) {
78- errors . push ( createError ( commit , this . description , "MUST be prefixed with a type" , "type" ) ) ;
96+ // Validated with EC-02
7997 } else {
8098 // Ensure that we have a noun
81- if ( commit . type . value . trim ( ) . includes ( " " ) || / [ ^ a - z ] / i . test ( commit . type . value . trim ( ) ) )
99+ if ( ! isNoun ( commit . type . value ) )
82100 errors . push ( createError ( commit , this . description , "which consists of a noun" , "type" ) ) ;
83101 // Validate for spacing after the type
84102 if ( commit . type . value . trim ( ) !== commit . type . value ) {
85103 if ( commit . scope . value )
86- errors . push ( createError ( commit , this . description , "followed by the OPTIONAL scope" , "scope" ) ) ;
104+ errors . push ( createError ( commit , this . description , "followed by the OPTIONAL scope" , "scope" , true ) ) ;
87105 else if ( commit . breaking . value )
88- errors . push ( createError ( commit , this . description , [ "followed by the" , "OPTIONAL !" ] , "breaking" ) ) ;
106+ errors . push ( createError ( commit , this . description , [ "followed by the" , "OPTIONAL !" ] , "breaking" , true ) ) ;
89107 else
90108 errors . push (
91- createError ( commit , this . description , [ "followed by the" , "REQUIRED terminal colon" ] , "seperator" )
109+ createError ( commit , this . description , [ "followed by the" , "REQUIRED terminal colon" ] , "seperator" , true )
92110 ) ;
93111 }
94112
95113 // Validate for spacing after the scope, breaking and seperator
96- if ( commit . scope . value && commit . scope . value . trim ( ) !== commit . scope . value )
97- errors . push ( createError ( commit , this . description , "followed by the OPTIONAL scope" , "scope" ) ) ;
114+ if ( commit . scope . value && commit . scope . value . trim ( ) !== commit . scope . value ) {
115+ if ( commit . breaking . value )
116+ errors . push ( createError ( commit , this . description , [ "followed by the" , "OPTIONAL !" ] , "breaking" , true ) ) ;
117+ else
118+ errors . push (
119+ createError ( commit , this . description , [ "followed by the" , "REQUIRED terminal colon" ] , "seperator" , true )
120+ ) ;
121+ }
122+
98123 if ( commit . breaking . value && commit . breaking . value . trim ( ) !== commit . breaking . value )
99- errors . push ( createError ( commit , this . description , [ "followed by the" , "OPTIONAL !" ] , "breaking" ) ) ;
100- if ( commit . seperator . value && commit . seperator . value . trim ( ) !== commit . seperator . value )
101- errors . push ( createError ( commit , this . description , [ "followed by the" , "REQUIRED terminal colon" ] , "seperator" ) ) ;
124+ errors . push (
125+ createError ( commit , this . description , [ "followed by the" , "REQUIRED terminal colon" ] , " seperator" , true )
126+ ) ;
102127 }
103128
104129 // MUST have a terminal colon
105130 if ( ! commit . seperator . value )
106131 errors . push ( createError ( commit , this . description , [ "followed by the" , "REQUIRED terminal colon" ] , "seperator" ) ) ;
107- // MUST have a space after the terminal colon
108- else if ( ! commit . spacing . value || commit . spacing . value . length !== 1 )
109- errors . push ( createError ( commit , this . description , [ "followed by the" , "REQUIRED" , "space" ] , "spacing" ) ) ;
110132
111133 return errors ;
112134 }
@@ -121,15 +143,13 @@ class CC04 implements ICommitRequirement {
121143 description =
122144 "A scope MAY be provided after a type. A scope MUST consist of a noun describing a section of the codebase surrounded by parenthesis, e.g., fix(parser):" ;
123145
124- // eslint-disable-next-line @typescript-eslint/no-unused-vars
125146 validate ( commit : IRawConventionalCommit , _options ?: IConventionalCommitOptions ) : DiagnosticsMessage [ ] {
126147 const errors : DiagnosticsMessage [ ] = [ ] ;
127148
128149 if (
129150 commit . scope . value &&
130- ( commit . scope . value . includes ( " " ) ||
131- commit . scope . value === "()" ||
132- / [ ^ a - z ] / i. test ( commit . scope . value . substring ( 1 , commit . scope . value . length - 1 ) ) )
151+ ( commit . scope . value === "()" ||
152+ ! isNoun ( commit . scope . value . trimEnd ( ) . substring ( 1 , commit . scope . value . trimEnd ( ) . length - 1 ) ) )
133153 ) {
134154 errors . push ( createError ( commit , this . description , "A scope MUST consist of a noun" , "scope" ) ) ;
135155 }
@@ -148,18 +168,21 @@ class CC05 implements ICommitRequirement {
148168 description =
149169 "A description MUST immediately follow the colon and space after the type/scope prefix. The description is a short summary of the code changes, e.g., fix: array parsing issue when multiple spaces were contained in string." ;
150170
151- // eslint-disable-next-line @typescript-eslint/no-unused-vars
152171 validate ( commit : IRawConventionalCommit , _options ?: IConventionalCommitOptions ) : DiagnosticsMessage [ ] {
153172 const errors : DiagnosticsMessage [ ] = [ ] ;
154173
155174 if ( ! commit . seperator . value ) return errors ;
156- if ( ! commit . spacing . value || commit . spacing . value . length > 1 || ! commit . description . value )
175+ if (
176+ commit . description . value === undefined ||
177+ commit . seperator . value . length - commit . seperator . value . trim ( ) . length !== 1
178+ )
157179 errors . push (
158180 createError (
159181 commit ,
160182 this . description ,
161183 "A description MUST immediately follow the colon and space" ,
162- "description"
184+ "description" ,
185+ true
163186 )
164187 ) ;
165188
@@ -209,7 +232,16 @@ class EC02 implements ICommitRequirement {
209232 ", "
210233 ) } ).`;
211234
212- if ( commit . type . value !== undefined && expectedTypes . includes ( commit . type . value ) ) return [ ] ;
235+ if (
236+ commit . type . value === undefined ||
237+ ! isNoun ( commit . type . value ) ||
238+ expectedTypes . includes ( commit . type . value . trimEnd ( ) )
239+ )
240+ return [ ] ;
241+
242+ if ( commit . type . value . trim ( ) . length === 0 ) {
243+ return [ createError ( commit , this . description , "prefixed with a type" , "type" ) ] ;
244+ }
213245
214246 return [
215247 createError (
0 commit comments