From 93772a44e1088275995b246ff9dedc806d853fd9 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Mon, 20 May 2024 14:56:56 -0700 Subject: [PATCH 01/10] offline supported scope rule added --- src/index.ts | 11 +- .../offline-graphql-unsupported-scope.ts | 106 ++++++++++++++ src/rules/graphql/types.ts | 138 ++++++++++++++++++ .../graphql/no-mutation-supported.spec.ts | 9 +- .../offline-graphql-unsupported-scope.spec.ts | 82 +++++++++++ test/shared.ts | 6 + 6 files changed, 343 insertions(+), 9 deletions(-) create mode 100644 src/rules/graphql/offline-graphql-unsupported-scope.ts create mode 100644 src/rules/graphql/types.ts create mode 100644 test/rules/graphql/offline-graphql-unsupported-scope.spec.ts create mode 100644 test/shared.ts diff --git a/src/index.ts b/src/index.ts index 4a54b50..2c80a1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,9 +18,13 @@ import { NO_MUTATION_SUPPORTED_RULE_ID, rule as mutationNotSupported } from './rules/graphql/no-mutation-supported.js'; - import { name, version } from '../package.json'; +import { + rule as offlineGraphqlUnsupportedScope, + OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID +} from './rules/graphql/offline-graphql-unsupported-scope.js'; + export = { configs: { base, @@ -33,7 +37,8 @@ export = { rules: { 'enforce-foo-bar': enforceFooBar, [APEX_IMPORT_RULE_ID]: apexImport, - [NO_MUTATION_SUPPORTED_RULE_ID]: mutationNotSupported, - [NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID]: aggregateQueryNotSupported + [NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID]: aggregateQueryNotSupported, + [NO_MUTATION_SUPPORTED_RULE_ID]: mutionNotSupported, + [OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope } }; diff --git a/src/rules/graphql/offline-graphql-unsupported-scope.ts b/src/rules/graphql/offline-graphql-unsupported-scope.ts new file mode 100644 index 0000000..aebc55a --- /dev/null +++ b/src/rules/graphql/offline-graphql-unsupported-scope.ts @@ -0,0 +1,106 @@ +import { GraphQLESLintRule, GraphQLESLintRuleContext } from '@graphql-eslint/eslint-plugin'; +import { Kind, FieldNode } from 'graphql'; +import { GraphQLESTreeNode } from './types'; +export const OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; + +export const ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY = + 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; +export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; +export const rule: GraphQLESLintRule = { + meta: { + type: 'problem', + docs: { + description: `For mobile offline use cases, scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other unsupported scopes are team, queue-owned, user-owned and everything. `, + category: 'Operations', + recommended: true, + examples: [ + { + title: 'Incorrect', + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + Case(scope: EVERYTHING) { + edges { + node { + Id + } + } + } + } + } + } + ` + }, + { + title: 'Incorrect', + code: /* GraphQL */ ` + query assignedtomeQuery { + uiapi { + query { + Case(scope: ASSIGNEDTOME) { + edges { + node { + Id + } + } + } + } + } + } + ` + } + ] + }, + messages: { + [ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY]: + 'Offline GraphQL: Scope ‘ASSIGNEDTOME’ is only supported for the ServiceAppointment entity, for mobile offline use cases', + [OTHER_UNSUPPORTED_SCOPE]: + 'Offline GraphQL: Scope "{{scopeName}}" is unsupported for mobile offline use cases.' + }, + schema: [] + }, + + create(context: GraphQLESLintRuleContext) { + return { + Argument(node) { + if (node.name.value === 'scope') { + if (node.value.kind === Kind.ENUM) { + const scopeName = node.value.value; + if ( + scopeName === 'TEAM' || + scopeName === 'QUEUE_OWNED' || + scopeName === 'USER_OWNED' || + scopeName === 'EVERYTHING' + ) { + context.report({ + messageId: OTHER_UNSUPPORTED_SCOPE, + data: { + scopeName + }, + loc: { + start: node.loc.start, + end: node.value.loc.end + } + }); + } else if (node.value.value === 'ASSIGNEDTOME') { + const entityNode = node.parent as GraphQLESTreeNode; + if ( + entityNode.name.kind === Kind.NAME && + entityNode.name.value !== 'ServiceAppointment' + ) { + context.report({ + messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, + loc: { + start: node.loc.start, + end: node.value.loc.end + } + }); + } + } + } + } + } + }; + } +}; diff --git a/src/rules/graphql/types.ts b/src/rules/graphql/types.ts new file mode 100644 index 0000000..e4bf90b --- /dev/null +++ b/src/rules/graphql/types.ts @@ -0,0 +1,138 @@ +import { AST } from 'eslint'; +import { Comment, SourceLocation } from 'estree'; +import { + ArgumentNode, + ASTNode, + DefinitionNode, + DirectiveDefinitionNode, + DirectiveNode, + DocumentNode, + EnumTypeDefinitionNode, + EnumTypeExtensionNode, + EnumValueDefinitionNode, + ExecutableDefinitionNode, + FieldDefinitionNode, + FieldNode, + FragmentSpreadNode, + InlineFragmentNode, + InputObjectTypeDefinitionNode, + InputObjectTypeExtensionNode, + InputValueDefinitionNode, + InterfaceTypeDefinitionNode, + InterfaceTypeExtensionNode, + ListTypeNode, + NamedTypeNode, + NameNode, + NonNullTypeNode, + ObjectTypeDefinitionNode, + ObjectTypeExtensionNode, + OperationTypeDefinitionNode, + SelectionNode, + SelectionSetNode, + TypeDefinitionNode, + TypeExtensionNode, + TypeInfo, + TypeNode, + VariableDefinitionNode, + VariableNode +} from 'graphql'; + +type SafeGraphQLType = T extends { type: TypeNode } + ? Omit & { gqlType: T['type'] } + : Omit; + +type Writeable = { -readonly [K in keyof T]: T[K] }; + +export type TypeInformation = { + argument: ReturnType; + defaultValue: ReturnType; + directive: ReturnType; + enumValue: ReturnType; + fieldDef: ReturnType; + inputType: ReturnType; + parentInputType: ReturnType; + parentType: ReturnType; + gqlType: ReturnType; +}; + +type NodeWithName = + | ArgumentNode + | DirectiveDefinitionNode + | EnumValueDefinitionNode + | ExecutableDefinitionNode + | FieldDefinitionNode + | FieldNode + | FragmentSpreadNode + | NamedTypeNode + | TypeDefinitionNode + | TypeExtensionNode + | VariableNode; + +type NodeWithType = + | FieldDefinitionNode + | InputValueDefinitionNode + | ListTypeNode + | NonNullTypeNode + | OperationTypeDefinitionNode + | VariableDefinitionNode; + +type ParentNode = T extends DocumentNode + ? AST.Program + : T extends DefinitionNode + ? DocumentNode + : T extends EnumValueDefinitionNode + ? EnumTypeDefinitionNode | EnumTypeExtensionNode + : T extends InputValueDefinitionNode + ? + | DirectiveDefinitionNode + | FieldDefinitionNode + | InputObjectTypeDefinitionNode + | InputObjectTypeExtensionNode + : T extends FieldDefinitionNode + ? + | InterfaceTypeDefinitionNode + | InterfaceTypeExtensionNode + | ObjectTypeDefinitionNode + | ObjectTypeExtensionNode + : T extends SelectionSetNode + ? ExecutableDefinitionNode | FieldNode | InlineFragmentNode + : T extends SelectionNode + ? SelectionSetNode + : T extends TypeNode + ? NodeWithType + : T extends NameNode + ? NodeWithName + : T extends DirectiveNode + ? InputObjectTypeDefinitionNode | ObjectTypeDefinitionNode + : T extends VariableNode + ? VariableDefinitionNode + : unknown; // Explicitly show error to add new ternary with parent nodes + +type Node = + // Remove readonly for friendly editor popup + Writeable> & { + type: T['kind']; + loc: SourceLocation; + range: AST.Range; + leadingComments: Comment[]; + typeInfo: () => WithTypeInfo extends true ? TypeInformation : Record; + rawNode: () => T; + parent: GraphQLESTreeNode>; + }; + +export type GraphQLESTreeNode = + // if value is ASTNode => convert to Node type + T extends ASTNode + ? { + // Loop recursively over object values + [K in keyof Node]: Node[K] extends + | ReadonlyArray + | undefined // If optional readonly array => loop over array items + ? GraphQLESTreeNode[] + : GraphQLESTreeNode[K], W>; + } + : // If Program node => add `parent: null` field + T extends AST.Program + ? T & { parent: null } + : // Return value as is + T; diff --git a/test/rules/graphql/no-mutation-supported.spec.ts b/test/rules/graphql/no-mutation-supported.spec.ts index 04f681c..035e001 100644 --- a/test/rules/graphql/no-mutation-supported.spec.ts +++ b/test/rules/graphql/no-mutation-supported.spec.ts @@ -4,12 +4,9 @@ import { NO_MUTATION_SUPPORTED_RULE_ID } from '../../../src/rules/graphql/no-mutation-supported'; -const ruleTester = new RuleTester({ - parser: '@graphql-eslint/eslint-plugin', - parserOptions: { - graphQLConfig: {} - } -}); +import { RULE_TESTER_CONFIG } from '../../shared'; + +const ruleTester = new RuleTester(RULE_TESTER_CONFIG); ruleTester.run('@salesforce/lwc-mobile/no-mutation-supported', rule as any, { valid: [ diff --git a/test/rules/graphql/offline-graphql-unsupported-scope.spec.ts b/test/rules/graphql/offline-graphql-unsupported-scope.spec.ts new file mode 100644 index 0000000..7800f66 --- /dev/null +++ b/test/rules/graphql/offline-graphql-unsupported-scope.spec.ts @@ -0,0 +1,82 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { + rule, + ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, + OTHER_UNSUPPORTED_SCOPE +} from '../../../src/rules/graphql/offline-graphql-unsupported-scope'; +import { RULE_TESTER_CONFIG } from '../../shared'; + +const ruleTester = new RuleTester(RULE_TESTER_CONFIG); + +ruleTester.run('@salesforce/lwc-mobile/offline-graphql-unsupported-scope', rule as any, { + valid: [ + { + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + ServiceAppointment(first: 20, scope: ASSIGNEDTOME) { + edges { + node { + Id + Name { + value + } + } + } + } + } + } + } + ` + } + ], + invalid: [ + { + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + Case(scope: EVERYTHING) { + edges { + node { + Id + } + } + } + } + } + } + `, + errors: [ + { + messageId: OTHER_UNSUPPORTED_SCOPE, + data: { + scopeName: 'EVERYTHING' + } + } + ] + }, + { + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + Case(first: 20, scope: ASSIGNEDTOME) { + edges { + node { + Id + Name { + value + } + } + } + } + } + } + } + `, + errors: [{ messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY }] + } + ] +}); diff --git a/test/shared.ts b/test/shared.ts new file mode 100644 index 0000000..a732a85 --- /dev/null +++ b/test/shared.ts @@ -0,0 +1,6 @@ +export const RULE_TESTER_CONFIG = { + parser: '@graphql-eslint/eslint-plugin', + parserOptions: { + graphQLConfig: {} + } +}; From 2dd7b1a969207c1f8cb36ceeb3b7dfa6647490a5 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Tue, 21 May 2024 15:48:35 -0700 Subject: [PATCH 02/10] sync the change --- src/configs/recommended.ts | 7 +++++-- src/index.ts | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 6237f34..68769b3 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -8,6 +8,9 @@ import type { ClassicConfig } from '@typescript-eslint/utils/ts-eslint'; export = { extends: ['./configs/base'], + rules: { + '@salesforce/lwc-mobile/apex-import': 'warn' + }, overrides: [ { files: ['*.js'], @@ -21,9 +24,9 @@ export = { skipGraphQLConfig: true }, rules: { - '@salesforce/lwc-mobile/apex-import': 'warn', '@salesforce/lwc-mobile/offline-graphql-no-mutation-supported': 'warn', - '@salesforce/lwc-mobile/offline-graphql-no-aggregate-query-supported': 'warn' + '@salesforce/lwc-mobile/offline-graphql-no-aggregate-query-supported': 'warn', + '@salesforce/lwc-mobile/offline-graphql-unsupported-scope': 'warn' } } ] diff --git a/src/index.ts b/src/index.ts index 2c80a1e..23a4743 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,6 +39,7 @@ export = { [APEX_IMPORT_RULE_ID]: apexImport, [NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID]: aggregateQueryNotSupported, [NO_MUTATION_SUPPORTED_RULE_ID]: mutionNotSupported, - [OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope + [OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope, + [APEX_IMPORT_RULE_ID]: apexImport } }; From 1f504e4b65b3e611ca6a5568696efbb3c6fe294a Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Tue, 21 May 2024 17:13:36 -0700 Subject: [PATCH 03/10] sync main --- src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index 23a4743..2c80a1e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -39,7 +39,6 @@ export = { [APEX_IMPORT_RULE_ID]: apexImport, [NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID]: aggregateQueryNotSupported, [NO_MUTATION_SUPPORTED_RULE_ID]: mutionNotSupported, - [OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope, - [APEX_IMPORT_RULE_ID]: apexImport + [OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope } }; From 8e541a4aded8e4b5034a494e00b80a89739c0c68 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Mon, 20 May 2024 14:56:56 -0700 Subject: [PATCH 04/10] offline supported scope rule added --- src/index.ts | 6 +- src/rules/graphql/unsupported-scope.ts | 106 +++++++++++++++++++ test/rules/graphql/unsupported-scope.spec.ts | 82 ++++++++++++++ 3 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 src/rules/graphql/unsupported-scope.ts create mode 100644 test/rules/graphql/unsupported-scope.spec.ts diff --git a/src/index.ts b/src/index.ts index 2c80a1e..273e76c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -22,8 +22,8 @@ import { name, version } from '../package.json'; import { rule as offlineGraphqlUnsupportedScope, - OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID -} from './rules/graphql/offline-graphql-unsupported-scope.js'; + UNSUPPORTED_SCOPE_RULE_ID +} from './rules/graphql/unsupported-scope.js'; export = { configs: { @@ -39,6 +39,6 @@ export = { [APEX_IMPORT_RULE_ID]: apexImport, [NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID]: aggregateQueryNotSupported, [NO_MUTATION_SUPPORTED_RULE_ID]: mutionNotSupported, - [OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope + [UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope } }; diff --git a/src/rules/graphql/unsupported-scope.ts b/src/rules/graphql/unsupported-scope.ts new file mode 100644 index 0000000..a1f2a2c --- /dev/null +++ b/src/rules/graphql/unsupported-scope.ts @@ -0,0 +1,106 @@ +import { GraphQLESLintRule, GraphQLESLintRuleContext } from '@graphql-eslint/eslint-plugin'; +import { Kind } from 'graphql'; + +export const UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; + +export const ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY = + 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; +export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; +export const rule: GraphQLESLintRule = { + meta: { + type: 'problem', + docs: { + description: `For mobile offline use cases, scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other unsupported scopes are team, queue-owned, user-owned and everything. `, + category: 'Operations', + recommended: true, + examples: [ + { + title: 'Incorrect', + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + Case(scope: EVERYTHING) { + edges { + node { + Id + } + } + } + } + } + } + ` + }, + { + title: 'Incorrect', + code: /* GraphQL */ ` + query assignedtomeQuery { + uiapi { + query { + Case(scope: ASSIGNEDTOME) { + edges { + node { + Id + } + } + } + } + } + } + ` + } + ] + }, + messages: { + [ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY]: + 'Offline GraphQL: Scope ‘ASSIGNEDTOME’ is only supported for the ServiceAppointment entity, for mobile offline use cases', + [OTHER_UNSUPPORTED_SCOPE]: + 'Offline GraphQL: Scope "{{scopeName}}" is unsupported for mobile offline use cases.' + }, + schema: [] + }, + + create(context: GraphQLESLintRuleContext) { + return { + Argument(node) { + if (node.name.value === 'scope') { + if (node.value.kind === Kind.ENUM) { + const scopeName = node.value.value; + if ( + scopeName === 'TEAM' || + scopeName === 'QUEUE_OWNED' || + scopeName === 'USER_OWNED' || + scopeName === 'EVERYTHING' + ) { + context.report({ + messageId: OTHER_UNSUPPORTED_SCOPE, + data: { + scopeName + }, + loc: { + start: node.loc.start, + end: node.value.loc.end + } + }); + } else if (node.value.value === 'ASSIGNEDTOME') { + const entityNode = node.parent as any; + if ( + entityNode.name.kind === Kind.NAME && + entityNode.name.value !== 'ServiceAppointment' + ) { + context.report({ + messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, + loc: { + start: node.loc.start, + end: node.value.loc.end + } + }); + } + } + } + } + } + }; + } +}; diff --git a/test/rules/graphql/unsupported-scope.spec.ts b/test/rules/graphql/unsupported-scope.spec.ts new file mode 100644 index 0000000..a6a9e21 --- /dev/null +++ b/test/rules/graphql/unsupported-scope.spec.ts @@ -0,0 +1,82 @@ +import { RuleTester } from '@typescript-eslint/rule-tester'; +import { + rule, + ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, + OTHER_UNSUPPORTED_SCOPE +} from '../../../src/rules/graphql/unsupported-scope'; +import { RULE_TESTER_CONFIG } from '../../shared'; + +const ruleTester = new RuleTester(RULE_TESTER_CONFIG); + +ruleTester.run('@salesforce/lwc-mobile/offline-graphql-unsupported-scope', rule as any, { + valid: [ + { + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + ServiceAppointment(first: 20, scope: ASSIGNEDTOME) { + edges { + node { + Id + Name { + value + } + } + } + } + } + } + } + ` + } + ], + invalid: [ + { + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + Case(scope: EVERYTHING) { + edges { + node { + Id + } + } + } + } + } + } + `, + errors: [ + { + messageId: OTHER_UNSUPPORTED_SCOPE, + data: { + scopeName: 'EVERYTHING' + } + } + ] + }, + { + code: /* GraphQL */ ` + query scopeQuery { + uiapi { + query { + Case(first: 20, scope: ASSIGNEDTOME) { + edges { + node { + Id + Name { + value + } + } + } + } + } + } + } + `, + errors: [{ messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY }] + } + ] +}); From fe2e467dea24b4408ee79dfd06b012cd319550b2 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Tue, 21 May 2024 20:56:50 -0700 Subject: [PATCH 05/10] update files --- .../offline-graphql-unsupported-scope.ts | 106 -------------- src/rules/graphql/types.ts | 138 ------------------ .../offline-graphql-unsupported-scope.spec.ts | 82 ----------- 3 files changed, 326 deletions(-) delete mode 100644 src/rules/graphql/offline-graphql-unsupported-scope.ts delete mode 100644 src/rules/graphql/types.ts delete mode 100644 test/rules/graphql/offline-graphql-unsupported-scope.spec.ts diff --git a/src/rules/graphql/offline-graphql-unsupported-scope.ts b/src/rules/graphql/offline-graphql-unsupported-scope.ts deleted file mode 100644 index aebc55a..0000000 --- a/src/rules/graphql/offline-graphql-unsupported-scope.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { GraphQLESLintRule, GraphQLESLintRuleContext } from '@graphql-eslint/eslint-plugin'; -import { Kind, FieldNode } from 'graphql'; -import { GraphQLESTreeNode } from './types'; -export const OFFLINE_GRAPHQL_UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; - -export const ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY = - 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; -export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; -export const rule: GraphQLESLintRule = { - meta: { - type: 'problem', - docs: { - description: `For mobile offline use cases, scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other unsupported scopes are team, queue-owned, user-owned and everything. `, - category: 'Operations', - recommended: true, - examples: [ - { - title: 'Incorrect', - code: /* GraphQL */ ` - query scopeQuery { - uiapi { - query { - Case(scope: EVERYTHING) { - edges { - node { - Id - } - } - } - } - } - } - ` - }, - { - title: 'Incorrect', - code: /* GraphQL */ ` - query assignedtomeQuery { - uiapi { - query { - Case(scope: ASSIGNEDTOME) { - edges { - node { - Id - } - } - } - } - } - } - ` - } - ] - }, - messages: { - [ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY]: - 'Offline GraphQL: Scope ‘ASSIGNEDTOME’ is only supported for the ServiceAppointment entity, for mobile offline use cases', - [OTHER_UNSUPPORTED_SCOPE]: - 'Offline GraphQL: Scope "{{scopeName}}" is unsupported for mobile offline use cases.' - }, - schema: [] - }, - - create(context: GraphQLESLintRuleContext) { - return { - Argument(node) { - if (node.name.value === 'scope') { - if (node.value.kind === Kind.ENUM) { - const scopeName = node.value.value; - if ( - scopeName === 'TEAM' || - scopeName === 'QUEUE_OWNED' || - scopeName === 'USER_OWNED' || - scopeName === 'EVERYTHING' - ) { - context.report({ - messageId: OTHER_UNSUPPORTED_SCOPE, - data: { - scopeName - }, - loc: { - start: node.loc.start, - end: node.value.loc.end - } - }); - } else if (node.value.value === 'ASSIGNEDTOME') { - const entityNode = node.parent as GraphQLESTreeNode; - if ( - entityNode.name.kind === Kind.NAME && - entityNode.name.value !== 'ServiceAppointment' - ) { - context.report({ - messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, - loc: { - start: node.loc.start, - end: node.value.loc.end - } - }); - } - } - } - } - } - }; - } -}; diff --git a/src/rules/graphql/types.ts b/src/rules/graphql/types.ts deleted file mode 100644 index e4bf90b..0000000 --- a/src/rules/graphql/types.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { AST } from 'eslint'; -import { Comment, SourceLocation } from 'estree'; -import { - ArgumentNode, - ASTNode, - DefinitionNode, - DirectiveDefinitionNode, - DirectiveNode, - DocumentNode, - EnumTypeDefinitionNode, - EnumTypeExtensionNode, - EnumValueDefinitionNode, - ExecutableDefinitionNode, - FieldDefinitionNode, - FieldNode, - FragmentSpreadNode, - InlineFragmentNode, - InputObjectTypeDefinitionNode, - InputObjectTypeExtensionNode, - InputValueDefinitionNode, - InterfaceTypeDefinitionNode, - InterfaceTypeExtensionNode, - ListTypeNode, - NamedTypeNode, - NameNode, - NonNullTypeNode, - ObjectTypeDefinitionNode, - ObjectTypeExtensionNode, - OperationTypeDefinitionNode, - SelectionNode, - SelectionSetNode, - TypeDefinitionNode, - TypeExtensionNode, - TypeInfo, - TypeNode, - VariableDefinitionNode, - VariableNode -} from 'graphql'; - -type SafeGraphQLType = T extends { type: TypeNode } - ? Omit & { gqlType: T['type'] } - : Omit; - -type Writeable = { -readonly [K in keyof T]: T[K] }; - -export type TypeInformation = { - argument: ReturnType; - defaultValue: ReturnType; - directive: ReturnType; - enumValue: ReturnType; - fieldDef: ReturnType; - inputType: ReturnType; - parentInputType: ReturnType; - parentType: ReturnType; - gqlType: ReturnType; -}; - -type NodeWithName = - | ArgumentNode - | DirectiveDefinitionNode - | EnumValueDefinitionNode - | ExecutableDefinitionNode - | FieldDefinitionNode - | FieldNode - | FragmentSpreadNode - | NamedTypeNode - | TypeDefinitionNode - | TypeExtensionNode - | VariableNode; - -type NodeWithType = - | FieldDefinitionNode - | InputValueDefinitionNode - | ListTypeNode - | NonNullTypeNode - | OperationTypeDefinitionNode - | VariableDefinitionNode; - -type ParentNode = T extends DocumentNode - ? AST.Program - : T extends DefinitionNode - ? DocumentNode - : T extends EnumValueDefinitionNode - ? EnumTypeDefinitionNode | EnumTypeExtensionNode - : T extends InputValueDefinitionNode - ? - | DirectiveDefinitionNode - | FieldDefinitionNode - | InputObjectTypeDefinitionNode - | InputObjectTypeExtensionNode - : T extends FieldDefinitionNode - ? - | InterfaceTypeDefinitionNode - | InterfaceTypeExtensionNode - | ObjectTypeDefinitionNode - | ObjectTypeExtensionNode - : T extends SelectionSetNode - ? ExecutableDefinitionNode | FieldNode | InlineFragmentNode - : T extends SelectionNode - ? SelectionSetNode - : T extends TypeNode - ? NodeWithType - : T extends NameNode - ? NodeWithName - : T extends DirectiveNode - ? InputObjectTypeDefinitionNode | ObjectTypeDefinitionNode - : T extends VariableNode - ? VariableDefinitionNode - : unknown; // Explicitly show error to add new ternary with parent nodes - -type Node = - // Remove readonly for friendly editor popup - Writeable> & { - type: T['kind']; - loc: SourceLocation; - range: AST.Range; - leadingComments: Comment[]; - typeInfo: () => WithTypeInfo extends true ? TypeInformation : Record; - rawNode: () => T; - parent: GraphQLESTreeNode>; - }; - -export type GraphQLESTreeNode = - // if value is ASTNode => convert to Node type - T extends ASTNode - ? { - // Loop recursively over object values - [K in keyof Node]: Node[K] extends - | ReadonlyArray - | undefined // If optional readonly array => loop over array items - ? GraphQLESTreeNode[] - : GraphQLESTreeNode[K], W>; - } - : // If Program node => add `parent: null` field - T extends AST.Program - ? T & { parent: null } - : // Return value as is - T; diff --git a/test/rules/graphql/offline-graphql-unsupported-scope.spec.ts b/test/rules/graphql/offline-graphql-unsupported-scope.spec.ts deleted file mode 100644 index 7800f66..0000000 --- a/test/rules/graphql/offline-graphql-unsupported-scope.spec.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; -import { - rule, - ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, - OTHER_UNSUPPORTED_SCOPE -} from '../../../src/rules/graphql/offline-graphql-unsupported-scope'; -import { RULE_TESTER_CONFIG } from '../../shared'; - -const ruleTester = new RuleTester(RULE_TESTER_CONFIG); - -ruleTester.run('@salesforce/lwc-mobile/offline-graphql-unsupported-scope', rule as any, { - valid: [ - { - code: /* GraphQL */ ` - query scopeQuery { - uiapi { - query { - ServiceAppointment(first: 20, scope: ASSIGNEDTOME) { - edges { - node { - Id - Name { - value - } - } - } - } - } - } - } - ` - } - ], - invalid: [ - { - code: /* GraphQL */ ` - query scopeQuery { - uiapi { - query { - Case(scope: EVERYTHING) { - edges { - node { - Id - } - } - } - } - } - } - `, - errors: [ - { - messageId: OTHER_UNSUPPORTED_SCOPE, - data: { - scopeName: 'EVERYTHING' - } - } - ] - }, - { - code: /* GraphQL */ ` - query scopeQuery { - uiapi { - query { - Case(first: 20, scope: ASSIGNEDTOME) { - edges { - node { - Id - Name { - value - } - } - } - } - } - } - } - `, - errors: [{ messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY }] - } - ] -}); From 49661a11e7b8aa8165860ff40c0d2494330a9989 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Wed, 22 May 2024 10:56:52 -0700 Subject: [PATCH 06/10] better way to check unsupported scope --- src/rules/graphql/unsupported-scope.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/rules/graphql/unsupported-scope.ts b/src/rules/graphql/unsupported-scope.ts index a1f2a2c..b1bd1f4 100644 --- a/src/rules/graphql/unsupported-scope.ts +++ b/src/rules/graphql/unsupported-scope.ts @@ -6,6 +6,17 @@ export const UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; export const ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY = 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; + +const unsupportedScopes = [ + 'TEAM', + 'QUEUE_OWNED', + 'USER_OWNED', + 'EVERYTHING', + 'TEAM_BY_ROLE', + 'GENERICTEAM', + 'FOLLOWING', + 'FILES_SHARED_WITH_ME' +]; export const rule: GraphQLESLintRule = { meta: { type: 'problem', @@ -67,12 +78,7 @@ export const rule: GraphQLESLintRule = { if (node.name.value === 'scope') { if (node.value.kind === Kind.ENUM) { const scopeName = node.value.value; - if ( - scopeName === 'TEAM' || - scopeName === 'QUEUE_OWNED' || - scopeName === 'USER_OWNED' || - scopeName === 'EVERYTHING' - ) { + if (unsupportedScopes.includes(scopeName)) { context.report({ messageId: OTHER_UNSUPPORTED_SCOPE, data: { From 5536e9aee1bf4701ac235818f4c5396660681cf9 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Wed, 22 May 2024 11:53:27 -0700 Subject: [PATCH 07/10] generic message reporting --- src/rules/graphql/unsupported-scope.ts | 57 ++++++++++---------- test/rules/graphql/unsupported-scope.spec.ts | 12 ++++- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/src/rules/graphql/unsupported-scope.ts b/src/rules/graphql/unsupported-scope.ts index b1bd1f4..d74e0c3 100644 --- a/src/rules/graphql/unsupported-scope.ts +++ b/src/rules/graphql/unsupported-scope.ts @@ -3,25 +3,19 @@ import { Kind } from 'graphql'; export const UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; -export const ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY = - 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; +export const SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY = 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; -const unsupportedScopes = [ - 'TEAM', - 'QUEUE_OWNED', - 'USER_OWNED', - 'EVERYTHING', - 'TEAM_BY_ROLE', - 'GENERICTEAM', - 'FOLLOWING', - 'FILES_SHARED_WITH_ME' -]; +// key is scope name, value is the array of supported entities. Empty array means that all entities are supported. +const supportedScopes: Record = { + MINE: [], + ASSIGNEDTOME: ['ServiceAppointment'] +}; export const rule: GraphQLESLintRule = { meta: { type: 'problem', docs: { - description: `For mobile offline use cases, scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other unsupported scopes are team, queue-owned, user-owned and everything. `, + description: `For mobile offline use cases, scope "MINE" is supported and scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other scopes like TEAM, QUEUE_OWNED and USER_OWNED are not supported `, category: 'Operations', recommended: true, examples: [ @@ -64,8 +58,8 @@ export const rule: GraphQLESLintRule = { ] }, messages: { - [ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY]: - 'Offline GraphQL: Scope ‘ASSIGNEDTOME’ is only supported for the ServiceAppointment entity, for mobile offline use cases', + [SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY]: + 'Offline GraphQL: Scope "{{scopeName}}" is only supported for the "{{supportedEntities}}" entity, for mobile offline use cases', [OTHER_UNSUPPORTED_SCOPE]: 'Offline GraphQL: Scope "{{scopeName}}" is unsupported for mobile offline use cases.' }, @@ -78,7 +72,7 @@ export const rule: GraphQLESLintRule = { if (node.name.value === 'scope') { if (node.value.kind === Kind.ENUM) { const scopeName = node.value.value; - if (unsupportedScopes.includes(scopeName)) { + if (supportedScopes[scopeName] === undefined) { context.report({ messageId: OTHER_UNSUPPORTED_SCOPE, data: { @@ -89,19 +83,26 @@ export const rule: GraphQLESLintRule = { end: node.value.loc.end } }); - } else if (node.value.value === 'ASSIGNEDTOME') { - const entityNode = node.parent as any; - if ( - entityNode.name.kind === Kind.NAME && - entityNode.name.value !== 'ServiceAppointment' - ) { - context.report({ - messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, - loc: { - start: node.loc.start, - end: node.value.loc.end + } else { + const entities = supportedScopes[scopeName]; + if (entities.length > 0) { + const entityNode = node.parent as any; + if (entityNode.name.kind === Kind.NAME) { + const entityName = entityNode.name.value; + if (!entities.includes(entityName)) { + context.report({ + messageId: SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY, + loc: { + start: node.loc.start, + end: node.value.loc.end + }, + data: { + scopeName, + supportedEntities: entities.join(', ') + } + }); } - }); + } } } } diff --git a/test/rules/graphql/unsupported-scope.spec.ts b/test/rules/graphql/unsupported-scope.spec.ts index a6a9e21..bf98c8e 100644 --- a/test/rules/graphql/unsupported-scope.spec.ts +++ b/test/rules/graphql/unsupported-scope.spec.ts @@ -1,7 +1,7 @@ import { RuleTester } from '@typescript-eslint/rule-tester'; import { rule, - ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY, + SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY, OTHER_UNSUPPORTED_SCOPE } from '../../../src/rules/graphql/unsupported-scope'; import { RULE_TESTER_CONFIG } from '../../shared'; @@ -76,7 +76,15 @@ ruleTester.run('@salesforce/lwc-mobile/offline-graphql-unsupported-scope', rule } } `, - errors: [{ messageId: ASSIGNED_TO_ME_SUPPORTED_FOR_SERVICEAPPOINTMENT_ONLY }] + errors: [ + { + messageId: SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY, + data: { + scopeName: 'ASSIGNEDTOME', + supportedEntities: 'ServiceAppointment' + } + } + ] } ] }); From 86bdde29a7cca314f7e1239fe06a1345e2720b6b Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Thu, 23 May 2024 12:09:20 -0700 Subject: [PATCH 08/10] update based on changes on main --- jest.config.ts | 2 +- src/configs/recommended.ts | 2 +- src/index.ts | 11 ++++++----- src/rules/graphql/unsupported-scope.ts | 5 ++++- .../graphql/no-aggregate-query-supported.spec.ts | 9 +++------ 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/jest.config.ts b/jest.config.ts index 895dd9f..d9490c2 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -14,7 +14,7 @@ const config: Config = { testMatch: ['/test/**/*.ts'], moduleFileExtensions: ['ts', 'js', 'json'], testResultsProcessor: 'jest-sonar-reporter', - testPathIgnorePatterns: ['/node_modules/', '/lib/'], + testPathIgnorePatterns: ['/node_modules/', '/lib/', '/test/shared.ts'], moduleDirectories: ['node_modules'], collectCoverage: true, collectCoverageFrom: ['src/**/*.ts', '!src/index.ts', '!src/configs/*.ts'], diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts index 68769b3..f9f3867 100644 --- a/src/configs/recommended.ts +++ b/src/configs/recommended.ts @@ -24,8 +24,8 @@ export = { skipGraphQLConfig: true }, rules: { - '@salesforce/lwc-mobile/offline-graphql-no-mutation-supported': 'warn', '@salesforce/lwc-mobile/offline-graphql-no-aggregate-query-supported': 'warn', + '@salesforce/lwc-mobile/offline-graphql-no-mutation-supported': 'warn', '@salesforce/lwc-mobile/offline-graphql-unsupported-scope': 'warn' } } diff --git a/src/index.ts b/src/index.ts index 273e76c..967fbb1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -18,13 +18,14 @@ import { NO_MUTATION_SUPPORTED_RULE_ID, rule as mutationNotSupported } from './rules/graphql/no-mutation-supported.js'; -import { name, version } from '../package.json'; import { - rule as offlineGraphqlUnsupportedScope, - UNSUPPORTED_SCOPE_RULE_ID + UNSUPPORTED_SCOPE_RULE_ID, + rule as unsupportedScope } from './rules/graphql/unsupported-scope.js'; +import { name, version } from '../package.json'; + export = { configs: { base, @@ -38,7 +39,7 @@ export = { 'enforce-foo-bar': enforceFooBar, [APEX_IMPORT_RULE_ID]: apexImport, [NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID]: aggregateQueryNotSupported, - [NO_MUTATION_SUPPORTED_RULE_ID]: mutionNotSupported, - [UNSUPPORTED_SCOPE_RULE_ID]: offlineGraphqlUnsupportedScope + [NO_MUTATION_SUPPORTED_RULE_ID]: mutationNotSupported, + [UNSUPPORTED_SCOPE_RULE_ID]: unsupportedScope } }; diff --git a/src/rules/graphql/unsupported-scope.ts b/src/rules/graphql/unsupported-scope.ts index d74e0c3..4fd34a4 100644 --- a/src/rules/graphql/unsupported-scope.ts +++ b/src/rules/graphql/unsupported-scope.ts @@ -6,6 +6,8 @@ export const UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; export const SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY = 'ASSIGNED_TO_ME__SERVICEAPPOINTMENT_ONLY'; export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; +import getDocUrl from '../../util/getDocUrl'; + // key is scope name, value is the array of supported entities. Empty array means that all entities are supported. const supportedScopes: Record = { MINE: [], @@ -15,8 +17,9 @@ export const rule: GraphQLESLintRule = { meta: { type: 'problem', docs: { - description: `For mobile offline use cases, scope "MINE" is supported and scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other scopes like TEAM, QUEUE_OWNED and USER_OWNED are not supported `, category: 'Operations', + description: `For mobile offline use cases, scope "MINE" is supported and scope "ASSIGNEDTOME" is only supported for ServiceAppointment . All other scopes like TEAM, QUEUE_OWNED and USER_OWNED are not supported `, + url: getDocUrl(UNSUPPORTED_SCOPE_RULE_ID), recommended: true, examples: [ { diff --git a/test/rules/graphql/no-aggregate-query-supported.spec.ts b/test/rules/graphql/no-aggregate-query-supported.spec.ts index a034b7e..43bf7bf 100644 --- a/test/rules/graphql/no-aggregate-query-supported.spec.ts +++ b/test/rules/graphql/no-aggregate-query-supported.spec.ts @@ -4,12 +4,9 @@ import { NO_AGGREGATE_QUERY_SUPPORTED_RULE_ID } from '../../../src/rules/graphql/no-aggregate-query-supported'; -const ruleTester = new RuleTester({ - parser: '@graphql-eslint/eslint-plugin', - parserOptions: { - graphQLConfig: {} - } -}); +import { RULE_TESTER_CONFIG } from '../../shared'; + +const ruleTester = new RuleTester(RULE_TESTER_CONFIG); ruleTester.run('@salesforce/lwc-mobile/no-aggregate-query-supported', rule as any, { valid: [ From 1de56175024a7b84f808283384ac4cf674d994ec Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Thu, 23 May 2024 12:15:18 -0700 Subject: [PATCH 09/10] clean up code --- src/rules/graphql/unsupported-scope.ts | 64 +++++++++++++------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/rules/graphql/unsupported-scope.ts b/src/rules/graphql/unsupported-scope.ts index 4fd34a4..5060024 100644 --- a/src/rules/graphql/unsupported-scope.ts +++ b/src/rules/graphql/unsupported-scope.ts @@ -72,39 +72,37 @@ export const rule: GraphQLESLintRule = { create(context: GraphQLESLintRuleContext) { return { Argument(node) { - if (node.name.value === 'scope') { - if (node.value.kind === Kind.ENUM) { - const scopeName = node.value.value; - if (supportedScopes[scopeName] === undefined) { - context.report({ - messageId: OTHER_UNSUPPORTED_SCOPE, - data: { - scopeName - }, - loc: { - start: node.loc.start, - end: node.value.loc.end - } - }); - } else { - const entities = supportedScopes[scopeName]; - if (entities.length > 0) { - const entityNode = node.parent as any; - if (entityNode.name.kind === Kind.NAME) { - const entityName = entityNode.name.value; - if (!entities.includes(entityName)) { - context.report({ - messageId: SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY, - loc: { - start: node.loc.start, - end: node.value.loc.end - }, - data: { - scopeName, - supportedEntities: entities.join(', ') - } - }); - } + if (node.name.value === 'scope' && node.value.kind === Kind.ENUM) { + const scopeName = node.value.value; + if (supportedScopes[scopeName] === undefined) { + context.report({ + messageId: OTHER_UNSUPPORTED_SCOPE, + data: { + scopeName + }, + loc: { + start: node.loc.start, + end: node.value.loc.end + } + }); + } else { + const entities = supportedScopes[scopeName]; + if (entities.length > 0) { + const entityNode = node.parent as any; + if (entityNode.name.kind === Kind.NAME) { + const entityName = entityNode.name.value; + if (!entities.includes(entityName)) { + context.report({ + messageId: SCOPE_SUPPORTED_FOR_CERTAIN_ENTITIES_ONLY, + loc: { + start: node.loc.start, + end: node.value.loc.end + }, + data: { + scopeName, + supportedEntities: entities.join(', ') + } + }); } } } From d8558fd1b86580ae12206a830edd5d691de745b5 Mon Sep 17 00:00:00 2001 From: "haifeng.li" Date: Thu, 23 May 2024 15:50:29 -0700 Subject: [PATCH 10/10] create a custom type to replace any --- src/rules/graphql/unsupported-scope.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/rules/graphql/unsupported-scope.ts b/src/rules/graphql/unsupported-scope.ts index 5060024..25e952c 100644 --- a/src/rules/graphql/unsupported-scope.ts +++ b/src/rules/graphql/unsupported-scope.ts @@ -1,5 +1,5 @@ import { GraphQLESLintRule, GraphQLESLintRuleContext } from '@graphql-eslint/eslint-plugin'; -import { Kind } from 'graphql'; +import { Kind, NameNode } from 'graphql'; export const UNSUPPORTED_SCOPE_RULE_ID = 'offline-graphql-unsupported-scope'; @@ -8,6 +8,10 @@ export const OTHER_UNSUPPORTED_SCOPE = 'OTHER_UNSUPPORTED_SCOPE'; import getDocUrl from '../../util/getDocUrl'; +type NodeWithName = { + name: NameNode; +}; + // key is scope name, value is the array of supported entities. Empty array means that all entities are supported. const supportedScopes: Record = { MINE: [], @@ -88,7 +92,7 @@ export const rule: GraphQLESLintRule = { } else { const entities = supportedScopes[scopeName]; if (entities.length > 0) { - const entityNode = node.parent as any; + const entityNode = node.parent as NodeWithName; if (entityNode.name.kind === Kind.NAME) { const entityName = entityNode.name.value; if (!entities.includes(entityName)) {