UNPKG

@netlify/content-engine

Version:
244 lines (242 loc) 9.03 kB
"use strict"; // @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