eslint-plugin-complexity
Version:
eslint plugin for complexity-related rules
149 lines (141 loc) • 4.99 kB
JavaScript
var eslintUtils = require('eslint-utils');
// modified from https://github.com/eslint/eslint/blob/main/lib/rules/complexity.js
const rule = {
meta: {
type: 'suggestion',
docs: {
description: 'cyclomatic complexity exceeding threshold should has a comment',
recommended: false
},
schema: [{
oneOf: [{
type: 'integer',
minimum: 0
}, {
type: 'object',
properties: {
max: {
type: 'integer',
minimum: 0
}
},
additionalProperties: false
}]
}],
messages: {
'complexity-comment': '{{name}} has a complexity of {{complexity}}, expect a comment'
}
},
create(context) {
const sourceCode = context.getSourceCode();
const option = context.options[0];
let THRESHOLD = 20;
if (typeof option === 'object' && Object.prototype.hasOwnProperty.call(option, 'max') && typeof option.max === 'number') {
THRESHOLD = option.max;
} else if (typeof option === 'number') {
THRESHOLD = option;
}
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
// Using a stack to store complexity per code path
const complexities = [];
function increaseComplexity() {
complexities[complexities.length - 1]++;
}
//--------------------------------------------------------------------------
// Public API
//--------------------------------------------------------------------------
return {
onCodePathStart() {
// The initial complexity is 1, representing one execution path in the CodePath
complexities.push(1);
},
// Each branching in the code adds 1 to the complexity
CatchClause: increaseComplexity,
ConditionalExpression: increaseComplexity,
LogicalExpression: increaseComplexity,
ForStatement: increaseComplexity,
ForInStatement: increaseComplexity,
ForOfStatement: increaseComplexity,
IfStatement: increaseComplexity,
WhileStatement: increaseComplexity,
DoWhileStatement: increaseComplexity,
// Avoid `default`
'SwitchCase[test]': increaseComplexity,
// Logical assignment operators have short-circuiting behavior
AssignmentExpression(node) {
if (isLogicalAssignmentOperator(node.operator)) {
increaseComplexity();
}
},
onCodePathEnd(codePath, node) {
const complexity = complexities.pop();
if (!complexity) {
return;
}
/*
* This rule only evaluates complexity of functions, so "program" is excluded.
* Class field initializers and class static blocks are implicit functions. Therefore,
* they shouldn't contribute to the enclosing function's complexity, but their
* own complexity should be evaluated.
*/
if (codePath.origin !== 'function' && codePath.origin !== 'class-field-initializer' && codePath.origin !== 'class-static-block') {
return;
}
if (complexity > THRESHOLD) {
let name;
if (codePath.origin === 'class-field-initializer') {
name = 'class field initializer';
} else if (codePath.origin === 'class-static-block') {
name = 'class static block';
} else {
name = eslintUtils.getFunctionNameWithKind(node);
}
if (!hasComment(sourceCode, node)) {
context.report({
node,
messageId: 'complexity-comment',
data: {
name: name,
complexity,
max: THRESHOLD
}
});
}
}
}
};
}
};
function hasComment(sourceCode, node) {
const comments = sourceCode.getAllComments();
const relatedComments = comments.filter(comment => {
const isCommentAheadOfNode = node.loc.start.line - comment.loc.end.line === 1;
if (isCommentAheadOfNode) {
return isCodeAroundCommentEmpty(sourceCode, comment);
}
return false;
});
return relatedComments.length > 0;
}
function isCodeAroundCommentEmpty(sourceCode, commentNode) {
const startLine = String(sourceCode.lines[commentNode.loc.start.line - 1]);
const endLine = String(sourceCode.lines[commentNode.loc.end.line - 1]);
const preamble = startLine.slice(0, commentNode.loc.start.column).trim();
const postamble = endLine.slice(commentNode.loc.end.column).trim();
const isPreambleEmpty = !preamble;
const isPostambleEmpty = !postamble;
return isPreambleEmpty && isPostambleEmpty;
}
const LOGICAL_ASSIGNMENT_OPERATORS = new Set(['&&=', '||=', '??=']);
function isLogicalAssignmentOperator(operator) {
return LOGICAL_ASSIGNMENT_OPERATORS.has(operator);
}
var index = {
rules: {
comment: rule
}
};
module.exports = index;
//# sourceMappingURL=index.cjs.map