UNPKG

eslint-plugin-sonarjs

Version:
193 lines (192 loc) 9.22 kB
"use strict"; /* * 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])}` : ''; }