Repository: avajs/eslint-plugin-ava (js)

Issue:
Add like assertion
https://github.com/avajs/ava/pull/2490 introduced a new `like()` assertion. It's pretty similar to `deepEqual()`. We need to recognize it here.

Pull request description:
Support t.like() assertion
Adds support for the [`like()` assertion](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#likevalue-selector-message) to the `assertion-arguments`, `use-t-well`, and `prefer-power-assert` rules.

For `prefer-power-assert`, I added `like()` to the list of allowed assertions. `prefer-power-assert` says it [allows assertions with no power-assert equivalent](https://github.com/avajs/eslint-plugin-ava/blob/master/docs/rules/prefer-power-assert.md#allow-only-use-of-the-asserts-that-have-no-power-assert-alternative), and I don't think there is such an equivalent. `prefer-power-assert` also allows `deepEqual()`, upon which `like()` is based.

I didn't find any other rules that need special support for `like()`.

One additional thing that might be useful for `like()` support would be a rule ensuring that, if any non-semver experiments are used, then `ava`'s config properly enables them and `package.json` pins `ava` to a specific version instead of using a semver range. I'm not sure how practical this is and in any case I think it would be a subject for another pr.

Closes #302, hopefully.

Patch:
diff --git a/docs/rules/prefer-power-assert.md b/docs/rules/prefer-power-assert.md
index 497b824..6e1999c 100644
--- a/docs/rules/prefer-power-assert.md
+++ b/docs/rules/prefer-power-assert.md
@@ -5,6 +5,7 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/master/fr_FR/re
 - [`t.assert()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#assertvalue-message)
 - [`t.deepEqual()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#deepequalvalue-expected-message)
 - [`t.notDeepEqual()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#notdeepequalvalue-expected-message)
+- [`t.like()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#likevalue-selector-message)
 - [`t.throws()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#throwsfn-expected-message)
 - [`t.notThrows()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#notthrowsfn-message)
 - [`t.pass()`](https://github.com/avajs/ava/blob/master/docs/03-assertions.md#passmessage)
diff --git a/rules/assertion-arguments.js b/rules/assertion-arguments.js
index 171ce24..a6c1494 100644
--- a/rules/assertion-arguments.js
+++ b/rules/assertion-arguments.js
@@ -28,6 +28,10 @@ const expectedNbArguments = {
 		min: 2,
 		max: 3
 	},
+	like: {
+		min: 2,
+		max: 3
+	},
 	not: {
 		min: 2,
 		max: 3
diff --git a/util.js b/util.js
index bb0c66f..9b54e55 100644
--- a/util.js
+++ b/util.js
@@ -105,6 +105,7 @@ const assertionMethodsNumberArguments = new Map([
 	['falsy', 1],
 	['ifError', 1],
 	['is', 2],
+	['like', 2],
 	['not', 2],
 	['notDeepEqual', 2],
 	['notRegex', 2],


Test patch:
diff --git a/test/assertion-arguments.js b/test/assertion-arguments.js
index 05cb033..ed6c1cd 100644
--- a/test/assertion-arguments.js
+++ b/test/assertion-arguments.js
@@ -42,6 +42,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase(false, 't.ifError(new Error(), \'message\');'),
 		testCase(false, 't.is.skip(\'same\', \'same\', \'message\');'),
 		testCase(false, 't.is(\'same\', \'same\', \'message\');'),
+		testCase(false, 't.like({}, {}, \'message\');'),
 		testCase(false, 't.not(\'not\', \'same\', \'message\');'),
 		testCase(false, 't.notDeepEqual({}, {a: true}, \'message\');'),
 		testCase(false, 't.notThrows(Promise.resolve(), \'message\');'),
@@ -67,6 +68,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase(false, 't.ifError(new Error());'),
 		testCase(false, 't.is.skip(\'same\', \'same\');'),
 		testCase(false, 't.is(\'same\', \'same\');'),
+		testCase(false, 't.like({}, {});'),
 		testCase(false, 't.not(\'not\', \'same\');'),
 		testCase(false, 't.notDeepEqual({}, {a: true});'),
 		testCase(false, 't.notThrows(Promise.resolve());'),
@@ -97,6 +99,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase('always', 't.not(\'not\', \'same\', \'message\');'),
 		testCase('always', 't.deepEqual({}, {}, \'message\');'),
 		testCase('always', 't.notDeepEqual({}, {a: true}, \'message\');'),
+		testCase('always', 't.like({}, {}, \'message\');'),
 		testCase('always', 't.throws(Promise.reject(), Error, \'message\');'),
 		testCase('always', 't.notThrows(Promise.resolve(), \'message\');'),
 		testCase('always', 't.regex(a, /a/, \'message\');'),
@@ -129,6 +132,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase('never', 't.not(\'not\', \'same\');'),
 		testCase('never', 't.deepEqual({}, {});'),
 		testCase('never', 't.notDeepEqual({}, {a: true});'),
+		testCase('never', 't.like({}, {});'),
 		testCase('never', 't.throws(Promise.reject());'),
 		testCase('never', 't.throws(Promise.reject(), Error);'),
 		testCase('never', 't.notThrows(Promise.resolve());'),
@@ -182,6 +186,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase(false, 't.not(\'not\');', tooFewError(2)),
 		testCase(false, 't.deepEqual({});', tooFewError(2)),
 		testCase(false, 't.notDeepEqual({});', tooFewError(2)),
+		testCase(false, 't.like({});', tooFewError(2)),
 		testCase(false, 't.throws();', tooFewError(1)),
 		testCase(false, 't.notThrows();', tooFewError(1)),
 		testCase(false, 't.regex(a);', tooFewError(2)),
@@ -206,6 +211,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase(false, 't.not(\'not\', \'same\', \'message\', \'extra argument\');', tooManyError(3)),
 		testCase(false, 't.deepEqual({}, {}, \'message\', \'extra argument\');', tooManyError(3)),
 		testCase(false, 't.notDeepEqual({}, {a: true}, \'message\', \'extra argument\');', tooManyError(3)),
+		testCase(false, 't.like({}, {}, \'message\', \'extra argument\');', tooManyError(3)),
 		testCase(false, 't.throws(Promise.reject(), Error, \'message\', \'extra argument\');', tooManyError(3)),
 		testCase(false, 't.notThrows(Promise.resolve(), \'message\', \'extra argument\');', tooManyError(2)),
 		testCase(false, 't.regex(a, /a/, \'message\', \'extra argument\');', tooManyError(3)),
@@ -227,6 +233,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase('always', 't.not(\'not\', \'same\');', missingError),
 		testCase('always', 't.deepEqual({}, {});', missingError),
 		testCase('always', 't.notDeepEqual({}, {a: true});', missingError),
+		testCase('always', 't.like({}, {});', missingError),
 		testCase('always', 't.throws(Promise.reject());', missingError),
 		testCase('always', 't.throws(Promise.reject(), Error);', missingError),
 		testCase('always', 't.notThrows(Promise.resolve());', missingError),
@@ -236,7 +243,6 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase('always', 't.skip.is(\'same\', \'same\');', missingError),
 		testCase('always', 't.is.skip(\'same\', \'same\');', missingError),
 		testCase('always', 't.snapshot(value);', missingError),
-
 		testCase('never', 't.pass(\'message\');', foundError),
 		testCase('never', 't.fail(\'message\');', foundError),
 		testCase('never', 't.truthy(\'unicorn\', \'message\');', foundError),
@@ -247,6 +253,7 @@ ruleTester.run('assertion-arguments', rule, {
 		testCase('never', 't.not(\'not\', \'same\', \'message\');', foundError),
 		testCase('never', 't.deepEqual({}, {}, \'message\');', foundError),
 		testCase('never', 't.notDeepEqual({}, {a: true}, \'message\');', foundError),
+		testCase('never', 't.like({}, {}, \'message\');', foundError),
 		testCase('never', 't.throws(Promise.reject(), Error, \'message\');', foundError),
 		testCase('never', 't.notThrows(Promise.resolve(), \'message\');', foundError),
 		testCase('never', 't.regex(a, /a/, \'message\');', foundError),
diff --git a/test/prefer-power-assert.js b/test/prefer-power-assert.js
index 40ba38b..ee6a11e 100644
--- a/test/prefer-power-assert.js
+++ b/test/prefer-power-assert.js
@@ -65,6 +65,7 @@ const allowedMethods = [
 	'assert(foo)',
 	'deepEqual(foo, bar)',
 	'notDeepEqual(foo, bar)',
+	'like(foo, bar)',
 	'throws(block)',
 	'notThrows(block)',
 	'pass(foo)',
diff --git a/test/use-t-well.js b/test/use-t-well.js
index f4fe4f3..03a7072 100644
--- a/test/use-t-well.js
+++ b/test/use-t-well.js
@@ -69,6 +69,10 @@ ruleTester.run('use-t-well', rule, {
 		testCase('t.try(tt => tt.pass(), 1, 2)'),
 		testCase('t.try(\'title\', tt => tt.pass())'),
 		testCase('t.try(\'title\', tt => tt.pass(), 1, 2)'),
+		testCase('t.like'),
+		testCase('t.like(v, v)'),
+		testCase('t.like(actual, {}, "")'),
+		testCase('t.like.skip(v, v)'),
 		// Shouldn't be triggered since it's not a test file
 		testCase('t.foo(a, a);', false),
 		testCase('t.foo;', false)
@@ -205,6 +209,11 @@ ruleTester.run('use-t-well', rule, {
 		{
 			code: testCase('t.deepEqual.context(a, a);'),
 			errors: [error('Unknown assertion method `.context`.')]
+		},
+		{
+			code: testCase('t.lik(a, a);'),
+			output: testCase('t.like(a, a);'),
+			errors: [error('Misspelled `.like` as `.lik`.')]
 		}
 	]
 });