eslint-plugin-playwright 2.2.0 → 2.2.2

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.
Files changed (3) hide show
  1. package/README.md +3 -1
  2. package/dist/index.cjs +230 -30
  3. package/package.json +1 -29
package/README.md CHANGED
@@ -139,11 +139,12 @@ CLI option\
139
139
  | [no-raw-locators](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-raw-locators.md) | Disallow using raw locators | | | |
140
140
  | [no-restricted-matchers](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-restricted-matchers.md) | Disallow specific matchers & modifiers | | | |
141
141
  | [no-skipped-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-skipped-test.md) | Disallow usage of the `.skip` annotation | ✅ | | 💡 |
142
- | [no-slowed-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md) | Disallow usage of the `.slow` annotation || | 💡 |
142
+ | [no-slowed-test](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md) | Disallow usage of the `.slow` annotation | | | 💡 |
143
143
  | [no-standalone-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-standalone-expect.md) | Disallow using expect outside of `test` blocks | ✅ | | |
144
144
  | [no-unsafe-references](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md) | Prevent unsafe variable references in `page.evaluate()` | ✅ | 🔧 | |
145
145
  | [no-useless-await](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-await.md) | Disallow unnecessary `await`s for Playwright methods | ✅ | 🔧 | |
146
146
  | [no-useless-not](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-useless-not.md) | Disallow usage of `not` matchers when a specific matcher exists | ✅ | 🔧 | |
147
+ | [no-wait-for-navigation](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-navigation.md) | Disallow usage of `page.waitForNavigation()` | ✅ | | 💡 |
147
148
  | [no-wait-for-selector](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-selector.md) | Disallow usage of `page.waitForSelector()` | ✅ | | 💡 |
148
149
  | [no-wait-for-timeout](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-timeout.md) | Disallow usage of `page.waitForTimeout()` | ✅ | | 💡 |
149
150
  | [prefer-comparison-matcher](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/prefer-comparison-matcher.md) | Suggest using the built-in comparison matchers | | 🔧 | |
@@ -167,3 +168,4 @@ CLI option\
167
168
  | [valid-expect-in-promise](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect-in-promise.md) | Require promises that have expectations in their chain to be valid | ✅ | | |
168
169
  | [valid-expect](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-expect.md) | Enforce valid `expect()` usage | ✅ | | |
169
170
  | [valid-title](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-title.md) | Enforce valid titles | ✅ | 🔧 | |
171
+ | [valid-test-tags](https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/valid-test-tags.md) | Enforce valid tag format in test blocks | ✅ | | |
package/dist/index.cjs CHANGED
@@ -689,7 +689,8 @@ var playwrightTestMatchers = [
689
689
  "toHaveTitle",
690
690
  "toHaveURL",
691
691
  "toHaveValue",
692
- "toHaveValues"
692
+ "toHaveValues",
693
+ "toContainClass"
693
694
  ];
