diff --git a/docs/rules/assertion-arguments.md b/docs/rules/assertion-arguments.md index 686f2ff3..24c5a5b7 100644 --- a/docs/rules/assertion-arguments.md +++ b/docs/rules/assertion-arguments.md @@ -16,6 +16,7 @@ const test = require('ava'); test(t => { t.is(value); // Not enough arguments t.is(value, expected, message, extra); // Too many arguments + t.is(value, expected, false); // Assertion message is not a string }); /* eslint ava/assertion-arguments: ["error", {"message": "always"}] */ diff --git a/rules/assertion-arguments.js b/rules/assertion-arguments.js index 5c050fd0..0f6bbaa2 100644 --- a/rules/assertion-arguments.js +++ b/rules/assertion-arguments.js @@ -1,6 +1,6 @@ 'use strict'; const {visitIf} = require('enhance-visitors'); -const {getStaticValue, isOpeningParenToken, isCommaToken} = require('eslint-utils'); +const {getStaticValue, isOpeningParenToken, isCommaToken, findVariable} = require('eslint-utils'); const util = require('../util'); const createAvaRule = require('../create-ava-rule'); @@ -203,6 +203,13 @@ function noComments(sourceCode, ...nodes) { }); } +function isString(node) { + const {type} = node; + return type === 'TemplateLiteral' || + type === 'TaggedTemplateExpression' || + (type === 'Literal' && typeof node.value === 'string'); +} + const create = context => { const ava = createAvaRule(); const options = context.options[0] || {}; @@ -271,6 +278,24 @@ const create = context => { checkArgumentOrder({node, assertion: firstNonSkipMember, context}); } + + if (gottenArgs === nArgs.max && nArgs.min !== nArgs.max) { + let lastArg = node.arguments[node.arguments.length - 1]; + + if (lastArg.type === 'Identifier') { + const variable = findVariable(context.getScope(), lastArg); + let value; + for (const ref of variable.references) { + value = ref.writeExpr || value; + } + + lastArg = value; + } + + if (!isString(lastArg)) { + report(node, 'Assertion message should be a string.'); + } + } }) }); }; diff --git a/test/assertion-arguments.js b/test/assertion-arguments.js index 51074f36..4b51147f 100644 --- a/test/assertion-arguments.js +++ b/test/assertion-arguments.js @@ -19,6 +19,7 @@ const outOfOrderError = (line, column, endLine, endColumn) => ({ message: 'Expected values should come after actual values.', line, column, endLine, endColumn }); +const messageIsNotStringError = 'Assertion message should be a string.'; const header = 'const test = require(\'ava\');'; @@ -331,7 +332,11 @@ ruleTester.run('assertion-arguments', rule, { testCase(false, 't.regex(\'static\', new RegExp(\'[dynamic]+\'));'), testCase(false, 't.regex(dynamic, /[static]/);'), testCase(false, 't.notRegex(\'static\', new RegExp(\'[dynamic]+\'));'), - testCase(false, 't.notRegex(dynamic, /[static]/);') + testCase(false, 't.notRegex(dynamic, /[static]/);'), + + // Lookup message type + testCase(false, 'const message = \'ok\'; t.assert(true, message);'), + testCase(false, 'const message = \'ok\'; t.is(42, 42, message);') ], invalid: [ // Not enough arguments @@ -538,6 +543,12 @@ ruleTester.run('assertion-arguments', rule, { outOfOrderError(1, 13, 1, 23 + expression.length), {output: `t.deepEqual(${expression}, 'static');`} ) - ) + ), + + // Message is not string + testCase(false, 't.assert(true, true);', messageIsNotStringError), + testCase(false, 't.deepEqual({}, {}, 42);', messageIsNotStringError), + testCase(false, 't.fail({});', messageIsNotStringError), + testCase(false, 'let message = "ok"; message = false; t.assert(true, message);', messageIsNotStringError) ] });