graphql#ASTNode TypeScript Examples
The following examples show how to use
graphql#ASTNode.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: graphql.ts From amplify-codegen with Apache License 2.0 | 6 votes |
export function removeConnectionDirectives(ast: ASTNode) {
return visit(ast, {
Directive(node: DirectiveNode): DirectiveNode | null {
switch(node.name.value) {
// TODO: remove reference to 'connection' on transformer vNext release
case 'connection':
return null;
case 'hasOne':
return null;
case 'belongsTo':
return null;
case 'hasMany':
return null;
case 'manyToMany':
return null;
default:
return node;
}
},
});
}
Example #2
Source File: graphql.ts From amplify-codegen with Apache License 2.0 | 6 votes |
export function removeClientDirectives(ast: ASTNode) {
return visit(ast, {
Field(node: FieldNode): FieldNode | null {
if (node.directives && node.directives.find(directive => directive.name.value === 'client')) return null;
return node;
},
OperationDefinition: {
leave(node: OperationDefinitionNode): OperationDefinitionNode | null {
if (!node.selectionSet.selections.length) return null;
return node;
},
},
});
}
Example #3
Source File: graphql.ts From amplify-codegen with Apache License 2.0 | 6 votes |
export function withTypenameFieldAddedWhereNeeded(ast: ASTNode) {
return visit(ast, {
enter: {
SelectionSet(node: SelectionSetNode) {
return {
...node,
selections: node.selections.filter(
selection => !(selection.kind === 'Field' && (selection as FieldNode).name.value === '__typename')
),
};
},
},
leave(node: ASTNode) {
if (!(node.kind === 'Field' || node.kind === 'FragmentDefinition')) return undefined;
if (!node.selectionSet) return undefined;
if (true) {
return {
...node,
selectionSet: {
...node.selectionSet,
selections: [typenameField, ...node.selectionSet.selections],
},
};
} else {
return undefined;
}
},
});
}
Example #4
Source File: utils.ts From mercurius-typescript with MIT License | 6 votes |
toGraphQLString = (v: string | ASTNode) =>
typeof v === 'string' ? v : print(v)
Example #5
Source File: customWhitespaceMatcher.ts From ra-data-prisma with MIT License | 6 votes |
toEqualGraphql = (
received: ASTNode | ASTNode[],
expected: string | string[],
) => {
const receivedArray = Array.isArray(received) ? received : [received];
const expectedArray = Array.isArray(expected) ? expected : [expected];
const expectedArrayCompressed = expectedArray.map(cleanString);
const receivedPrinted = receivedArray.map((node) => cleanString(print(node)));
const pass = receivedPrinted.every(
(s, index) => s === expectedArrayCompressed[index],
);
const message = pass
? () =>
`${matcherHint(`.not.${name}`)}\n\n` +
` ${printExpected(expectedArrayCompressed)}\n` +
`Received:\n` +
` ${printReceived(receivedPrinted)}`
: () => {
const diffString = diff(expectedArrayCompressed, receivedPrinted);
return (
`${matcherHint(`.${name}`)}\n\n` +
`Expected value to equal:\n` +
` ${printExpected(expectedArrayCompressed)}\n` +
`Received:\n` +
` ${printReceived(receivedPrinted)}${
diffString ? `\n\nDifference:\n\n${diffString}` : ``
}`
);
};
return {
actual: received,
expected,
message,
name,
pass,
};
}
Example #6
Source File: no-unreachable-types.ts From graphql-eslint with MIT License | 5 votes |
function getReachableTypes(schema: GraphQLSchema): ReachableTypes {
// We don't want cache reachableTypes on test environment
// Otherwise reachableTypes will be same for all tests
if (process.env.NODE_ENV !== 'test' && reachableTypesCache) {
return reachableTypesCache;
}
const reachableTypes: ReachableTypes = new Set();
const collect = (node: ASTNode): false | void => {
const typeName = getTypeName(node);
if (reachableTypes.has(typeName)) {
return;
}
reachableTypes.add(typeName);
const type = schema.getType(typeName) || schema.getDirective(typeName);
if (isInterfaceType(type)) {
const { objects, interfaces } = schema.getImplementations(type);
for (const { astNode } of [...objects, ...interfaces]) {
visit(astNode, visitor);
}
} else if (type.astNode) {
// astNode can be undefined for ID, String, Boolean
visit(type.astNode, visitor);
}
};
const visitor: ASTVisitor = {
InterfaceTypeDefinition: collect,
ObjectTypeDefinition: collect,
InputValueDefinition: collect,
UnionTypeDefinition: collect,
FieldDefinition: collect,
Directive: collect,
NamedType: collect,
};
for (const type of [
schema, // visiting SchemaDefinition node
schema.getQueryType(),
schema.getMutationType(),
schema.getSubscriptionType(),
]) {
// if schema don't have Query type, schema.astNode will be undefined
if (type?.astNode) {
visit(type.astNode, visitor);
}
}
reachableTypesCache = reachableTypes;
return reachableTypesCache;
}
Example #7
Source File: graphql.ts From amplify-codegen with Apache License 2.0 | 5 votes |
export function filePathForNode(node: ASTNode): string {
const name = node.loc && node.loc.source && node.loc.source.name;
if (!name || name === 'GraphQL') {
throw new Error('Node does not seem to have a file path');
}
return name;
}
Example #8
Source File: alphabetize.ts From graphql-eslint with MIT License | 4 votes |
rule: GraphQLESLintRule<[AlphabetizeConfig]> = {
meta: {
type: 'suggestion',
fixable: 'code',
docs: {
category: ['Schema', 'Operations'],
description:
'Enforce arrange in alphabetical order for type fields, enum values, input object fields, operation selections and more.',
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
examples: [
{
title: 'Incorrect',
usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
code: /* GraphQL */ `
type User {
password: String
firstName: String! # should be before "password"
age: Int # should be before "firstName"
lastName: String!
}
`,
},
{
title: 'Correct',
usage: [{ fields: [Kind.OBJECT_TYPE_DEFINITION] }],
code: /* GraphQL */ `
type User {
age: Int
firstName: String!
lastName: String!
password: String
}
`,
},
{
title: 'Incorrect',
usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
code: /* GraphQL */ `
enum Role {
SUPER_ADMIN
ADMIN # should be before "SUPER_ADMIN"
USER
GOD # should be before "USER"
}
`,
},
{
title: 'Correct',
usage: [{ values: [Kind.ENUM_TYPE_DEFINITION] }],
code: /* GraphQL */ `
enum Role {
ADMIN
GOD
SUPER_ADMIN
USER
}
`,
},
{
title: 'Incorrect',
usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
code: /* GraphQL */ `
query {
me {
firstName
lastName
email # should be before "lastName"
}
}
`,
},
{
title: 'Correct',
usage: [{ selections: [Kind.OPERATION_DEFINITION] }],
code: /* GraphQL */ `
query {
me {
email
firstName
lastName
}
}
`,
},
],
configOptions: {
schema: [
{
fields: fieldsEnum,
values: valuesEnum,
arguments: argumentsEnum,
// TODO: add in graphql-eslint v4
// definitions: true,
},
],
operations: [
{
selections: selectionsEnum,
variables: variablesEnum,
arguments: [Kind.FIELD, Kind.DIRECTIVE],
},
],
},
},
messages: {
[RULE_ID]: '`{{ currName }}` should be before {{ prevName }}.',
},
schema: {
type: 'array',
minItems: 1,
maxItems: 1,
items: {
type: 'object',
additionalProperties: false,
minProperties: 1,
properties: {
fields: {
...ARRAY_DEFAULT_OPTIONS,
items: {
enum: fieldsEnum,
},
description: 'Fields of `type`, `interface`, and `input`.',
},
values: {
...ARRAY_DEFAULT_OPTIONS,
items: {
enum: valuesEnum,
},
description: 'Values of `enum`.',
},
selections: {
...ARRAY_DEFAULT_OPTIONS,
items: {
enum: selectionsEnum,
},
description: 'Selections of `fragment` and operations `query`, `mutation` and `subscription`.',
},
variables: {
...ARRAY_DEFAULT_OPTIONS,
items: {
enum: variablesEnum,
},
description: 'Variables of operations `query`, `mutation` and `subscription`.',
},
arguments: {
...ARRAY_DEFAULT_OPTIONS,
items: {
enum: argumentsEnum,
},
description: 'Arguments of fields and directives.',
},
definitions: {
type: 'boolean',
description: 'Definitions – `type`, `interface`, `enum`, `scalar`, `input`, `union` and `directive`.',
default: false,
},
},
},
},
},
create(context) {
const sourceCode = context.getSourceCode();
function isNodeAndCommentOnSameLine(node: { loc: SourceLocation }, comment: Comment): boolean {
return node.loc.end.line === comment.loc.start.line;
}
function getBeforeComments(node): Comment[] {
const commentsBefore = sourceCode.getCommentsBefore(node);
if (commentsBefore.length === 0) {
return [];
}
const tokenBefore = sourceCode.getTokenBefore(node);
if (tokenBefore) {
return commentsBefore.filter(comment => !isNodeAndCommentOnSameLine(tokenBefore, comment));
}
const filteredComments = [];
const nodeLine = node.loc.start.line;
// Break on comment that not attached to node
for (let i = commentsBefore.length - 1; i >= 0; i -= 1) {
const comment = commentsBefore[i];
if (nodeLine - comment.loc.start.line - filteredComments.length > 1) {
break;
}
filteredComments.unshift(comment);
}
return filteredComments;
}
function getRangeWithComments(node): AST.Range {
if (node.kind === Kind.VARIABLE) {
node = node.parent;
}
const [firstBeforeComment] = getBeforeComments(node);
const [firstAfterComment] = sourceCode.getCommentsAfter(node);
const from = firstBeforeComment || node;
const to = firstAfterComment && isNodeAndCommentOnSameLine(node, firstAfterComment) ? firstAfterComment : node;
return [from.range[0], to.range[1]];
}
function checkNodes(nodes: GraphQLESTreeNode<ASTNode>[]) {
// Starts from 1, ignore nodes.length <= 1
for (let i = 1; i < nodes.length; i += 1) {
const currNode = nodes[i];
const currName = 'name' in currNode && currNode.name?.value;
if (!currName) {
// we don't move unnamed current nodes
continue;
}
const prevNode = nodes[i - 1];
const prevName = 'name' in prevNode && prevNode.name?.value;
if (prevName) {
// Compare with lexicographic order
const compareResult = prevName.localeCompare(currName);
const shouldSort = compareResult === 1;
if (!shouldSort) {
const isSameName = compareResult === 0;
if (!isSameName || !prevNode.kind.endsWith('Extension') || currNode.kind.endsWith('Extension')) {
continue;
}
}
}
context.report({
node: currNode.name,
messageId: RULE_ID,
data: {
currName,
prevName: prevName ? `\`${prevName}\`` : lowerCase(prevNode.kind),
},
*fix(fixer) {
const prevRange = getRangeWithComments(prevNode);
const currRange = getRangeWithComments(currNode);
yield fixer.replaceTextRange(prevRange, sourceCode.getText({ range: currRange } as any));
yield fixer.replaceTextRange(currRange, sourceCode.getText({ range: prevRange } as any));
},
});
}
}
const opts = context.options[0];
const fields = new Set(opts.fields ?? []);
const listeners: GraphQLESLintRuleListener = {};
const kinds = [
fields.has(Kind.OBJECT_TYPE_DEFINITION) && [Kind.OBJECT_TYPE_DEFINITION, Kind.OBJECT_TYPE_EXTENSION],
fields.has(Kind.INTERFACE_TYPE_DEFINITION) && [Kind.INTERFACE_TYPE_DEFINITION, Kind.INTERFACE_TYPE_EXTENSION],
fields.has(Kind.INPUT_OBJECT_TYPE_DEFINITION) && [
Kind.INPUT_OBJECT_TYPE_DEFINITION,
Kind.INPUT_OBJECT_TYPE_EXTENSION,
],
]
.filter(Boolean)
.flat();
const fieldsSelector = kinds.join(',');
const hasEnumValues = opts.values?.[0] === Kind.ENUM_TYPE_DEFINITION;
const selectionsSelector = opts.selections?.join(',');
const hasVariables = opts.variables?.[0] === Kind.OPERATION_DEFINITION;
const argumentsSelector = opts.arguments?.join(',');
if (fieldsSelector) {
listeners[fieldsSelector] = (
node: GraphQLESTreeNode<
| ObjectTypeDefinitionNode
| ObjectTypeExtensionNode
| InterfaceTypeDefinitionNode
| InterfaceTypeExtensionNode
| InputObjectTypeDefinitionNode
| InputObjectTypeExtensionNode
>
) => {
checkNodes(node.fields);
};
}
if (hasEnumValues) {
const enumValuesSelector = [Kind.ENUM_TYPE_DEFINITION, Kind.ENUM_TYPE_EXTENSION].join(',');
listeners[enumValuesSelector] = (node: GraphQLESTreeNode<EnumTypeDefinitionNode | EnumTypeExtensionNode>) => {
checkNodes(node.values);
};
}
if (selectionsSelector) {
listeners[`:matches(${selectionsSelector}) SelectionSet`] = (node: GraphQLESTreeNode<SelectionSetNode>) => {
checkNodes(
node.selections.map(selection =>
// sort by alias is field is renamed
'alias' in selection && selection.alias ? ({ name: selection.alias } as any) : selection
)
);
};
}
if (hasVariables) {
listeners.OperationDefinition = (node: GraphQLESTreeNode<OperationDefinitionNode>) => {
checkNodes(node.variableDefinitions.map(varDef => varDef.variable));
};
}
if (argumentsSelector) {
listeners[argumentsSelector] = (
node: GraphQLESTreeNode<FieldDefinitionNode | FieldNode | DirectiveDefinitionNode | DirectiveNode>
) => {
checkNodes(node.arguments);
};
}
if (opts.definitions) {
listeners.Document = node => {
checkNodes(node.definitions);
};
}
return listeners;
},
}
Example #9
Source File: require-id-when-available.ts From graphql-eslint with MIT License | 4 votes |
rule: GraphQLESLintRule<[RequireIdWhenAvailableRuleConfig], true> = {
meta: {
type: 'suggestion',
// eslint-disable-next-line eslint-plugin/require-meta-has-suggestions
hasSuggestions: true,
docs: {
category: 'Operations',
description: 'Enforce selecting specific fields when they are available on the GraphQL type.',
url: `https://github.com/B2o5T/graphql-eslint/blob/master/docs/rules/${RULE_ID}.md`,
requiresSchema: true,
requiresSiblings: true,
examples: [
{
title: 'Incorrect',
code: /* GraphQL */ `
# In your schema
type User {
id: ID!
name: String!
}
# Query
query {
user {
name
}
}
`,
},
{
title: 'Correct',
code: /* GraphQL */ `
# In your schema
type User {
id: ID!
name: String!
}
# Query
query {
user {
id
name
}
}
# Selecting \`id\` with an alias is also valid
query {
user {
id: name
}
}
`,
},
],
recommended: true,
},
messages: {
[RULE_ID]:
"Field{{ pluralSuffix }} {{ fieldName }} must be selected when it's available on a type.\nInclude it in your selection set{{ addition }}.",
},
schema: {
definitions: {
asString: {
type: 'string',
},
asArray: ARRAY_DEFAULT_OPTIONS,
},
type: 'array',
maxItems: 1,
items: {
type: 'object',
additionalProperties: false,
properties: {
fieldName: {
oneOf: [{ $ref: '#/definitions/asString' }, { $ref: '#/definitions/asArray' }],
default: DEFAULT_ID_FIELD_NAME,
},
},
},
},
},
create(context) {
const schema = requireGraphQLSchemaFromContext(RULE_ID, context);
const siblings = requireSiblingsOperations(RULE_ID, context);
const { fieldName = DEFAULT_ID_FIELD_NAME } = context.options[0] || {};
const idNames = asArray(fieldName);
// Check selections only in OperationDefinition,
// skip selections of OperationDefinition and InlineFragment
const selector = 'OperationDefinition SelectionSet[parent.kind!=/(^OperationDefinition|InlineFragment)$/]';
const typeInfo = new TypeInfo(schema);
function checkFragments(node: GraphQLESTreeNode<SelectionSetNode>): void {
for (const selection of node.selections) {
if (selection.kind !== Kind.FRAGMENT_SPREAD) {
continue;
}
const [foundSpread] = siblings.getFragment(selection.name.value);
if (!foundSpread) {
continue;
}
const checkedFragmentSpreads = new Set<string>();
const visitor = visitWithTypeInfo(typeInfo, {
SelectionSet(node, key, parent: ASTNode) {
if (parent.kind === Kind.FRAGMENT_DEFINITION) {
checkedFragmentSpreads.add(parent.name.value);
} else if (parent.kind !== Kind.INLINE_FRAGMENT) {
checkSelections(node, typeInfo.getType(), selection.loc.start, parent, checkedFragmentSpreads);
}
},
});
visit(foundSpread.document, visitor);
}
}
function checkSelections(
node: OmitRecursively<SelectionSetNode, 'loc'>,
type: GraphQLOutputType,
// Fragment can be placed in separate file
// Provide actual fragment spread location instead of location in fragment
loc: ESTree.Position,
// Can't access to node.parent in GraphQL AST.Node, so pass as argument
parent: any,
checkedFragmentSpreads = new Set<string>()
): void {
const rawType = getBaseType(type);
const isObjectType = rawType instanceof GraphQLObjectType;
const isInterfaceType = rawType instanceof GraphQLInterfaceType;
if (!isObjectType && !isInterfaceType) {
return;
}
const fields = rawType.getFields();
const hasIdFieldInType = idNames.some(name => fields[name]);
if (!hasIdFieldInType) {
return;
}
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;
}
return idNames.includes(selection.name.value);
}
if (selection.kind === Kind.INLINE_FRAGMENT) {
return hasIdField(selection.selectionSet);
}
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>);
if (hasId) {
return;
}
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 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} `),
}));
}
context.report(problem);
}
return {
[selector](node: GraphQLESTreeNode<SelectionSetNode, true>) {
const typeInfo = node.typeInfo();
if (typeInfo.gqlType) {
checkSelections(node, typeInfo.gqlType, node.loc.start, node.parent);
}
},
};
},
}