eslint-plugin-sonarjs
Version:
193 lines (192 loc) • 9.22 kB
JavaScript
;
/*
* SonarQube JavaScript Plugin
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* 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/
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getChaiBddSuggestion = getChaiBddSuggestion;
exports.getChaiAssertSuggestion = getChaiAssertSuggestion;
const ast_js_1 = require("../helpers/ast.js");
const module_js_1 = require("../helpers/module.js");
const assertion_suggestions_js_1 = require("./assertion-suggestions.js");
const assertion_utils_js_1 = require("./assertion-utils.js");
const MAX_ASSERT_ARGUMENTS_WITH_MESSAGE = 3;
const CHAI_EQUALITY_MATCHERS = new Set(['equal', 'equals', 'eq', 'eql', 'eqls']);
function getChaiBddSuggestion(node, sourceCode) {
return getChaiExpectSuggestion(node, sourceCode) ?? getChaiShouldSuggestion(node, sourceCode);
}
function getChaiAssertSuggestion(context, node, sourceCode) {
const method = getAssertMethod(context, node);
if (!method ||
node.arguments.length < 2 ||
node.arguments.length > MAX_ASSERT_ARGUMENTS_WITH_MESSAGE) {
return null;
}
const [actual, expected] = node.arguments;
const negated = method.startsWith('not');
const assertObject = getAssertObject(context, node);
if (!assertObject) {
return null;
}
const assertText = sourceCode.getText(assertObject);
if (!['strictEqual', 'notStrictEqual'].includes(method)) {
return null;
}
const actualText = sourceCode.getText(actual);
const trailingArgs = messageArgument(node, sourceCode);
if ((0, assertion_utils_js_1.isNullLiteral)(expected)) {
return (0, assertion_utils_js_1.replacement)(`${assertText}.${negated ? 'isNotNull' : 'isNull'}(${actualText}${trailingArgs})`, node, sourceCode);
}
if ((0, assertion_utils_js_1.isUndefinedExpression)(expected)) {
return (0, assertion_utils_js_1.replacement)(`${assertText}.${negated ? 'isDefined' : 'isUndefined'}(${actualText}${trailingArgs})`, node, sourceCode);
}
if ((0, assertion_utils_js_1.isLengthAccess)(actual) && !negated) {
return (0, assertion_utils_js_1.replacement)(`${assertText}.lengthOf(${sourceCode.getText(actual.object)}, ${sourceCode.getText(expected)}${trailingArgs})`, node, sourceCode, 'preferSpecificLengthAssertion');
}
const booleanExpected = (0, assertion_utils_js_1.getBooleanValue)(expected);
if (booleanExpected === undefined) {
return null;
}
return (0, assertion_suggestions_js_1.getBooleanExpressionSuggestion)(actual, booleanExpected !== negated, 'assert', sourceCode, node, assertText, trailingArgs);
}
function getChaiExpectSuggestion(node, sourceCode) {
if (!(0, ast_js_1.isMethodCall)(node) ||
!CHAI_EQUALITY_MATCHERS.has(node.callee.property.name) ||
node.arguments.length !== 1) {
return null;
}
const chain = getChaiExpectChain(node.callee.object, sourceCode);
if (!chain) {
return null;
}
return getChaiValueSuggestion(chain.actual, node.arguments[0], chain.negated, node, sourceCode, chain.messageArguments);
}
function getChaiShouldSuggestion(node, sourceCode) {
if (!(0, ast_js_1.isMethodCall)(node) ||
!CHAI_EQUALITY_MATCHERS.has(node.callee.property.name) ||
node.arguments.length !== 1) {
return null;
}
const chain = getShouldChain(node.callee.object);
if (!chain) {
return null;
}
return getChaiShouldValueSuggestion(chain.actual, node.arguments[0], chain.negated, node, sourceCode);
}
function getChaiValueSuggestion(actual, expected, negated, node, sourceCode, messageArguments = '') {
const actualText = sourceCode.getText(actual);
if ((0, assertion_utils_js_1.isNullLiteral)(expected)) {
return (0, assertion_utils_js_1.replacement)(`expect(${actualText}${messageArguments}).to${negated ? '.not' : ''}.be.null`, node, sourceCode);
}
if ((0, assertion_utils_js_1.isUndefinedExpression)(expected)) {
return (0, assertion_utils_js_1.replacement)(`expect(${actualText}${messageArguments}).to${negated ? '.not' : ''}.be.undefined`, node, sourceCode);
}
if ((0, assertion_utils_js_1.isLengthAccess)(actual)) {
return (0, assertion_utils_js_1.replacement)(`expect(${sourceCode.getText(actual.object)}${messageArguments}).to${negated ? '.not' : ''}.have.lengthOf(${sourceCode.getText(expected)})`, node, sourceCode, 'preferSpecificLengthAssertion');
}
const booleanExpected = (0, assertion_utils_js_1.getBooleanValue)(expected);
if (booleanExpected === undefined) {
return null;
}
return (0, assertion_suggestions_js_1.getBooleanExpressionSuggestion)(actual, booleanExpected !== negated, 'chai', sourceCode, node, 'expect', messageArguments);
}
function getChaiShouldValueSuggestion(actual, expected, negated, node, sourceCode) {
const actualText = sourceCode.getText(actual);
if ((0, assertion_utils_js_1.isNullLiteral)(expected)) {
return (0, assertion_utils_js_1.replacement)(`${(0, assertion_suggestions_js_1.chaiShouldReceiver)(actualText, actual)}.should${negated ? '.not' : ''}.be.null`, node, sourceCode);
}
if ((0, assertion_utils_js_1.isUndefinedExpression)(expected)) {
return (0, assertion_utils_js_1.replacement)(`${(0, assertion_suggestions_js_1.chaiShouldReceiver)(actualText, actual)}.should${negated ? '.not' : ''}.be.undefined`, node, sourceCode);
}
if ((0, assertion_utils_js_1.isLengthAccess)(actual)) {
const receiver = (0, assertion_suggestions_js_1.chaiShouldReceiver)(sourceCode.getText(actual.object), actual.object);
return (0, assertion_utils_js_1.replacement)(`${receiver}.should${negated ? '.not' : ''}.have.lengthOf(${sourceCode.getText(expected)})`, node, sourceCode, 'preferSpecificLengthAssertion');
}
const booleanExpected = (0, assertion_utils_js_1.getBooleanValue)(expected);
if (booleanExpected === undefined) {
return null;
}
return (0, assertion_suggestions_js_1.getBooleanExpressionSuggestion)(actual, booleanExpected !== negated, 'chai-should', sourceCode, node);
}
function getChaiExpectChain(node, sourceCode) {
let current = node;
let negated = false;
while (current.type === 'MemberExpression' &&
!current.computed &&
current.property.type === 'Identifier') {
if (current.property.name === 'not') {
negated = !negated;
}
current = current.object;
}
const [actual, ...messageArgs] = current.type === 'CallExpression' ? current.arguments : [];
if (current.type !== 'CallExpression' || !(0, ast_js_1.isIdentifier)(current.callee, 'expect') || !actual) {
return null;
}
return {
actual,
negated,
messageArguments: messageArgs.map(argument => `, ${sourceCode.getText(argument)}`).join(''),
};
}
function getShouldChain(node) {
let current = node;
let negated = false;
while (current.type === 'MemberExpression' &&
!current.computed &&
current.property.type === 'Identifier') {
if (current.property.name === 'not') {
negated = !negated;
}
if (current.property.name === 'should') {
return { actual: current.object, negated };
}
current = current.object;
}
return null;
}
function getAssertMethod(context, node) {
if (node.callee.type === 'MemberExpression' &&
!node.callee.computed &&
node.callee.property.type === 'Identifier') {
const object = node.callee.object;
if ((0, ast_js_1.isIdentifier)(object, 'assert') ||
(object.type === 'MemberExpression' && (0, ast_js_1.isIdentifier)(object.property, 'assert'))) {
return node.callee.property.name;
}
const fqn = (0, module_js_1.getFullyQualifiedName)(context, node.callee);
if (fqn?.startsWith('chai.assert.')) {
return node.callee.property.name;
}
}
return null;
}
function getAssertObject(context, node) {
if (node.callee.type !== 'MemberExpression' ||
node.callee.computed ||
node.callee.property.type !== 'Identifier') {
return null;
}
const object = node.callee.object;
if ((0, ast_js_1.isIdentifier)(object, 'assert') ||
(object.type === 'MemberExpression' && (0, ast_js_1.isIdentifier)(object.property, 'assert')) ||
(0, module_js_1.getFullyQualifiedName)(context, node.callee)?.startsWith('chai.assert.')) {
return object;
}
return null;
}
function messageArgument(node, sourceCode) {
return node.arguments[2] ? `, ${sourceCode.getText(node.arguments[2])}` : '';
}