Skip to content

Instantly share code, notes, and snippets.

@stubailo
Last active November 9, 2020 18:55
Show Gist options
  • Save stubailo/7a2071c4e568a185726c583073695bc0 to your computer and use it in GitHub Desktop.
Save stubailo/7a2071c4e568a185726c583073695bc0 to your computer and use it in GitHub Desktop.
Find all GraphQL queries in your codebase that use a certain field
// This is a script that looks for usage of a specific field in GraphQL
// queries in your codebase.
//
// First, add a .graphqlconfig in your directory pointing to your schema
// Then, run the script with:
//
// node graphql-field-finder.js Field.name
//
// It will output a list of files and queries that contain the field you're
// looking for:
//
// src/filename.js:123 MyFavoriteQuery
//
// Please try this out and comment below! To learn more, check out the
// slides for the talk I gave about this here:
// https://www.slideshare.net/sashko1/building-custom-graphql-tooling-for-your-team
const { getGraphQLProjectConfig } = require("graphql-config");
const { exec } = require("child_process");
const { readFileSync } = require("fs");
const { parse: parseJS } = require("@babel/parser");
const { default: traverse } = require("@babel/traverse");
const {
parse: parseGraphQL,
visit,
visitWithTypeInfo,
TypeInfo
} = require("graphql");
const schema = getGraphQLProjectConfig().getSchema();
const desiredTypeDotField = getDesiredTypeDotField();
// Do a grep to find which files have GraphQL queries in them so we don't
// look through all JS in the whole app
exec(`git grep -rl "gql" -- '*.js' '*.jsx'`, (error, stdout, stderr) => {
const files = stdout.split("\n").filter(filename => !!filename.length);
const usages = files
.map(filename => findGraphQLUsagesInFile(filename, desiredTypeDotField))
.flat();
console.log(
usages
.map(usage => `${usage.filename}:${usage.line} ${usage.operationName}`)
.join("\n")
);
});
// Given a filename and a Type.field name, read the file contents and return a
// list of operations/fragments where this field is used.
function findGraphQLUsagesInFile(filename, typeDotField) {
const fileContents = readFileSync(filename, { encoding: "utf-8" });
const ast = parseJS(fileContents, {
sourceType: "module",
plugins: ["jsx", "flow", "classProperties"]
});
const graphqlStringNodes = [];
traverse(ast, {
TaggedTemplateExpression: function(path) {
if (path.node.tag.name === "gql") {
graphqlStringNodes.push(path.node.quasi.quasis[0]);
}
}
});
const usages = [];
graphqlStringNodes.forEach(jsNode => {
const ast = parseGraphQL(jsNode.value.raw);
const typeInfo = new TypeInfo(schema);
visit(
ast,
visitWithTypeInfo(typeInfo, {
Field(graphqlNode) {
const currentTypeDotField =
typeInfo.getParentType().name + "." + graphqlNode.name.value;
if (currentTypeDotField === typeDotField) {
usages.push({
filename,
operationName: ast.definitions[0].name.value,
line: jsNode.loc.start.line
});
}
}
})
);
});
return usages;
}
// Get the first argument passed to this script and validate it a bit
function getDesiredTypeDotField() {
const desiredTypeDotField = process.argv[2];
if (!desiredTypeDotField) {
throw new Error("Please supply a field name as Type.field");
}
const [typeName, fieldName] = desiredTypeDotField.split(".");
if (!schema.getType(typeName)) {
throw new Error(`Couldn't find type ${typeName} in schema.`);
}
if (!schema.getType(typeName).getFields()[fieldName]) {
throw new Error(`Couldn't find field ${fieldName} on type ${typeName}.`);
}
return desiredTypeDotField;
}
@dashmug
Copy link

dashmug commented Jun 21, 2019

Fair enough. Thank you for this. I would try replacing git grep with ripgrep as it's faster than grep and ignores git-ignored files by default.

@stubailo
Copy link
Author

Nice good idea!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment