eslint-plugin-playwright 1.4.2 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -238,7 +238,7 @@ function parse(context, node) {
238
238
  return null;
239
239
  }
240
240
  let type = group;
241
- if ((name === "test" || name === "describe") && (node.arguments.length !== 2 || !isFunction(node.arguments[1]))) {
241
+ if ((name === "test" || name === "describe") && (node.arguments.length < 2 || !isFunction(node.arguments.at(-1)))) {
242
242
  type = "config";
243
243
  }
244
244
  return {
@@ -1515,9 +1515,7 @@ var no_standalone_expect_default = {
1515
1515
  CallExpression(node) {
1516
1516
  const call = parseFnCall(context, node);
1517
1517
  if (call?.type === "expect") {
1518
- if (getParent(call.head.node)?.type === "MemberExpression" && call.members.length === 1 && !["assertions", "hasAssertions"].includes(
1519
- getStringValue(call.members[0])
1520
- )) {
1518
+ if (getParent(call.head.node)?.type === "MemberExpression" && call.members.length === 1) {
1521
1519
  return;
1522
1520
  }
1523
1521
  const parent = callStack.at(-1);
@@ -1749,14 +1747,44 @@ function replaceAccessorFixer(fixer, node, text) {
1749
1747
  text
1750
1748
  );
1751
1749
  }
1750
+ function removePropertyFixer(fixer, property) {
1751
+ const parent = getParent(property);
1752
+ if (parent?.type !== "ObjectExpression")
1753
+ return;
1754
+ if (parent.properties.length === 1) {
1755
+ return fixer.remove(parent);
1756
+ }
1757
+ const index2 = parent.properties.indexOf(property);
1758
+ const range = index2 ? [parent.properties[index2 - 1].range[1], property.range[1]] : [property.range[0], parent.properties[1].range[0]];
1759
+ return fixer.removeRange(range);
1760
+ }
1752
1761
 
1753
1762
  // src/rules/no-useless-not.ts
1754
- var matcherMap = {
1755
- toBeDisabled: "toBeEnabled",
1756
- toBeEnabled: "toBeDisabled",
1757
- toBeHidden: "toBeVisible",
1758
- toBeVisible: "toBeHidden"
1763
+ var matcherConfig = {
1764
+ toBeDisabled: { inverse: "toBeEnabled" },
1765
+ toBeEnabled: {
1766
+ argName: "enabled",
1767
+ inverse: "toBeDisabled"
1768
+ },
1769
+ toBeHidden: { inverse: "toBeVisible" },
1770
+ toBeVisible: {
1771
+ argName: "visible",
1772
+ inverse: "toBeHidden"
1773
+ }
1759
1774
  };
1775
+ function getOptions(call, name) {
1776
+ const [arg] = call.matcherArgs;
1777
+ if (arg?.type !== "ObjectExpression")
1778
+ return;
1779
+ const property = arg.properties.find(
1780
+ (p) => p.type === "Property" && getStringValue(p.key) === name && isBooleanLiteral(p.value)
1781
+ );
1782
+ return {
1783
+ arg,
1784
+ property,
1785
+ value: property?.value?.value
1786
+ };
1787
+ }
1760
1788
  var no_useless_not_default = {
1761
1789
  create(context) {
1762
1790
  return {
@@ -1764,30 +1792,41 @@ var no_useless_not_default = {
1764
1792
  const call = parseFnCall(context, node);
1765
1793
  if (call?.type !== "expect")
1766
1794
  return;
1795
+ const config = matcherConfig[call.matcherName];
1796
+ if (!config)
1797
+ return;
1798
+ const options = config.argName ? getOptions(call, config.argName) : void 0;
1799
+ if (options?.arg && options.value === void 0)
1800
+ return;
1767
1801
  const notModifier = call.modifiers.find(
1768
1802
  (mod) => getStringValue(mod) === "not"
1769
1803
  );
1770
- if (!notModifier)
1804
+ if (!notModifier && !options?.property)
1771
1805
  return;
1772
- const matcherName = call.matcherName;
1773
- if (matcherName in matcherMap) {
1774
- const newMatcher = matcherMap[matcherName];
1775
- context.report({
1776
- data: { new: newMatcher, old: matcherName },
1777
- fix: (fixer) => [
1778
- fixer.removeRange([
1806
+ const isInverted = !!notModifier !== (options?.value === false);
1807
+ const newMatcherName = isInverted ? config.inverse : call.matcherName;
1808
+ context.report({
1809
+ data: {
1810
+ new: newMatcherName,
1811
+ old: call.matcherName,
1812
+ property: config.argName ?? ""
1813
+ },
1814
+ fix: (fixer) => {
1815
+ return [
1816
+ // Remove the `not` modifier if it exists
1817
+ notModifier && fixer.removeRange([
1779
1818
  notModifier.range[0] - getRangeOffset(notModifier),
1780
1819
  notModifier.range[1] + 1
1781
1820
  ]),
1782
- replaceAccessorFixer(fixer, call.matcher, newMatcher)
1783
- ],
1784
- loc: {
1785
- end: call.matcher.loc.end,
1786
- start: notModifier.loc.start
1787
- },
1788
- messageId: "noUselessNot"
1789
- });
1790
- }
1821
+ // Remove the `visible` or `enabled` property if it exists
1822
+ options?.property && removePropertyFixer(fixer, options.property),
1823
+ // Swap the matcher name if it's different
1824
+ call.matcherName !== newMatcherName && replaceAccessorFixer(fixer, call.matcher, newMatcherName)
1825
+ ].filter(truthy);
1826
+ },
1827
+ loc: notModifier ? { end: call.matcher.loc.end, start: notModifier.loc.start } : options.property.loc,
1828
+ messageId: notModifier ? "noUselessNot" : isInverted ? "noUselessProperty" : "noUselessTruthy"
1829
+ });
1791
1830
  }
1792
1831
  };
1793
1832
  },
@@ -1800,7 +1839,9 @@ var no_useless_not_default = {
1800
1839
  },
1801
1840
  fixable: "code",
1802
1841
  messages: {
1803
- noUselessNot: "Unexpected usage of not.{{old}}(). Use {{new}}() instead."
1842
+ noUselessNot: "Unexpected usage of not.{{old}}(). Use {{new}}() instead.",
1843
+ noUselessProperty: "Unexpected usage of '{{old}}({ {{property}}: false })'. Use '{{new}}()' instead.",
1844
+ noUselessTruthy: "Unexpected usage of '{{old}}({ {{property}}: true })'. Use '{{new}}()' instead."
1804
1845
  },
1805
1846
  type: "problem"
1806
1847
  }
package/dist/index.mjs CHANGED
@@ -86,7 +86,7 @@ function parse(context, node) {
86
86
  return null;
87
87
  }
88
88
  let type = group;
89
- if ((name === "test" || name === "describe") && (node.arguments.length !== 2 || !isFunction(node.arguments[1]))) {
89
+ if ((name === "test" || name === "describe") && (node.arguments.length < 2 || !isFunction(node.arguments.at(-1)))) {
90
90
  type = "config";
91
91
  }
92
92
  return {
@@ -1671,9 +1671,7 @@ var init_no_standalone_expect = __esm({
1671
1671
  CallExpression(node) {
1672
1672
  const call = parseFnCall(context, node);
1673
1673
  if (call?.type === "expect") {
1674
- if (getParent(call.head.node)?.type === "MemberExpression" && call.members.length === 1 && !["assertions", "hasAssertions"].includes(
1675
- getStringValue(call.members[0])
1676
- )) {
1674
+ if (getParent(call.head.node)?.type === "MemberExpression" && call.members.length === 1) {
1677
1675
  return;
1678
1676
  }
1679
1677
  const parent = callStack.at(-1);
@@ -1927,27 +1925,59 @@ function replaceAccessorFixer(fixer, node, text) {
1927
1925
  text
1928
1926
  );
1929
1927
  }
1928
+ function removePropertyFixer(fixer, property) {
1929
+ const parent = getParent(property);
1930
+ if (parent?.type !== "ObjectExpression")
1931
+ return;
1932
+ if (parent.properties.length === 1) {
1933
+ return fixer.remove(parent);
1934
+ }
1935
+ const index = parent.properties.indexOf(property);
1936
+ const range = index ? [parent.properties[index - 1].range[1], property.range[1]] : [property.range[0], parent.properties[1].range[0]];
1937
+ return fixer.removeRange(range);
1938
+ }
1930
1939
  var getRangeOffset;
1931
1940
  var init_fixer = __esm({
1932
1941
  "src/utils/fixer.ts"() {
1933
1942
  "use strict";
1943
+ init_ast();
1934
1944
  getRangeOffset = (node) => node.type === "Identifier" ? 0 : 1;
1935
1945
  }
1936
1946
  });
1937
1947
 
1938
1948
  // src/rules/no-useless-not.ts
1939
- var matcherMap, no_useless_not_default;
1949
+ function getOptions(call, name) {
1950
+ const [arg] = call.matcherArgs;
1951
+ if (arg?.type !== "ObjectExpression")
1952
+ return;
1953
+ const property = arg.properties.find(
1954
+ (p) => p.type === "Property" && getStringValue(p.key) === name && isBooleanLiteral(p.value)
1955
+ );
1956
+ return {
1957
+ arg,
1958
+ property,
1959
+ value: property?.value?.value
1960
+ };
1961
+ }
1962
+ var matcherConfig, no_useless_not_default;
1940
1963
  var init_no_useless_not = __esm({
1941
1964
  "src/rules/no-useless-not.ts"() {
1942
1965
  "use strict";
1943
1966
  init_ast();
1944
1967
  init_fixer();
1968
+ init_misc();
1945
1969
  init_parseFnCall();
1946
- matcherMap = {
1947
- toBeDisabled: "toBeEnabled",
1948
- toBeEnabled: "toBeDisabled",
1949
- toBeHidden: "toBeVisible",
1950
- toBeVisible: "toBeHidden"
1970
+ matcherConfig = {
1971
+ toBeDisabled: { inverse: "toBeEnabled" },
1972
+ toBeEnabled: {
1973
+ argName: "enabled",
1974
+ inverse: "toBeDisabled"
1975
+ },
1976
+ toBeHidden: { inverse: "toBeVisible" },
1977
+ toBeVisible: {
1978
+ argName: "visible",
1979
+ inverse: "toBeHidden"
1980
+ }
1951
1981
  };
1952
1982
  no_useless_not_default = {
1953
1983
  create(context) {
@@ -1956,30 +1986,41 @@ var init_no_useless_not = __esm({
1956
1986
  const call = parseFnCall(context, node);
1957
1987
  if (call?.type !== "expect")
1958
1988
  return;
1989
+ const config = matcherConfig[call.matcherName];
1990
+ if (!config)
1991
+ return;
1992
+ const options = config.argName ? getOptions(call, config.argName) : void 0;
1993
+ if (options?.arg && options.value === void 0)
1994
+ return;
1959
1995
  const notModifier = call.modifiers.find(
1960
1996
  (mod) => getStringValue(mod) === "not"
1961
1997
  );
1962
- if (!notModifier)
1998
+ if (!notModifier && !options?.property)
1963
1999
  return;
1964
- const matcherName = call.matcherName;
1965
- if (matcherName in matcherMap) {
1966
- const newMatcher = matcherMap[matcherName];
1967
- context.report({
1968
- data: { new: newMatcher, old: matcherName },
1969
- fix: (fixer) => [
1970
- fixer.removeRange([
2000
+ const isInverted = !!notModifier !== (options?.value === false);
2001
+ const newMatcherName = isInverted ? config.inverse : call.matcherName;
2002
+ context.report({
2003
+ data: {
2004
+ new: newMatcherName,
2005
+ old: call.matcherName,
2006
+ property: config.argName ?? ""
2007
+ },
2008
+ fix: (fixer) => {
2009
+ return [
2010
+ // Remove the `not` modifier if it exists
2011
+ notModifier && fixer.removeRange([
1971
2012
  notModifier.range[0] - getRangeOffset(notModifier),
1972
2013
  notModifier.range[1] + 1
1973
2014
  ]),
1974
- replaceAccessorFixer(fixer, call.matcher, newMatcher)
1975
- ],
1976
- loc: {
1977
- end: call.matcher.loc.end,
1978
- start: notModifier.loc.start
1979
- },
1980
- messageId: "noUselessNot"
1981
- });
1982
- }
2015
+ // Remove the `visible` or `enabled` property if it exists
2016
+ options?.property && removePropertyFixer(fixer, options.property),
2017
+ // Swap the matcher name if it's different
2018
+ call.matcherName !== newMatcherName && replaceAccessorFixer(fixer, call.matcher, newMatcherName)
2019
+ ].filter(truthy);
2020
+ },
2021
+ loc: notModifier ? { end: call.matcher.loc.end, start: notModifier.loc.start } : options.property.loc,
2022
+ messageId: notModifier ? "noUselessNot" : isInverted ? "noUselessProperty" : "noUselessTruthy"
2023
+ });
1983
2024
  }
1984
2025
  };
1985
2026
  },
@@ -1992,7 +2033,9 @@ var init_no_useless_not = __esm({
1992
2033
  },
1993
2034
  fixable: "code",
1994
2035
  messages: {
1995
- noUselessNot: "Unexpected usage of not.{{old}}(). Use {{new}}() instead."
2036
+ noUselessNot: "Unexpected usage of not.{{old}}(). Use {{new}}() instead.",
2037
+ noUselessProperty: "Unexpected usage of '{{old}}({ {{property}}: false })'. Use '{{new}}()' instead.",
2038
+ noUselessTruthy: "Unexpected usage of '{{old}}({ {{property}}: true })'. Use '{{new}}()' instead."
1996
2039
  },
1997
2040
  type: "problem"
1998
2041
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-playwright",
3
3
  "description": "ESLint plugin for Playwright testing.",
4
- "version": "1.4.2",
4
+ "version": "1.5.0",
5
5
  "repository": "https://github.com/playwright-community/eslint-plugin-playwright",
6
6
  "author": "Mark Skelton <mark@mskelton.dev>",
7
7
  "packageManager": "pnpm@8.12.0",