-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
52b93d7
commit 61b4a83
Showing
7 changed files
with
345 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<FieldNode>; | ||
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 | ||
} | ||
}); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ASTNode> = T extends { type: TypeNode } | ||
? Omit<T, 'loc' | 'type'> & { gqlType: T['type'] } | ||
: Omit<T, 'loc'>; | ||
|
||
type Writeable<T> = { -readonly [K in keyof T]: T[K] }; | ||
|
||
export type TypeInformation = { | ||
argument: ReturnType<TypeInfo['getArgument']>; | ||
defaultValue: ReturnType<TypeInfo['getDefaultValue']>; | ||
directive: ReturnType<TypeInfo['getDirective']>; | ||
enumValue: ReturnType<TypeInfo['getEnumValue']>; | ||
fieldDef: ReturnType<TypeInfo['getFieldDef']>; | ||
inputType: ReturnType<TypeInfo['getInputType']>; | ||
parentInputType: ReturnType<TypeInfo['getParentInputType']>; | ||
parentType: ReturnType<TypeInfo['getParentType']>; | ||
gqlType: ReturnType<TypeInfo['getType']>; | ||
}; | ||
|
||
type NodeWithName = | ||
| ArgumentNode | ||
| DirectiveDefinitionNode | ||
| EnumValueDefinitionNode | ||
| ExecutableDefinitionNode | ||
| FieldDefinitionNode | ||
| FieldNode | ||
| FragmentSpreadNode | ||
| NamedTypeNode | ||
| TypeDefinitionNode | ||
| TypeExtensionNode | ||
| VariableNode; | ||
|
||
type NodeWithType = | ||
| FieldDefinitionNode | ||
| InputValueDefinitionNode | ||
| ListTypeNode | ||
| NonNullTypeNode | ||
| OperationTypeDefinitionNode | ||
| VariableDefinitionNode; | ||
|
||
type ParentNode<T> = 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<T extends ASTNode, WithTypeInfo extends boolean> = | ||
// Remove readonly for friendly editor popup | ||
Writeable<SafeGraphQLType<T>> & { | ||
type: T['kind']; | ||
loc: SourceLocation; | ||
range: AST.Range; | ||
leadingComments: Comment[]; | ||
typeInfo: () => WithTypeInfo extends true ? TypeInformation : Record<string, never>; | ||
rawNode: () => T; | ||
parent: GraphQLESTreeNode<ParentNode<T>>; | ||
}; | ||
|
||
export type GraphQLESTreeNode<T, W extends boolean = false> = | ||
// if value is ASTNode => convert to Node type | ||
T extends ASTNode | ||
? { | ||
// Loop recursively over object values | ||
[K in keyof Node<T, W>]: Node<T, W>[K] extends | ||
| ReadonlyArray<infer ArrayItem> | ||
| undefined // If optional readonly array => loop over array items | ||
? GraphQLESTreeNode<ArrayItem, W>[] | ||
: GraphQLESTreeNode<Node<T, W>[K], W>; | ||
} | ||
: // If Program node => add `parent: null` field | ||
T extends AST.Program | ||
? T & { parent: null } | ||
: // Return value as is | ||
T; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
82 changes: 82 additions & 0 deletions
82
test/rules/graphql/offline-graphql-unsupported-scope.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 }] | ||
} | ||
] | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const RULE_TESTER_CONFIG = { | ||
parser: '@graphql-eslint/eslint-plugin', | ||
parserOptions: { | ||
graphQLConfig: {} | ||
} | ||
}; |