From 6d53a8ca78785dab577029b2c0139375663a7d36 Mon Sep 17 00:00:00 2001 From: comatory Date: Wed, 13 Sep 2023 21:04:26 +0200 Subject: [PATCH 01/10] edges and node field should not be considered unused with util This rule allows to not trigger unused fields rule warning when utility `collectConnectionNodes` is present in the source and used as a function call. The rule assumes that `collectConnectionNodes` will internally map over `edges` and `node` field and thus, the fields are actually used. --- src/rule-unused-fields.js | 11 +++++++++- test/unused-fields.js | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index 13724c8..89eebc6 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -97,6 +97,7 @@ function rule(context) { let currentMethod = []; let foundMemberAccesses = {}; let templateLiterals = []; + let hasCollectConnectionNodes = false; function visitGetByPathCall(node) { // The `getByPath` utility accesses nested fields in the form @@ -129,6 +130,10 @@ function rule(context) { } } + function shouldIgnoreWhiteListedCollectConnectionFields(field) { + return (field === 'edges' || field === 'node') && hasCollectConnectionNodes; + } + return { Program(_node) { currentMethod = []; @@ -150,7 +155,8 @@ function rule(context) { !isPageInfoField(field) && // Do not warn for unused __typename which can be a workaround // when only interested in existence of an object. - field !== '__typename' + field !== '__typename' && + !shouldIgnoreWhiteListedCollectConnectionFields(field) ) { context.report({ node: templateLiteral, @@ -172,6 +178,9 @@ function rule(context) { return; } switch (node.callee.name) { + case 'collectConnectionNodes': + hasCollectConnectionNodes = true; + break; case 'getByPath': visitGetByPathCall(node); break; diff --git a/test/unused-fields.js b/test/unused-fields.js index 5bbdad0..38cd866 100644 --- a/test/unused-fields.js +++ b/test/unused-fields.js @@ -89,6 +89,22 @@ ruleTester.run('unused-fields', rules['unused-fields'], { # eslint-disable-next-line @productboard/relay/unused-fields name }\`; + `, + ` + graphql\`fragment foo on Page { + fields { + edges { + node { + __typename + id + } + } + } + }\`; + + const nodes = collectConnectionNodes(data.fields); + + const ids = nodes.map((node) => node.id); ` ], invalid: [ @@ -163,6 +179,34 @@ ruleTester.run('unused-fields', rules['unused-fields'], { line: 4 } ] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + edges { + node { + __typename + id + } + } + } + }\`; + + const nodes = filterSomeData(data.fields); + + const ids = nodes.map((node) => node.id); + `, + errors: [ + { + message: unusedFieldsWarning('edges'), + line: 4 + }, + { + message: unusedFieldsWarning('node'), + line: 5 + } + ] } ] }); From 61fac90be3a4367bf12d0415aaa2ddff638c0044 Mon Sep 17 00:00:00 2001 From: comatory Date: Wed, 13 Sep 2023 21:23:53 +0200 Subject: [PATCH 02/10] make whitelist function for edges and nodes configurable --- src/rule-unused-fields.js | 31 ++++++++++++++++++++++++++----- test/unused-fields.js | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index 89eebc6..9da5eb4 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -94,10 +94,13 @@ function isPageInfoField(field) { } function rule(context) { + const edgesAndNodesWhiteListFunctionName = context.options[0] + ? context.options[0].edgesAndNodesWhiteListFunctionName + : null; let currentMethod = []; let foundMemberAccesses = {}; let templateLiterals = []; - let hasCollectConnectionNodes = false; + let hasEdgesAndNodesWhiteListFunctionCall = false; function visitGetByPathCall(node) { // The `getByPath` utility accesses nested fields in the form @@ -131,7 +134,7 @@ function rule(context) { } function shouldIgnoreWhiteListedCollectConnectionFields(field) { - return (field === 'edges' || field === 'node') && hasCollectConnectionNodes; + return (field === 'edges' || field === 'node') && hasEdgesAndNodesWhiteListFunctionCall; } return { @@ -178,8 +181,8 @@ function rule(context) { return; } switch (node.callee.name) { - case 'collectConnectionNodes': - hasCollectConnectionNodes = true; + case edgesAndNodesWhiteListFunctionName: + hasEdgesAndNodesWhiteListFunctionCall = true; break; case 'getByPath': visitGetByPathCall(node); @@ -215,4 +218,22 @@ function rule(context) { }; } -module.exports = rule; +module.exports = { + meta: { + docs: { + description: 'Warns about unused fields in graphql queries' + }, + schema: [ + { + type: 'object', + properties: { + edgesAndNodesWhiteListFunctionName: { + type: 'string' + } + }, + additionalProperties: false + } + ] + }, + create: rule +}; diff --git a/test/unused-fields.js b/test/unused-fields.js index 38cd866..ee4c8cf 100644 --- a/test/unused-fields.js +++ b/test/unused-fields.js @@ -90,7 +90,8 @@ ruleTester.run('unused-fields', rules['unused-fields'], { name }\`; `, - ` + { + code: ` graphql\`fragment foo on Page { fields { edges { @@ -105,7 +106,9 @@ ruleTester.run('unused-fields', rules['unused-fields'], { const nodes = collectConnectionNodes(data.fields); const ids = nodes.map((node) => node.id); - ` + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] + } ], invalid: [ { @@ -207,6 +210,35 @@ ruleTester.run('unused-fields', rules['unused-fields'], { line: 5 } ] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + edges { + node { + __typename + id + } + } + } + }\`; + + const nodes = collectConnectionNodes_TYPO(data.fields); + + const ids = nodes.map((node) => node.id); + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}], + errors: [ + { + message: unusedFieldsWarning('edges'), + line: 4 + }, + { + message: unusedFieldsWarning('node'), + line: 5 + } + ] } ] }); From 32b3097e51263ac25ee11aedeab2f985630eeace Mon Sep 17 00:00:00 2001 From: comatory Date: Wed, 13 Sep 2023 22:40:42 +0200 Subject: [PATCH 03/10] make sure that white listing function detects arguments that relate to field with edges --- src/rule-unused-fields.js | 60 ++++++++++++++++++++++++++++++++++----- test/unused-fields.js | 52 +++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 7 deletions(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index 9da5eb4..d7cc5f4 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -18,6 +18,8 @@ const ESLINT_DISABLE_COMMENT = function getGraphQLFieldNames(graphQLAst) { const fieldNames = {}; + const edgesParents = []; + let prevField = null; function walkAST(node, ignoreLevel) { if (node.kind === 'Field' && !ignoreLevel) { @@ -26,6 +28,17 @@ function getGraphQLFieldNames(graphQLAst) { } const nameNode = node.alias || node.name; fieldNames[nameNode.value] = nameNode; + + if ( + prevField && + prevField.kind === 'Field' && + node.kind === 'Field' && + node.name.value === 'edges' + ) { + edgesParents.push(prevField.name.value); + } + + prevField = node; } if (node.kind === 'OperationDefinition') { if ( @@ -42,6 +55,7 @@ function getGraphQLFieldNames(graphQLAst) { }); return; } + for (const prop in node) { const value = node[prop]; if (prop === 'loc') { @@ -58,7 +72,8 @@ function getGraphQLFieldNames(graphQLAst) { } walkAST(graphQLAst); - return fieldNames; + + return {fieldNames, edgesParents}; } function isGraphQLTemplate(node) { @@ -100,7 +115,7 @@ function rule(context) { let currentMethod = []; let foundMemberAccesses = {}; let templateLiterals = []; - let hasEdgesAndNodesWhiteListFunctionCall = false; + const edgesAndNodesWhiteListFunctionCalls = []; function visitGetByPathCall(node) { // The `getByPath` utility accesses nested fields in the form @@ -133,8 +148,28 @@ function rule(context) { } } - function shouldIgnoreWhiteListedCollectConnectionFields(field) { - return (field === 'edges' || field === 'node') && hasEdgesAndNodesWhiteListFunctionCall; + function getEdgesAndNodesWhiteListFunctionCallArguments(calls) { + return calls.flatMap(call => call.arguments.map(arg => arg.property.name)); + } + + // Naively checks whether the function call for + // `edgesAndNodesWhiteListFunctionName` contains arguments + // that are property accesses on a field that contains + // `edges` + function shouldIgnoreWhiteListedCollectConnectionFields( + field, + edgesParents, + callArguments + ) { + const callArgumentsSet = new Set([...callArguments]); + const edgesParentsSet = new Set([...edgesParents]); + const intersect = new Set( + [...callArgumentsSet].filter(callArgument => + edgesParentsSet.has(callArgument) + ) + ); + + return (field === 'edges' || field === 'node') && intersect.size > 0; } return { @@ -151,7 +186,14 @@ function rule(context) { return; } - const queriedFields = getGraphQLFieldNames(graphQLAst); + const {fieldNames: queriedFields, edgesParents} = + getGraphQLFieldNames(graphQLAst); + + const edgesAndNodesWhiteListFunctionCallArguments = + getEdgesAndNodesWhiteListFunctionCallArguments( + edgesAndNodesWhiteListFunctionCalls + ); + for (const field in queriedFields) { if ( !foundMemberAccesses[field] && @@ -159,7 +201,11 @@ function rule(context) { // Do not warn for unused __typename which can be a workaround // when only interested in existence of an object. field !== '__typename' && - !shouldIgnoreWhiteListedCollectConnectionFields(field) + !shouldIgnoreWhiteListedCollectConnectionFields( + field, + edgesParents, + edgesAndNodesWhiteListFunctionCallArguments + ) ) { context.report({ node: templateLiteral, @@ -182,7 +228,7 @@ function rule(context) { } switch (node.callee.name) { case edgesAndNodesWhiteListFunctionName: - hasEdgesAndNodesWhiteListFunctionCall = true; + edgesAndNodesWhiteListFunctionCalls.push(node); break; case 'getByPath': visitGetByPathCall(node); diff --git a/test/unused-fields.js b/test/unused-fields.js index ee4c8cf..c264883 100644 --- a/test/unused-fields.js +++ b/test/unused-fields.js @@ -106,6 +106,38 @@ ruleTester.run('unused-fields', rules['unused-fields'], { const nodes = collectConnectionNodes(data.fields); const ids = nodes.map((node) => node.id); + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + edges { + node { + __typename + id + } + } + } + }\`; + graphql\`fragment bar on Page { + items { + edges { + node { + id + } + } + } + }\`; + + const nodes = collectConnectionNodes(data.fields); + + const ids = nodes.map((node) => node.id); + + const otherNodes = collectConnectionNodes(data.items); + + const otherIds = otherNodes.map((node) => node.id); `, options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] } @@ -239,6 +271,26 @@ ruleTester.run('unused-fields', rules['unused-fields'], { line: 5 } ] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + name + } + }\`; + + const nodes = collectConnectionNodes(data.fields); + + const ids = nodes.map((node) => node.id); + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}], + errors: [ + { + message: unusedFieldsWarning('name'), + line: 4 + } + ] } ] }); From bd07d9cd8b8d9053fa18c42b050baab6e6fc1c85 Mon Sep 17 00:00:00 2001 From: comatory Date: Thu, 14 Sep 2023 16:48:03 +0200 Subject: [PATCH 04/10] detect edges' parent correctly When there's a field that contains other fields than `edges`, make sure the parsing picks it up. --- src/rule-unused-fields.js | 32 ++++++++++++++++---------------- test/unused-fields.js | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index d7cc5f4..525f4d4 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -19,7 +19,6 @@ const ESLINT_DISABLE_COMMENT = function getGraphQLFieldNames(graphQLAst) { const fieldNames = {}; const edgesParents = []; - let prevField = null; function walkAST(node, ignoreLevel) { if (node.kind === 'Field' && !ignoreLevel) { @@ -29,16 +28,9 @@ function getGraphQLFieldNames(graphQLAst) { const nameNode = node.alias || node.name; fieldNames[nameNode.value] = nameNode; - if ( - prevField && - prevField.kind === 'Field' && - node.kind === 'Field' && - node.name.value === 'edges' - ) { - edgesParents.push(prevField.name.value); + if (node.kind === 'Field' && node.selectionSet && containsEdges(node)) { + edgesParents.push(node.name.value); } - - prevField = node; } if (node.kind === 'OperationDefinition') { if ( @@ -108,6 +100,13 @@ function isPageInfoField(field) { } } +function containsEdges(node) { + return node.selectionSet.selections.some( + selection => + selection.name && selection.name.value && selection.name.value === 'edges' + ); +} + function rule(context) { const edgesAndNodesWhiteListFunctionName = context.options[0] ? context.options[0].edgesAndNodesWhiteListFunctionName @@ -149,7 +148,9 @@ function rule(context) { } function getEdgesAndNodesWhiteListFunctionCallArguments(calls) { - return calls.flatMap(call => call.arguments.map(arg => arg.property.name)); + return calls.flatMap(call => + call.arguments.map(arg => (arg.property ? arg.property.name : null)) + ); } // Naively checks whether the function call for @@ -179,6 +180,10 @@ function rule(context) { templateLiterals = []; }, 'Program:exit'(_node) { + const edgesAndNodesWhiteListFunctionCallArguments = + getEdgesAndNodesWhiteListFunctionCallArguments( + edgesAndNodesWhiteListFunctionCalls + ); templateLiterals.forEach(templateLiteral => { const graphQLAst = getGraphQLAST(templateLiteral); if (!graphQLAst) { @@ -189,11 +194,6 @@ function rule(context) { const {fieldNames: queriedFields, edgesParents} = getGraphQLFieldNames(graphQLAst); - const edgesAndNodesWhiteListFunctionCallArguments = - getEdgesAndNodesWhiteListFunctionCallArguments( - edgesAndNodesWhiteListFunctionCalls - ); - for (const field in queriedFields) { if ( !foundMemberAccesses[field] && diff --git a/test/unused-fields.js b/test/unused-fields.js index c264883..c580046 100644 --- a/test/unused-fields.js +++ b/test/unused-fields.js @@ -138,6 +138,27 @@ ruleTester.run('unused-fields', rules['unused-fields'], { const otherNodes = collectConnectionNodes(data.items); const otherIds = otherNodes.map((node) => node.id); + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + __id + edges { + node { + __typename + id + } + } + } + }\`; + + const nodes = collectConnectionNodes(data.fields); + + const ids = nodes.map((node) => node.id); + const connectionId = data.fields.__id; `, options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] } From 91a76be5bb806c7b1d0e80848625fc9e1ea50f67 Mon Sep 17 00:00:00 2001 From: comatory Date: Thu, 14 Sep 2023 20:04:06 +0200 Subject: [PATCH 05/10] fix situation when argument to whitelisted function uses optional chaining --- src/rule-unused-fields.js | 9 +++++- test/unused-fields.js | 68 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index 525f4d4..a6e4063 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -149,7 +149,14 @@ function rule(context) { function getEdgesAndNodesWhiteListFunctionCallArguments(calls) { return calls.flatMap(call => - call.arguments.map(arg => (arg.property ? arg.property.name : null)) + call.arguments.map(arg => { + if ('expression' in arg) { + return arg.expression.property.name; + } else if ('property' in arg) { + return arg.property.name; + } + return null; + }) ); } diff --git a/test/unused-fields.js b/test/unused-fields.js index c580046..37e7af7 100644 --- a/test/unused-fields.js +++ b/test/unused-fields.js @@ -156,8 +156,52 @@ ruleTester.run('unused-fields', rules['unused-fields'], { }\`; const nodes = collectConnectionNodes(data.fields); + const firstNode = nodes[0].id; + + const connectionId = data.fields.__id; + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + __id + edges { + node { + __typename + id + } + } + } + }\`; + + const nodes = collectConnectionNodes(data?.fields); + const firstNode = nodes[0].id; + + const connectionId = data.fields.__id; + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] + }, + { + code: ` + graphql\`query fields($id: ID!) { + node(id: $id) { + fields { + __id + edges { + node { + __typename + id + } + } + } + } + }\`; + + const nodes = collectConnectionNodes(data.node.fields); + const firstNode = nodes[0].id; - const ids = nodes.map((node) => node.id); const connectionId = data.fields.__id; `, options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] @@ -312,6 +356,28 @@ ruleTester.run('unused-fields', rules['unused-fields'], { line: 4 } ] + }, + { + code: ` + graphql\`fragment foo on Page { + fields { + name + } + }\`; + + const nodes = collectConnectionNodes(data.unrelatedData); + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}], + errors: [ + { + message: unusedFieldsWarning('fields'), + line: 3 + }, + { + message: unusedFieldsWarning('name'), + line: 4 + } + ] } ] }); From d93ca1914789158b2e2bc3c9e1e585f426796d6f Mon Sep 17 00:00:00 2001 From: comatory Date: Thu, 14 Sep 2023 20:28:11 +0200 Subject: [PATCH 06/10] handle de-structured properties passed to whitelist function --- src/rule-unused-fields.js | 4 +++- test/unused-fields.js | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index a6e4063..7ba390d 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -150,7 +150,9 @@ function rule(context) { function getEdgesAndNodesWhiteListFunctionCallArguments(calls) { return calls.flatMap(call => call.arguments.map(arg => { - if ('expression' in arg) { + if (arg.type === 'Identifier') { + return arg.name; + } else if ('expression' in arg) { return arg.expression.property.name; } else if ('property' in arg) { return arg.property.name; diff --git a/test/unused-fields.js b/test/unused-fields.js index 37e7af7..3fc449e 100644 --- a/test/unused-fields.js +++ b/test/unused-fields.js @@ -183,6 +183,28 @@ ruleTester.run('unused-fields', rules['unused-fields'], { `, options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] }, + { + code: ` + graphql\`fragment foo on Page { + fields { + __id + edges { + node { + __typename + id + } + } + } + }\`; + + const { fields } = data; + const nodes = collectConnectionNodes(fields); + const firstNode = nodes[0].id; + + const connectionId = fields.__id; + `, + options: [{edgesAndNodesWhiteListFunctionName: 'collectConnectionNodes'}] + }, { code: ` graphql\`query fields($id: ID!) { From 3e51870a2273898a9db7e1cb4b7ef815f9109f18 Mon Sep 17 00:00:00 2001 From: comatory Date: Thu, 14 Sep 2023 20:32:25 +0200 Subject: [PATCH 07/10] optimize speed --- src/rule-unused-fields.js | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/rule-unused-fields.js b/src/rule-unused-fields.js index 7ba390d..ed39375 100644 --- a/src/rule-unused-fields.js +++ b/src/rule-unused-fields.js @@ -166,8 +166,7 @@ function rule(context) { // `edgesAndNodesWhiteListFunctionName` contains arguments // that are property accesses on a field that contains // `edges` - function shouldIgnoreWhiteListedCollectConnectionFields( - field, + function wasWhiteListFunctionCalledWithEdgesAndNodesArgument( edgesParents, callArguments ) { @@ -179,7 +178,17 @@ function rule(context) { ) ); - return (field === 'edges' || field === 'node') && intersect.size > 0; + return intersect.size > 0; + } + + function shouldIgnoreWhiteListedCollectConnectionFields( + field, + whiteListFunctionCalledWithEdgesAndNodes + ) { + return ( + (field === 'edges' || field === 'node') && + whiteListFunctionCalledWithEdgesAndNodes + ); } return { @@ -203,6 +212,12 @@ function rule(context) { const {fieldNames: queriedFields, edgesParents} = getGraphQLFieldNames(graphQLAst); + const whiteListFunctionCalledWithEdgesAndNodes = + wasWhiteListFunctionCalledWithEdgesAndNodesArgument( + edgesParents, + edgesAndNodesWhiteListFunctionCallArguments + ); + for (const field in queriedFields) { if ( !foundMemberAccesses[field] && @@ -212,8 +227,7 @@ function rule(context) { field !== '__typename' && !shouldIgnoreWhiteListedCollectConnectionFields( field, - edgesParents, - edgesAndNodesWhiteListFunctionCallArguments + whiteListFunctionCalledWithEdgesAndNodes ) ) { context.report({ From 25da8ceb04bdc1f278b312c4da480b0323171e1f Mon Sep 17 00:00:00 2001 From: comatory Date: Wed, 13 Sep 2023 21:28:33 +0200 Subject: [PATCH 08/10] prepare changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6baf171..1049211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.9.4 + +- rule `unused-fields` has additional functionality which allows you to pass option `edgesAndNodesWhiteListFunctionName`, this is a name of a function and if that function receives an object with `edges` as argument, it will ignore `edges` and `node` warning for unused fields because it considers them to be used inside this function + ## v1.9.3 - allow to pass custom error message to `no-future-added-value` rule From 457dcbc7f72c0dd42066ed09233c392f4e65a659 Mon Sep 17 00:00:00 2001 From: comatory Date: Wed, 13 Sep 2023 22:47:05 +0200 Subject: [PATCH 09/10] fix version number in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1049211..d483da3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v1.9.4 +## v1.9.6 - rule `unused-fields` has additional functionality which allows you to pass option `edgesAndNodesWhiteListFunctionName`, this is a name of a function and if that function receives an object with `edges` as argument, it will ignore `edges` and `node` warning for unused fields because it considers them to be used inside this function From 02eabd808a36683aaccc33d6042265523d559afc Mon Sep 17 00:00:00 2001 From: comatory Date: Wed, 13 Sep 2023 22:41:35 +0200 Subject: [PATCH 10/10] v1.9.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0e64316..ddddbab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@productboard/eslint-plugin-relay", "private": true, - "version": "1.9.5", + "version": "1.9.6", "description": "ESLint plugin for Relay.", "main": "eslint-plugin-relay", "repository": "https://github.com/productboard/eslint-plugin-relay",