2222import { Rule } from "eslint" ;
2323import * as estree from "estree" ;
2424import { getParent , isIfStatement , isLogicalExpression } from "../utils/nodes" ;
25- import { getMainFunctionTokenLocation } from "../utils/locations" ;
25+ import {
26+ getMainFunctionTokenLocation ,
27+ getFirstToken ,
28+ getFirstTokenAfter ,
29+ report ,
30+ IssueLocation ,
31+ issueLocation ,
32+ } from "../utils/locations" ;
2633
27- const MESSAGE =
28- "Refactor this function to reduce its Cognitive Complexity from {{complexity}} to the {{threshold}} allowed." ;
2934const DEFAULT_THRESHOLD = 15 ;
3035
3136type LoopStatement =
@@ -39,16 +44,22 @@ type OptionalLocation = estree.SourceLocation | null | undefined;
3944
4045const rule : Rule . RuleModule = {
4146 meta : {
42- schema : [ { type : "integer" , minimum : 0 } ] ,
47+ schema : [
48+ { type : "integer" , minimum : 0 } ,
49+ {
50+ // internal parameter
51+ enum : [ "sonar-runtime" ] ,
52+ } ,
53+ ] ,
4354 } ,
4455 create ( context : Rule . RuleContext ) {
4556 const threshold : number = context . options [ 0 ] !== undefined ? context . options [ 0 ] : DEFAULT_THRESHOLD ;
4657
4758 /** Complexity of the current function if it is *not* considered nested to the first level function */
48- let complexityIfNotNested = 0 ;
59+ let complexityIfNotNested : ComplexityPoint [ ] = [ ] ;
4960
5061 /** Complexity of the current function if it is considered nested to the first level function */
51- let complexityIfNested = 0 ;
62+ let complexityIfNested : ComplexityPoint [ ] = [ ] ;
5263
5364 /** Current nesting level (number of enclosing control flow statements and functions) */
5465 let nesting = 0 ;
@@ -57,7 +68,7 @@ const rule: Rule.RuleModule = {
5768 let topLevelHasStructuralComplexity = false ;
5869
5970 /** Own (not including nested functions) complexity of the current top function */
60- let topLevelOwnComplexity = 0 ;
71+ let topLevelOwnComplexity : ComplexityPoint [ ] = [ ] ;
6172
6273 /** Nodes that should increase nesting level */
6374 const nestingNodes : Set < estree . Node > = new Set ( ) ;
@@ -71,8 +82,8 @@ const rule: Rule.RuleModule = {
7182 let secondLevelFunctions : Array < {
7283 node : estree . Function ;
7384 parent : estree . Node | undefined ;
74- complexityIfThisSecondaryIsTopLevel : number ;
75- complexityIfNested : number ;
85+ complexityIfThisSecondaryIsTopLevel : ComplexityPoint [ ] ;
86+ complexityIfNested : ComplexityPoint [ ] ;
7687 loc : OptionalLocation ;
7788 } > = [ ] ;
7889
@@ -138,12 +149,12 @@ const rule: Rule.RuleModule = {
138149 if ( enclosingFunctions . length === 0 ) {
139150 // top level function
140151 topLevelHasStructuralComplexity = false ;
141- topLevelOwnComplexity = 0 ;
152+ topLevelOwnComplexity = [ ] ;
142153 secondLevelFunctions = [ ] ;
143154 } else if ( enclosingFunctions . length === 1 ) {
144155 // second level function
145- complexityIfNotNested = 0 ;
146- complexityIfNested = 0 ;
156+ complexityIfNotNested = [ ] ;
157+ complexityIfNested = [ ] ;
147158 } else {
148159 nesting ++ ;
149160 nestingNodes . add ( node ) ;
@@ -158,7 +169,7 @@ const rule: Rule.RuleModule = {
158169 if ( topLevelHasStructuralComplexity ) {
159170 let totalComplexity = topLevelOwnComplexity ;
160171 secondLevelFunctions . forEach ( secondLevelFunction => {
161- totalComplexity += secondLevelFunction . complexityIfNested ;
172+ totalComplexity = totalComplexity . concat ( secondLevelFunction . complexityIfNested ) ;
162173 } ) ;
163174 checkFunction ( totalComplexity , getMainFunctionTokenLocation ( node , getParent ( context ) , context ) ) ;
164175 } else {
@@ -187,11 +198,12 @@ const rule: Rule.RuleModule = {
187198
188199 function visitIfStatement ( ifStatement : estree . IfStatement ) {
189200 const parent = getParent ( context ) ;
201+ const { loc : ifLoc } = getFirstToken ( ifStatement , context ) ;
190202 // if the current `if` statement is `else if`, do not count it in structural complexity
191203 if ( isIfStatement ( parent ) && parent . alternate === ifStatement ) {
192- addComplexity ( ) ;
204+ addComplexity ( ifLoc ) ;
193205 } else {
194- addStructuralComplexity ( ) ;
206+ addStructuralComplexity ( ifLoc ) ;
195207 }
196208
197209 // always increase nesting level inside `then` statement
@@ -202,35 +214,37 @@ const rule: Rule.RuleModule = {
202214 // - add +1 complexity
203215 if ( ifStatement . alternate && ! isIfStatement ( ifStatement . alternate ) ) {
204216 nestingNodes . add ( ifStatement . alternate ) ;
205- addComplexity ( ) ;
217+ const elseTokenLoc = getFirstTokenAfter ( ifStatement . consequent , context ) ! . loc ;
218+ addComplexity ( elseTokenLoc ) ;
206219 }
207220 }
208221
209222 function visitLoop ( loop : LoopStatement ) {
210- addStructuralComplexity ( ) ;
223+ addStructuralComplexity ( getFirstToken ( loop , context ) . loc ) ;
211224 nestingNodes . add ( loop . body ) ;
212225 }
213226
214227 function visitSwitchStatement ( switchStatement : estree . SwitchStatement ) {
215- addStructuralComplexity ( ) ;
228+ addStructuralComplexity ( getFirstToken ( switchStatement , context ) . loc ) ;
216229 for ( const switchCase of switchStatement . cases ) {
217230 nestingNodes . add ( switchCase ) ;
218231 }
219232 }
220233
221234 function visitContinueOrBreakStatement ( statement : estree . ContinueStatement | estree . BreakStatement ) {
222235 if ( statement . label ) {
223- addComplexity ( ) ;
236+ addComplexity ( getFirstToken ( statement , context ) . loc ) ;
224237 }
225238 }
226239
227240 function visitCatchClause ( catchClause : estree . CatchClause ) {
228- addStructuralComplexity ( ) ;
241+ addStructuralComplexity ( getFirstToken ( catchClause , context ) . loc ) ;
229242 nestingNodes . add ( catchClause . body ) ;
230243 }
231244
232245 function visitConditionalExpression ( conditionalExpression : estree . ConditionalExpression ) {
233- addStructuralComplexity ( ) ;
246+ const questionTokenLoc = getFirstTokenAfter ( conditionalExpression . test , context ) ! . loc ;
247+ addStructuralComplexity ( questionTokenLoc ) ;
234248 nestingNodes . add ( conditionalExpression . consequent ) ;
235249 nestingNodes . add ( conditionalExpression . alternate ) ;
236250 }
@@ -242,7 +256,8 @@ const rule: Rule.RuleModule = {
242256 let previous : estree . LogicalExpression | undefined ;
243257 for ( const current of flattenedLogicalExpressions ) {
244258 if ( ! previous || previous . operator !== current . operator ) {
245- addComplexity ( ) ;
259+ const operatorTokenLoc = getFirstTokenAfter ( logicalExpression . left , context ) ! . loc ;
260+ addComplexity ( operatorTokenLoc ) ;
246261 }
247262 previous = current ;
248263 }
@@ -257,40 +272,58 @@ const rule: Rule.RuleModule = {
257272 return [ ] ;
258273 }
259274
260- function addStructuralComplexity ( ) {
275+ function addStructuralComplexity ( location : estree . SourceLocation ) {
261276 const added = nesting + 1 ;
277+ const complexityPoint = { complexity : added , location } ;
262278 if ( enclosingFunctions . length === 1 ) {
263279 // top level function
264280 topLevelHasStructuralComplexity = true ;
265- topLevelOwnComplexity += added ;
281+ topLevelOwnComplexity . push ( complexityPoint ) ;
266282 } else {
267283 // second+ level function
268- complexityIfNested += added + 1 ;
269- complexityIfNotNested += added ;
284+ complexityIfNested . push ( { complexity : added + 1 , location } ) ;
285+ complexityIfNotNested . push ( complexityPoint ) ;
270286 }
271287 }
272288
273- function addComplexity ( ) {
289+ function addComplexity ( location : estree . SourceLocation ) {
290+ const complexityPoint = { complexity : 1 , location } ;
274291 if ( enclosingFunctions . length === 1 ) {
275292 // top level function
276- topLevelOwnComplexity ++ ;
293+ topLevelOwnComplexity . push ( complexityPoint ) ;
277294 } else {
278295 // second+ level function
279- complexityIfNested ++ ;
280- complexityIfNotNested ++ ;
296+ complexityIfNested . push ( complexityPoint ) ;
297+ complexityIfNotNested . push ( complexityPoint ) ;
281298 }
282299 }
283300
284- function checkFunction ( complexity : number , loc : estree . SourceLocation ) {
285- if ( complexity > threshold ) {
286- context . report ( {
287- message : MESSAGE ,
288- data : { complexity : String ( complexity ) , threshold : String ( threshold ) } ,
289- loc,
301+ function checkFunction ( complexity : ComplexityPoint [ ] = [ ] , loc : estree . SourceLocation ) {
302+ const complexityAmount = complexity . reduce ( ( acc , cur ) => acc + cur . complexity , 0 ) ;
303+ if ( complexityAmount > threshold ) {
304+ const secondaryLocations : IssueLocation [ ] = complexity . map ( complexityPoint => {
305+ const { complexity, location } = complexityPoint ;
306+ const message = complexity === 1 ? "+1" : `+${ complexity } (incl. ${ complexity - 1 } for nesting)` ;
307+ return issueLocation ( location , undefined , message ) ;
290308 } ) ;
309+
310+ report (
311+ context ,
312+ {
313+ message : `Refactor this function to reduce its Cognitive Complexity from ${ complexityAmount } to the ${ threshold } allowed.` ,
314+ loc,
315+ } ,
316+ secondaryLocations ,
317+ complexityAmount - threshold ,
318+ ) ;
291319 }
292320 }
293321 } ,
294322} ;
295323
296324export = rule ;
325+
326+ type ComplexityPoint = {
327+ complexity : number ;
328+ location : estree . SourceLocation ;
329+ } ;
0 commit comments