Skip to content

Commit

Permalink
fix require-id-when-available check unions as well (#1467)
Browse files Browse the repository at this point in the history
* fix `require-id-when-available` check unions as well

* fixes

* fixes
  • Loading branch information
dimaMachina committed Feb 26, 2023
1 parent 458c448 commit 12f802c
Show file tree
Hide file tree
Showing 61 changed files with 498 additions and 243 deletions.
5 changes: 5 additions & 0 deletions .changeset/afraid-pugs-fail.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@graphql-eslint/eslint-plugin': patch
---

fix `require-id-when-available` check unions as well
6 changes: 5 additions & 1 deletion examples/basic/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as graphqlESLint from '@graphql-eslint/eslint-plugin';
import js from '@eslint/js';

export default [
'eslint:recommended',
{
files: ['**/*.js'],
rules: js.configs.recommended.rules,
},
{
files: ['**/*.graphql'],
plugins: {
Expand Down
1 change: 1 addition & 0 deletions examples/basic/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"graphql": "16.6.0"
},
"devDependencies": {
"@eslint/js": "8.35.0",
"@graphql-eslint/eslint-plugin": "workspace:*",
"eslint": "8.33.0"
}
Expand Down
3 changes: 2 additions & 1 deletion examples/code-file/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import * as graphqlESLint from '@graphql-eslint/eslint-plugin';
import js from '@eslint/js';

export default [
'eslint:recommended',
{
files: ['**/*.js'],
processor: graphqlESLint.processors.graphql,
rules: {
...js.configs.recommended.rules,
'no-console': 'error',
},
},
Expand Down
1 change: 1 addition & 0 deletions examples/code-file/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"graphql": "16.6.0"
},
"devDependencies": {
"@eslint/js": "8.35.0",
"@graphql-eslint/eslint-plugin": "workspace:*",
"eslint": "8.33.0"
}
Expand Down
3 changes: 2 additions & 1 deletion examples/graphql-config-code-file/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as graphqlESLint from '@graphql-eslint/eslint-plugin';
import js from '@eslint/js';

export default [
'eslint:recommended',
{
files: ['**/*.js'],
processor: graphqlESLint.processors.graphql,
rules: js.configs.recommended.rules,
},
{
files: ['**/*.graphql'],
Expand Down
1 change: 1 addition & 0 deletions examples/graphql-config-code-file/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"graphql-tag": "2.12.6"
},
"devDependencies": {
"@eslint/js": "8.35.0",
"@graphql-eslint/eslint-plugin": "workspace:*",
"eslint": "8.33.0"
}
Expand Down
6 changes: 5 additions & 1 deletion examples/graphql-config/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import * as graphqlESLint from '@graphql-eslint/eslint-plugin';
import js from '@eslint/js';

export default [
'eslint:recommended',
{
files: ['**/*.js'],
rules: js.configs.recommended.rules,
},
{
files: ['**/*.graphql'],
plugins: {
Expand Down
1 change: 1 addition & 0 deletions examples/graphql-config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"graphql": "16.6.0"
},
"devDependencies": {
"@eslint/js": "8.35.0",
"@graphql-eslint/eslint-plugin": "workspace:*",
"eslint": "8.33.0"
}
Expand Down
6 changes: 5 additions & 1 deletion examples/monorepo/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as graphqlESLint from '@graphql-eslint/eslint-plugin';
import js from '@eslint/js';

const SCHEMA_PATH = 'server/**/*.gql';
const OPERATIONS_PATH = 'client/**/*.{tsx,gql}';

export default [
'eslint:recommended',
{
files: ['**/*.{js,tsx}'],
rules: js.configs.recommended.rules,
},
{
files: ['client/**/*.tsx'],
// Setup processor for operations/fragments definitions on code-files
Expand Down
1 change: 1 addition & 0 deletions examples/monorepo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"graphql": "16.6.0"
},
"devDependencies": {
"@eslint/js": "8.35.0",
"@graphql-eslint/eslint-plugin": "workspace:*",
"eslint": "8.33.0"
}
Expand Down
3 changes: 2 additions & 1 deletion examples/prettier/eslint.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as graphqlESLint from '@graphql-eslint/eslint-plugin';
import prettierPlugin from 'eslint-plugin-prettier';
import prettierConfig from 'eslint-config-prettier';
import js from '@eslint/js';

export default [
'eslint:recommended',
{
plugins: {
prettier: prettierPlugin,
Expand All @@ -13,6 +13,7 @@ export default [
files: ['**/*.js'],
processor: graphqlESLint.processors.graphql,
rules: {
...js.configs.recommended.rules,
...prettierConfig.rules,
...prettierPlugin.configs.recommended.rules,
},
Expand Down
1 change: 1 addition & 0 deletions examples/prettier/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"graphql": "16.6.0"
},
"devDependencies": {
"@eslint/js": "8.35.0",
"@graphql-eslint/eslint-plugin": "workspace:*",
"eslint": "8.33.0",
"eslint-config-prettier": "8.6.0",
Expand Down
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"chalk": "4.1.2",
"dedent": "0.7.0",
"enquirer": "2.3.6",
"eslint": "8.33.0",
"eslint": "8.35.0",
"eslint-plugin-eslint-plugin": "5.0.7",
"eslint-plugin-tailwindcss": "3.9.0",
"husky": "8.0.3",
Expand All @@ -42,17 +42,17 @@
"rimraf": "4.1.2",
"tsx": "3.12.3",
"typescript": "4.9.5",
"vitest": "0.28.4"
"vitest": "0.29.1"
},
"resolutions": {
"graphql": "16.6.0"
},
"pnpm": {
"patchedDependencies": {
"eslint@8.33.0": "patches/eslint@8.31.0.patch",
"eslint@8.35.0": "patches/eslint@8.31.0.patch",
"eslint-plugin-eslint-plugin@5.0.7": "patches/eslint-plugin-eslint-plugin@5.0.6.patch",
"json-schema-to-markdown@1.1.1": "patches/json-schema-to-markdown@1.1.1.patch",
"@vitest/runner@0.28.4": "patches/@vitest__runner@0.28.4.patch"
"@vitest/runner@0.29.1": "patches/@vitest__runner@0.28.4.patch"
}
}
}
141 changes: 80 additions & 61 deletions packages/plugin/src/rules/require-id-when-available.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
GraphQLInterfaceType,
GraphQLObjectType,
GraphQLOutputType,
GraphQLUnionType,
Kind,
SelectionSetNode,
TypeInfo,
Expand Down Expand Up @@ -165,83 +166,101 @@ export const rule: GraphQLESLintRule<RuleOptions, true> = {
checkedFragmentSpreads = new Set<string>(),
): void {
const rawType = getBaseType(type);
const isObjectType = rawType instanceof GraphQLObjectType;
const isInterfaceType = rawType instanceof GraphQLInterfaceType;

if (!isObjectType && !isInterfaceType) {
return;
if (rawType instanceof GraphQLObjectType || rawType instanceof GraphQLInterfaceType) {
checkFields(rawType);
} else if (rawType instanceof GraphQLUnionType) {
for (const selection of node.selections) {
if (selection.kind === Kind.INLINE_FRAGMENT) {
const types = rawType.getTypes();
const t = types.find(t => t.name === selection.typeCondition!.name.value);
if (t) {
checkFields(t);
}
}
}
}
const fields = rawType.getFields();
const hasIdFieldInType = idNames.some(name => fields[name]);

if (!hasIdFieldInType) {
return;
}
function checkFields(rawType: GraphQLInterfaceType | GraphQLObjectType) {
const fields = rawType.getFields();
const hasIdFieldInType = idNames.some(name => fields[name]);

function hasIdField({ selections }: typeof node): boolean {
return selections.some(selection => {
if (selection.kind === Kind.FIELD) {
if (selection.alias && idNames.includes(selection.alias.value)) {
return true;
}
if (!hasIdFieldInType) {
return;
}

return idNames.includes(selection.name.value);
}
function hasIdField({ selections }: typeof node): boolean {
return selections.some(selection => {
if (selection.kind === Kind.FIELD) {
if (selection.alias && idNames.includes(selection.alias.value)) {
return true;
}

if (selection.kind === Kind.INLINE_FRAGMENT) {
return hasIdField(selection.selectionSet);
}
return idNames.includes(selection.name.value);
}

if (selection.kind === Kind.FRAGMENT_SPREAD) {
const [foundSpread] = siblings.getFragment(selection.name.value);
if (foundSpread) {
const fragmentSpread = foundSpread.document;
checkedFragmentSpreads.add(fragmentSpread.name.value);
return hasIdField(fragmentSpread.selectionSet);
if (selection.kind === Kind.INLINE_FRAGMENT) {
return hasIdField(selection.selectionSet);
}
}
return false;
});
}

const hasId = hasIdField(node);
if (selection.kind === Kind.FRAGMENT_SPREAD) {
const [foundSpread] = siblings.getFragment(selection.name.value);
if (foundSpread) {
const fragmentSpread = foundSpread.document;
checkedFragmentSpreads.add(fragmentSpread.name.value);
return hasIdField(fragmentSpread.selectionSet);
}
}
return false;
});
}

const hasId = hasIdField(node);

checkFragments(node as GraphQLESTreeNode<SelectionSetNode>);
checkFragments(node as GraphQLESTreeNode<SelectionSetNode>);

if (hasId) {
return;
}
if (hasId) {
return;
}

const pluralSuffix = idNames.length > 1 ? 's' : '';
const fieldName = englishJoinWords(
idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``),
);
const pluralSuffix = idNames.length > 1 ? 's' : '';
const fieldName = englishJoinWords(
idNames.map(name => `\`${(parent.alias || parent.name).value}.${name}\``),
);

const addition =
checkedFragmentSpreads.size === 0
? ''
: ` or add to used fragment${
checkedFragmentSpreads.size > 1 ? 's' : ''
} ${englishJoinWords([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;
const addition =
checkedFragmentSpreads.size === 0
? ''
: ` or add to used fragment${
checkedFragmentSpreads.size > 1 ? 's' : ''
} ${englishJoinWords([...checkedFragmentSpreads].map(name => `\`${name}\``))}`;

const problem: ReportDescriptor = {
loc,
messageId: RULE_ID,
data: {
pluralSuffix,
fieldName,
addition,
},
};
const problem: ReportDescriptor = {
loc,
messageId: RULE_ID,
data: {
pluralSuffix,
fieldName,
addition,
},
};

// Don't provide suggestions for selections in fragments as fragment can be in a separate file
if ('type' in node) {
problem.suggest = idNames.map(idName => ({
desc: `Add \`${idName}\` selection`,
fix: fixer => fixer.insertTextBefore((node as any).selections[0], `${idName} `),
}));
// Don't provide suggestions for selections in fragments as fragment can be in a separate file
if ('type' in node) {
problem.suggest = idNames.map(idName => ({
desc: `Add \`${idName}\` selection`,
fix: fixer => {
let insertNode = (node as any).selections[0];
insertNode =
insertNode.kind === Kind.INLINE_FRAGMENT
? insertNode.selectionSet.selections[0]
: insertNode;
return fixer.insertTextBefore(insertNode, `${idName} `);
},
}));
}
context.report(problem);
}
context.report(problem);
}

return {
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/tests/__snapshots__/alphabetize.spec.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Invalid #1 1`] = `
#### ⌨️ Code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Invalid #1 1`] = `
#### ⌨️ Code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Invalid #1 1`] = `
#### ⌨️ Code
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/tests/__snapshots__/examples.spec.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Examples > should work in monorepo 1`] = `
[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Invalid #1 1`] = `
#### ⌨️ Code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`should highlight selection on multi line 1`] = `
#### ⌨️ Code
Expand Down
2 changes: 1 addition & 1 deletion packages/plugin/tests/__snapshots__/input-name.spec.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`Invalid #1 1`] = `
#### ⌨️ Code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`should work only with Kind.FIELD 1`] = `
#### ⌨️ Code
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Vitest Snapshot v1
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`should not throw an error on undefined fragment 1`] = `
#### ⌨️ Code
Expand Down

0 comments on commit 12f802c

Please sign in to comment.