eslint-plugin-sonarjs
Version:
SonarJS rules for ESLint
116 lines (115 loc) • 4.72 kB
JavaScript
/*
* SonarQube JavaScript Plugin
* Copyright (C) 2011-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
// https://sonarsource.github.io/rspec/#/rspec/S128/javascript
Object.defineProperty(exports, "__esModule", { value: true });
exports.rule = void 0;
const index_js_1 = require("../helpers/index.js");
const meta_js_1 = require("./meta.js");
exports.rule = {
meta: (0, index_js_1.generateMeta)(meta_js_1.meta, {
messages: {
switchEnd: 'End this switch case with an unconditional break, continue, return or throw statement.',
},
}),
create(context) {
let currentCodeSegment = null;
let enteringSwitchCase = false;
let currentSegments = new Set();
const allCurrentSegments = [];
const segmentsWithExit = new Set();
const initialSegmentBySwitchCase = new Map();
const switchCaseStack = [];
function noComment(node) {
return context.sourceCode.getCommentsAfter(node).length === 0;
}
function isAfterProcessExitCall(segment, initialSegment) {
const stack = [];
const visitedSegments = new Set();
stack.push(segment);
while (stack.length !== 0) {
const current = stack.pop();
visitedSegments.add(current.id);
if (!segmentsWithExit.has(current.id)) {
if (current === initialSegment) {
return false;
}
current.prevSegments.filter(p => !visitedSegments.has(p.id)).forEach(p => stack.push(p));
}
}
return true;
}
return {
onCodePathStart() {
allCurrentSegments.push(currentSegments);
currentSegments = new Set();
},
onCodePathEnd() {
currentSegments = allCurrentSegments.pop();
},
onCodePathSegmentStart(segment) {
currentSegments.add(segment);
currentCodeSegment = segment;
if (enteringSwitchCase) {
initialSegmentBySwitchCase.set(switchCaseStack.pop(), currentCodeSegment);
enteringSwitchCase = false;
}
},
onCodePathSegmentEnd(segment) {
currentSegments.delete(segment);
},
onUnreachableCodePathSegmentStart(segment) {
currentSegments.add(segment);
},
onUnreachableCodePathSegmentEnd(segment) {
currentSegments.delete(segment);
},
CallExpression(node) {
const callExpr = node;
if (isProcessExitCall(callExpr)) {
segmentsWithExit.add(currentCodeSegment.id);
}
},
SwitchCase(node) {
enteringSwitchCase = true;
switchCaseStack.push(node);
},
'SwitchCase:exit'(node) {
const switchCase = node;
const initialSegment = initialSegmentBySwitchCase.get(switchCase);
const isReachable = Array.from(currentSegments).some(s => s.reachable && !isAfterProcessExitCall(s, initialSegment));
const { cases } = (0, index_js_1.getParent)(context, node);
if (isReachable &&
switchCase.consequent.length > 0 &&
cases[cases.length - 1] !== node &&
noComment(switchCase)) {
context.report({
messageId: 'switchEnd',
loc: context.sourceCode.getFirstToken(node).loc,
});
}
},
};
},
};
function isProcessExitCall(callExpr) {
return (callExpr.callee.type === 'MemberExpression' &&
callExpr.callee.object.type === 'Identifier' &&
callExpr.callee.object.name === 'process' &&
callExpr.callee.property.type === 'Identifier' &&
callExpr.callee.property.name === 'exit');
}
;