@netlify/content-engine
Version:
244 lines (242 loc) • 9.03 kB
JavaScript
// @flow
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.multipleRootQueriesError = multipleRootQueriesError;
exports.graphqlError = graphqlError;
exports.unknownFragmentError = unknownFragmentError;
exports.duplicateFragmentError = duplicateFragmentError;
const fs = require(`fs-extra`);
const graphql_1 = require("graphql");
const code_frame_1 = require("@babel/code-frame");
const { distance: levenshtein } = require(`fastest-levenshtein`);
const lodash_camelcase_1 = __importDefault(require("lodash.camelcase"));
const reporter_1 = __importDefault(require("../reporter"));
const { locInGraphQlToLocInFile } = require(`./error-parser`);
const graphql_errors_codeframe_1 = require("./graphql-errors-codeframe");
const upper_first_1 = require("../core-utils/upper-first");
// These handle specific errors throw by RelayParser. If an error matches
// you get a pointer to the location in the query that is broken, otherwise
// we show the error and the query.
const handlers = [
[
/Unknown field `(.+)` on type `(.+)`/i,
([name], node) => {
if (node.kind === `Field` && node.name.value === name) {
return node.name.loc;
}
return null;
},
],
[
/Unknown argument `(.+)`/i,
([name], node) => {
if (node.kind === `Argument` && node.name.value === name) {
return node.name.loc;
}
return null;
},
],
[
/Unknown directive `@(.+)`/i,
([name], node) => {
if (node.kind === `Directive` && node.name.value === name) {
return node.name.loc;
}
return null;
},
],
];
function formatFilePath(filePath) {
return `${reporter_1.default.format.bold(`file:`)} ${reporter_1.default.format.blue(filePath)}`;
}
function formatError(message, filePath, codeFrame) {
return (reporter_1.default.stripIndent `
${message}
${formatFilePath(filePath)}
` + `\n\n${codeFrame}\n`);
}
function extractError(error) {
const docRegex = /Error:.(RelayParser|GraphQLParser):(.*)Source: document.`(.*)`.file.*(GraphQL.request.*^\s*$)/gms;
let matches;
let message = ``;
let docName = ``;
let codeBlock = ``;
while ((matches = docRegex.exec(error.toString())) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (matches.index === docRegex.lastIndex)
docRegex.lastIndex++;
[, , message, docName, codeBlock] = matches;
}
if (!message) {
message = error.toString();
}
message = message.trim();
return { message, codeBlock, docName };
}
function findLocation(extractedMessage, def) {
let location = null;
(0, graphql_1.visit)(def, {
enter(node) {
if (location)
return;
for (const [regex, handler] of handlers) {
const match = extractedMessage.match(regex);
if (!match)
continue;
if ((location = handler(match.slice(1), node)))
break;
}
},
});
return location;
}
function getCodeFrameFromRelayError(def, extractedMessage, _error) {
const { start, source } = findLocation(extractedMessage, def) || {};
const query = source ? source.body : (0, graphql_1.print)(def);
// we can't reliably get a location without the location source, since
// the printed query may differ from the original.
const { line, column } = (source && (0, graphql_1.getLocation)(source, start)) || {};
return (0, graphql_errors_codeframe_1.getCodeFrame)(query, line, column);
}
function multipleRootQueriesError(filePath, def, otherDef) {
const name = def.name.value;
const otherName = otherDef.name.value;
const field = def.selectionSet.selections[0].name.value;
const otherField = otherDef.selectionSet.selections[0].name.value;
const unifiedName = `${(0, lodash_camelcase_1.default)(name)}And${(0, upper_first_1.upperFirst)((0, lodash_camelcase_1.default)(otherName))}`;
// colors are problematic for tests as we can different
// results depending on platform, so we don't
// highlight code for tests
const highlightCode = process.env.NODE_ENV !== `test`;
return {
id: `85910`,
filePath,
context: {
name,
otherName,
beforeCodeFrame: (0, code_frame_1.codeFrameColumns)(reporter_1.default.stripIndent `
query ${otherName} {
${field} {
#...
}
}
query ${name} {
${otherField} {
#...
}
}
`, {
start: {
column: 0,
line: 0,
},
}, {
linesBelow: Number.MAX_SAFE_INTEGER,
highlightCode,
}),
afterCodeFrame: (0, code_frame_1.codeFrameColumns)(reporter_1.default.stripIndent `
query ${unifiedName} {
${field} {
#...
}
${otherField} {
#...
}
}
`, {
start: {
column: 0,
line: 0,
},
}, {
linesBelow: Number.MAX_SAFE_INTEGER,
highlightCode,
}),
},
};
}
function graphqlError(definitionsByName, error) {
let codeBlock;
const { message, docName } = extractError(error);
const { def, filePath } = definitionsByName.get(docName) || {};
if (filePath && docName) {
codeBlock = getCodeFrameFromRelayError(def, message, error);
const formattedMessage = formatError(message, filePath, codeBlock);
return { formattedMessage, docName, message, codeBlock };
}
let reportedMessage = `There was an error while compiling your site's GraphQL queries.
${message || error.message}
`;
if (error.message.match(/must be an instance of/)) {
reportedMessage +=
`This usually means that more than one instance of 'graphql' is installed ` +
`in your node_modules. Remove all but the top level one or run \`npm dedupe\` to fix it.`;
}
if (error.message.match(/Duplicate document/)) {
reportedMessage += `${error.message.slice(21)}\n`;
}
return { formattedMessage: reportedMessage, docName, message, codeBlock };
}
function unknownFragmentError({ fragmentNames, filePath, definition, node, }) {
const name = node.name.value;
const closestFragment = fragmentNames
.map((f) => {
return { fragment: f, score: levenshtein(name, f) };
})
.filter((f) => f.score < 10)
.sort((a, b) => a.score > b.score)[0]?.fragment;
let text;
try {
text = fs.readFileSync(filePath, { encoding: `utf-8` });
}
catch {
text = definition.text;
}
return {
id: `85908`,
filePath,
context: {
fragmentName: name,
closestFragment,
codeFrame: (0, code_frame_1.codeFrameColumns)(text, {
start: locInGraphQlToLocInFile(definition.templateLoc, (0, graphql_1.getLocation)({ body: definition.text }, node.loc.start)),
end: locInGraphQlToLocInFile(definition.templateLoc, (0, graphql_1.getLocation)({ body: definition.text }, node.loc.end)),
}, {
linesAbove: 10,
linesBelow: 10,
}),
},
};
}
function duplicateFragmentError({ name, leftDefinition, rightDefinition, }) {
return {
id: `85919`,
context: {
fragmentName: name,
leftFragment: {
filePath: leftDefinition.filePath,
codeFrame: (0, code_frame_1.codeFrameColumns)(leftDefinition.text, {
start: (0, graphql_1.getLocation)({ body: leftDefinition.text }, leftDefinition.def.name.loc.start),
end: (0, graphql_1.getLocation)({ body: leftDefinition.text }, leftDefinition.def.name.loc.end),
}, {
linesAbove: 10,
linesBelow: 10,
}),
},
rightFragment: {
filePath: rightDefinition.filePath,
codeFrame: (0, code_frame_1.codeFrameColumns)(rightDefinition.text, {
start: (0, graphql_1.getLocation)({ body: rightDefinition.text }, rightDefinition.def.name.loc.start),
end: (0, graphql_1.getLocation)({ body: rightDefinition.text }, rightDefinition.def.name.loc.end),
}, {
linesAbove: 10,
linesBelow: 10,
}),
},
},
};
}
//# sourceMappingURL=graphql-errors.js.map
;