694
695
  function getReportNode(node) {
695
696
  const parent = getParent(node);
@@ -929,7 +930,17 @@ var no_conditional_in_test_default = createRule({
929
930
  if (!call)
930
931
  return;
931
932
  if (isTypeOfFnCall(context, call, ["test", "step"])) {
932
- context.report({ messageId: "conditionalInTest", node });
933
+ const testFunction = call.arguments[call.arguments.length - 1];
934
+ const functionBody = findParent(node, "BlockStatement");
935
+ if (!functionBody)
936
+ return;
937
+ let currentParent = functionBody.parent;
938
+ while (currentParent && currentParent !== testFunction) {
939
+ currentParent = currentParent.parent;
940
+ }
941
+ if (currentParent === testFunction) {
942
+ context.report({ messageId: "conditionalInTest", node });
943
+ }
933
944
  }
934
945
  }
935
946
  return {
@@ -1429,7 +1440,7 @@ var no_raw_locators_default = createRule({
1429
1440
  }
1430
1441
  return {
1431
1442
  CallExpression(node) {
1432
- if (node.callee.type !== "MemberExpression")
1443
+ if (node.callee.type !== "MemberExpression" || node.arguments[0]?.type === "Identifier")
1433
1444
  return;
1434
1445
  const method = getStringValue(node.callee.property);
1435
1446
  const arg = getStringValue(node.arguments[0]);
@@ -1540,7 +1551,7 @@ var no_skipped_test_default = createRule({
1540
1551
  if (!skipNode)
1541
1552
  return;
1542
1553
  const isStandalone = call.type === "config";
1543
- if (isStandalone && allowConditional) {
1554
+ if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement")) {
1544
1555
  return;
1545
1556
  }
1546
1557
  context.report({
@@ -1604,7 +1615,7 @@ var no_slowed_test_default = createRule({
1604
1615
  if (!slowNode)
1605
1616
  return;
1606
1617
  const isStandalone = call.type === "config";
1607
- if (isStandalone && allowConditional) {
1618
+ if (isStandalone && allowConditional && (node.arguments.length !== 0 || findParent(node, "BlockStatement")?.parent?.type === "IfStatement")) {
1608
1619
  return;
1609
1620
  }
1610
1621
  context.report({
@@ -1629,7 +1640,7 @@ var no_slowed_test_default = createRule({
1629
1640
  docs: {
1630
1641
  category: "Best Practices",
1631
1642
  description: "Prevent usage of the `.slow()` slow test annotation.",
1632
- recommended: true,
1643
+ recommended: false,
1633
1644
  url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-slowed-test.md"
1634
1645
  },
1635
1646
  hasSuggestions: true,
@@ -1795,7 +1806,7 @@ var no_unsafe_references_default = createRule({
1795
1806
  create(context) {
1796
1807
  return {
1797
1808
  CallExpression(node) {
1798
- if (!isPageMethod(node, "evaluate"))
1809
+ if (!isPageMethod(node, "evaluate") && !isPageMethod(node, "addInitScript"))
1799
1810
  return;
1800
1811
  const [fn] = node.arguments;
1801
1812
  if (!fn || !isFunction(fn))
@@ -1806,8 +1817,9 @@ var no_unsafe_references_default = createRule({
1806
1817
  const parent = getParent(ref.identifier);
1807
1818
  return parent?.type !== "TSTypeReference";
1808
1819
  }).filter((ref) => allRefs.has(ref.identifier.name)).forEach((ref, i, arr) => {
1820
+ const methodName = isPageMethod(node, "evaluate") ? "evaluate" : "addInitScript";
1809
1821
  const descriptor = {
1810
- data: { variable: ref.identifier.name },
1822
+ data: { method: methodName, variable: ref.identifier.name },
1811
1823
  messageId: "noUnsafeReference",
1812
1824
  node: ref.identifier
1813
1825
  };
@@ -1832,13 +1844,13 @@ var no_unsafe_references_default = createRule({
1832
1844
  meta: {
1833
1845
  docs: {
1834
1846
  category: "Possible Errors",
1835
- description: "Prevent unsafe variable references in page.evaluate()",
1847
+ description: "Prevent unsafe variable references in page.evaluate() and page.addInitScript()",
1836
1848
  recommended: true,
1837
1849
  url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-unsafe-references.md"
1838
1850
  },
1839
1851
  fixable: "code",
1840
1852
  messages: {
1841
- noUnsafeReference: 'Unsafe reference to variable "{{ variable }}" in page.evaluate()'
1853
+ noUnsafeReference: 'Unsafe reference to variable "{{ variable }}" in page.{{ method }}()'
1842
1854
  },
1843
1855
  type: "problem"
1844
1856
  }
@@ -2065,6 +2077,44 @@ var no_useless_not_default = createRule({
2065
2077
  }
2066
2078
  });
2067
2079
 
2080
+ // src/rules/no-wait-for-navigation.ts
2081
+ var no_wait_for_navigation_default = createRule({
2082
+ create(context) {
2083
+ return {
2084
+ CallExpression(node) {
2085
+ if (isPageMethod(node, "waitForNavigation")) {
2086
+ context.report({
2087
+ messageId: "noWaitForNavigation",
2088
+ node,
2089
+ suggest: [
2090
+ {
2091
+ fix: (fixer) => fixer.remove(
2092
+ node.parent && node.parent.type !== "AwaitExpression" && node.parent.type !== "VariableDeclarator" ? node.parent : node.parent.parent
2093
+ ),
2094
+ messageId: "removeWaitForNavigation"
2095
+ }
2096
+ ]
2097
+ });
2098
+ }
2099
+ }
2100
+ };
2101
+ },
2102
+ meta: {
2103
+ docs: {
2104
+ category: "Possible Errors",
2105
+ description: "Prevent usage of page.waitForNavigation()",
2106
+ recommended: true,
2107
+ url: "https://github.com/playwright-community/eslint-plugin-playwright/tree/main/docs/rules/no-wait-for-navigation.md"
2108
+ },
2109
+ hasSuggestions: true,
2110
+ messages: {
2111
+ noWaitForNavigation: "Unexpected use of page.waitForNavigation().",
2112
+ removeWaitForNavigation: "Remove the page.waitForNavigation() method."
2113
+ },
2114
+ type: "suggestion"
2115
+ }
2116
+ });
2117
+
2068
2118
  // src/rules/no-wait-for-selector.ts
2069
2119
  var no_wait_for_selector_default = createRule({
2070
2120
  create(context) {
@@ -3016,45 +3066,45 @@ var prefer_web_first_assertions_default = createRule({
3016
3066
  create(context) {
3017
3067
  return {
3018
3068
  CallExpression(node) {
3019
- const call = parseFnCall(context, node);
3020
- if (call?.type !== "expect")
3069
+ const fnCall = parseFnCall(context, node);
3070
+ if (fnCall?.type !== "expect")
3021
3071
  return;
3022
- const expect = findParent(call.head.node, "CallExpression");
3072
+ const expect = findParent(fnCall.head.node, "CallExpression");
3023
3073
  if (!expect)
3024
3074
  return;
3025
- const arg = dereference(context, call.args[0]);
3026
- if (!arg || arg.type !== "AwaitExpression" || arg.argument.type !== "CallExpression" || arg.argument.callee.type !== "MemberExpression") {
3075
+ const arg = dereference(context, fnCall.args[0]);
3076
+ if (!arg)
3077
+ return;
3078
+ const call = arg.type === "AwaitExpression" ? arg.argument : arg;
3079
+ if (call.type !== "CallExpression" || call.callee.type !== "MemberExpression") {
3027
3080
  return;
3028
3081
  }
3029
- if (!supportedMatchers.has(call.matcherName))
3082
+ if (!supportedMatchers.has(fnCall.matcherName))
3030
3083
  return;
3031
- const method = getStringValue(arg.argument.callee.property);
3084
+ const method = getStringValue(call.callee.property);
3032
3085
  const methodConfig = methods3[method];
3033
- if (!methodConfig)
3086
+ if (!Object.hasOwn(methods3, method))
3034
3087
  return;
3035
- const notModifier = call.modifiers.find(
3088
+ const notModifier = fnCall.modifiers.find(
3036
3089
  (mod) => getStringValue(mod) === "not"
3037
3090
  );
3038
- const isFalsy = methodConfig.type === "boolean" && (!!call.matcherArgs.length && isBooleanLiteral(call.matcherArgs[0], false) || call.matcherName === "toBeFalsy");
3091
+ const isFalsy = methodConfig.type === "boolean" && (!!fnCall.matcherArgs.length && isBooleanLiteral(fnCall.matcherArgs[0], false) || fnCall.matcherName === "toBeFalsy");
3039
3092
  const isInverse = methodConfig.inverse ? notModifier || isFalsy : notModifier && isFalsy;
3040
3093
  const newMatcher = +!!notModifier ^ +isFalsy && methodConfig.inverse || methodConfig.matcher;
3041
- const { callee } = arg.argument;
3094
+ const { callee } = call;
3042
3095
  context.report({
3043
3096
  data: {
3044
3097
  matcher: newMatcher,
3045
3098
  method
3046
3099
  },
3047
3100
  fix: (fixer) => {
3048
- const methodArgs = arg.argument.type === "CallExpression" ? arg.argument.arguments : [];
3101
+ const methodArgs = call.type === "CallExpression" ? call.arguments : [];
3049
3102
  const methodEnd = methodArgs.length ? methodArgs.at(-1).range[1] + 1 : callee.property.range[1] + 2;
3050
3103
  const fixes = [
3051
3104
  // Add await to the expect call
3052
3105
  fixer.insertTextBefore(expect, "await "),
3053
3106
  // Remove the await keyword
3054
- fixer.replaceTextRange(
3055
- [arg.range[0], arg.argument.range[0]],
3056
- ""
3057
- ),
3107
+ fixer.replaceTextRange([arg.range[0], call.range[0]], ""),
3058
3108
  // Remove the old Playwright method and any arguments
3059
3109
  fixer.replaceTextRange(
3060
3110
  [callee.property.range[0] - 1, methodEnd],
@@ -3066,10 +3116,10 @@ var prefer_web_first_assertions_default = createRule({
3066
3116
  fixes.push(fixer.removeRange([notRange[0], notRange[1] + 1]));
3067
3117
  }
3068
3118
  if (!methodConfig.inverse && !notModifier && isFalsy) {
3069
- fixes.push(fixer.insertTextBefore(call.matcher, "not."));
3119
+ fixes.push(fixer.insertTextBefore(fnCall.matcher, "not."));
3070
3120
  }
3071
- fixes.push(fixer.replaceText(call.matcher, newMatcher));
3072
- const [matcherArg] = call.matcherArgs ?? [];
3121
+ fixes.push(fixer.replaceText(fnCall.matcher, newMatcher));
3122
+ const [matcherArg] = fnCall.matcherArgs ?? [];
3073
3123
  if (matcherArg && isBooleanLiteral(matcherArg)) {
3074
3124
  fixes.push(fixer.remove(matcherArg));
3075
3125
  } else if (methodConfig.prop && matcherArg) {
@@ -3082,7 +3132,7 @@ var prefer_web_first_assertions_default = createRule({
3082
3132
  (arg2) => !isBooleanLiteral(arg2)
3083
3133
  ).length;
3084
3134
  if (methodArgs) {
3085
- const range = call.matcher.range;
3135
+ const range = fnCall.matcher.range;
3086
3136
  const stringArgs = methodArgs.map((arg2) => getRawValue(arg2)).concat(hasOtherArgs ? "" : []).join(", ");
3087
3137
  fixes.push(
3088
3138
  fixer.insertTextAfterRange(
@@ -3789,6 +3839,152 @@ var valid_expect_in_promise_default = createRule({
3789
3839
  }
3790
3840
  });
3791
3841
 
3842
+ // src/rules/valid-test-tags.ts
3843
+ var valid_test_tags_default = createRule({
3844
+ create(context) {
3845
+ const options = context.options[0] || {};
3846
+ const allowedTags = options.allowedTags || [];
3847
+ const disallowedTags = options.disallowedTags || [];
3848
+ if (allowedTags.length > 0 && disallowedTags.length > 0) {
3849
+ throw new Error(
3850
+ "The allowedTags and disallowedTags options cannot be used together"
3851
+ );
3852
+ }
3853
+ for (const tag of [...allowedTags, ...disallowedTags]) {
3854
+ if (typeof tag === "string" && !tag.startsWith("@")) {
3855
+ throw new Error(
3856
+ `Invalid tag "${tag}" in configuration: tags must start with @`
3857
+ );
3858
+ }
3859
+ }
3860
+ const validateTag = (tag, node) => {
3861
+ if (!tag.startsWith("@")) {
3862
+ context.report({
3863
+ messageId: "invalidTagFormat",
3864
+ node
3865
+ });
3866
+ return;
3867
+ }
3868
+ if (allowedTags.length > 0) {
3869
+ const isAllowed = allowedTags.some(
3870
+ (pattern) => pattern instanceof RegExp ? pattern.test(tag) : pattern === tag
3871
+ );
3872
+ if (!isAllowed) {
3873
+ context.report({
3874
+ data: { tag },
3875
+ messageId: "unknownTag",
3876
+ node
3877
+ });
3878
+ return;
3879
+ }
3880
+ }
3881
+ if (disallowedTags.length > 0) {
3882
+ const isDisallowed = disallowedTags.some(
3883
+ (pattern) => pattern instanceof RegExp ? pattern.test(tag) : pattern === tag
3884
+ );
3885
+ if (isDisallowed) {
3886
+ context.report({
3887
+ data: { tag },
3888
+ messageId: "disallowedTag",
3889
+ node
3890
+ });
3891
+ }
3892
+ }
3893
+ };
3894
+ return {
3895
+ CallExpression(node) {
3896
+ const call = parseFnCall(context, node);
3897
+ if (!call)
3898
+ return;
3899
+ const { type } = call;
3900
+ if (type !== "test" && type !== "describe" && type !== "step")
3901
+ return;
3902
+ if (node.arguments.length < 2)
3903
+ return;
3904
+ const optionsArg = node.arguments[1];
3905
+ if (!optionsArg || optionsArg.type !== "ObjectExpression")
3906
+ return;
3907
+ const tagProperty = optionsArg.properties.find(
3908
+ (prop) => prop.type === "Property" && !("argument" in prop) && // Ensure it's not a spread element
3909
+ prop.key.type === "Identifier" && prop.key.name === "tag"
3910
+ );
3911
+ if (!tagProperty)
3912
+ return;
3913
+ const tagValue = tagProperty.value;
3914
+ if (tagValue.type === "Literal") {
3915
+ if (typeof tagValue.value !== "string") {
3916
+ context.report({
3917
+ messageId: "invalidTagValue",
3918
+ node
3919
+ });
3920
+ return;
3921
+ }
3922
+ validateTag(tagValue.value, node);
3923
+ } else if (tagValue.type === "ArrayExpression") {
3924
+ for (const element of tagValue.elements) {
3925
+ if (!element || element.type !== "Literal" || typeof element.value !== "string") {
3926
+ return;
3927
+ }
3928
+ validateTag(element.value, node);
3929
+ }
3930
+ } else {
3931
+ context.report({
3932
+ messageId: "invalidTagValue",
3933
+ node
3934
+ });
3935
+ }
3936
+ }
3937
+ };
3938
+ },
3939
+ meta: {
3940
+ docs: {
3941
+ description: "Enforce valid tag format in Playwright test blocks",
3942
+ recommended: true
3943
+ },
3944
+ messages: {
3945
+ disallowedTag: 'Tag "{{tag}}" is not allowed',
3946
+ invalidTagFormat: "Tag must start with @",
3947
+ invalidTagValue: "Tag must be a string or array of strings",
3948
+ unknownTag: 'Unknown tag "{{tag}}"'
3949
+ },
3950
+ schema: [
3951
+ {
3952
+ additionalProperties: false,
3953
+ properties: {
3954
+ allowedTags: {
3955
+ items: {
3956
+ oneOf: [
3957
+ { type: "string" },
3958
+ {
3959
+ additionalProperties: false,
3960
+ properties: { source: { type: "string" } },
3961
+ type: "object"
3962
+ }
3963
+ ]
3964
+ },
3965
+ type: "array"
3966
+ },
3967
+ disallowedTags: {
3968
+ items: {
3969
+ oneOf: [
3970
+ { type: "string" },
3971
+ {
3972
+ additionalProperties: false,
3973
+ properties: { source: { type: "string" } },
3974
+ type: "object"
3975
+ }
3976
+ ]
3977
+ },
3978
+ type: "array"
3979
+ }
3980
+ },
3981
+ type: "object"
3982
+ }
3983
+ ],
3984
+ type: "problem"
3985
+ }
3986
+ });
3987
+
3792
3988
  // src/rules/valid-title.ts
3793
3989
  var doesBinaryExpressionContainStringNode = (binaryExp) => {
3794
3990
  if (isStringNode(binaryExp.right)) {
@@ -4037,6 +4233,7 @@ var index = {
4037
4233
  "no-unsafe-references": no_unsafe_references_default,
4038
4234
  "no-useless-await": no_useless_await_default,
4039
4235
  "no-useless-not": no_useless_not_default,
4236
+ "no-wait-for-navigation": no_wait_for_navigation_default,
4040
4237
  "no-wait-for-selector": no_wait_for_selector_default,
4041
4238
  "no-wait-for-timeout": no_wait_for_timeout_default,
4042
4239
  "prefer-comparison-matcher": prefer_comparison_matcher_default,
@@ -4059,6 +4256,7 @@ var index = {
4059
4256
  "valid-describe-callback": valid_describe_callback_default,
4060
4257
  "valid-expect": valid_expect_default,
4061
4258
  "valid-expect-in-promise": valid_expect_in_promise_default,
4259
+ "valid-test-tags": valid_test_tags_default,
4062
4260
  "valid-title": valid_title_default
4063
4261
  }
4064
4262
  };
@@ -4082,12 +4280,14 @@ var sharedConfig = {
4082
4280
  "playwright/no-unsafe-references": "error",
4083
4281
  "playwright/no-useless-await": "warn",
4084
4282
  "playwright/no-useless-not": "warn",
4283
+ "playwright/no-wait-for-navigation": "error",
4085
4284
  "playwright/no-wait-for-selector": "warn",
4086
4285
  "playwright/no-wait-for-timeout": "warn",
4087
4286
  "playwright/prefer-web-first-assertions": "error",
4088
4287
  "playwright/valid-describe-callback": "error",
4089
4288
  "playwright/valid-expect": "error",
4090
4289
  "playwright/valid-expect-in-promise": "error",
4290
+ "playwright/valid-test-tags": "error",
4091
4291
  "playwright/valid-title": "error"
4092
4292
  }
4093
4293
  };
package/package.json CHANGED
@@ -1,17 +1,13 @@
1
1
  {
2
2
  "name": "eslint-plugin-playwright",
3
3
  "description": "ESLint plugin for Playwright testing.",
4
- "version": "2.2.0",
4
+ "version": "2.2.2",
5
5
  "repository": "https://github.com/playwright-community/eslint-plugin-playwright",
6
6
  "author": "Mark Skelton <mark@mskelton.dev>",
7
- "packageManager": "pnpm@8.12.0",
8
7
  "contributors": [
9
8
  "Max Schmitt <max@schmitt.mx>"
10
9
  ],
11
10
  "license": "MIT",
12
- "workspaces": [
13
- "examples"
14
- ],
15
11
  "engines": {
16
12
  "node": ">=16.6.0"
17
13
  },
@@ -30,34 +26,10 @@
30
26
  "index.cjs",
31
27
  "index.d.ts"
32
28
  ],
33
- "scripts": {
34
- "build": "tsup src/index.ts --format cjs --out-dir dist",
35
- "lint": "eslint .",
36
- "fmt": "prettier --write .",
37
- "fmt:check": "prettier --check .",
38
- "test": "vitest",
39
- "test:watch": "vitest --reporter=dot",
40
- "ts": "tsc --noEmit"
41
- },
42
29
  "peerDependencies": {
43
30
  "eslint": ">=8.40.0"
44
31
  },
45
32
  "dependencies": {
46
33
  "globals": "^13.23.0"
47
- },
48
- "devDependencies": {
49
- "@mskelton/eslint-config": "^9.0.1",
50
- "@mskelton/semantic-release-config": "^1.0.1",
51
- "@types/estree": "^1.0.6",
52
- "@types/node": "^20.11.17",
53
- "@typescript-eslint/parser": "^8.11.0",
54
- "dedent": "^1.5.1",
55
- "eslint": "^9.13.0",
56
- "prettier": "^3.0.3",
57
- "prettier-plugin-jsdoc": "^1.3.0",
58
- "semantic-release": "^23.0.2",
59
- "tsup": "^8.0.1",
60
- "typescript": "^5.2.2",
61
- "vitest": "^1.3.1"
62
34
  }
63
35
  }