UNPKG

graphql-document-analyzer

Version:

Resilient analyzing and printing of GraphQL documents

1,155 lines (1,133 loc) 32.4 kB
// src/analyze.ts import { isExecutableDefinitionNode, Kind as Kind2, Location as Location4, parse as graphqlParse, Source as Source7 } from "graphql"; // src/lib/source.ts import { Source, Token, TokenKind } from "graphql"; function substring(source, location) { return source.body.substring(location.start, location.end); } function splitLines(source) { source = typeof source === "string" ? new Source(source) : source; const lines = []; let lastIndex = 0; let line = 0; for (const match of source.body.matchAll(/\r?\n/g)) { line += 1; if (match.index == null) continue; const start2 = lastIndex; const end2 = match.index; const value2 = source.body.substring(start2, end2); lines.push(new Token(TokenKind.STRING, start2, end2, line, 1, null, value2)); lastIndex = match.index + match[0].length; } const start = lastIndex; const end = source.body.length; const value = source.body.substring(start, end); lines.push(new Token(TokenKind.STRING, start, end, line + 1, 1, null, value)); return lines; } // src/resilient-parser.ts import { Kind, Location as Location3, Source as Source6, TokenKind as TokenKind5 } from "graphql"; import { Parser } from "graphql/language/parser"; // src/extended-ast.ts import { Location as Location2 } from "graphql"; // src/lib/is-record.ts function isRecord(value) { return value != null && typeof value === "object"; } // src/extended-ast.ts function isExtendedNode(node) { return isRecord(node) && (node.kind === "ExtendedDocument" || node.kind === "InvalidOperationDefinition" || node.kind === "InvalidFragmentDefinition" || node.kind === "Invalid" || node.kind === "Ignored"); } function isExtendedDocumentNode(node) { return isRecord(node) && node.kind === "ExtendedDocument"; } function invalidOperationDefinition(source, operation, name, start, end) { const loc = new Location2(start, end, source); return { kind: "InvalidOperationDefinition", operation, name: name ? { kind: "Name", value: name } : void 0, value: substring(source, loc), loc }; } function invalidShorthandOperationDefinition(source, start, end) { const loc = new Location2(start, end, source); return { kind: "InvalidOperationDefinition", operation: "query", value: substring(source, loc), loc }; } function invalidFragment(source, name, typeCondition, start, end) { const loc = new Location2(start, end, source); return { kind: "InvalidFragmentDefinition", name: { kind: "Name", value: name }, typeCondition: { kind: "NamedType", name: { kind: "Name", value: typeCondition } }, value: substring(source, loc), loc }; } function invalid(source, start, end) { const loc = new Location2(start, end, source); return { kind: "Invalid", value: substring(source, loc), loc }; } function ignored(source, start, end) { const loc = new Location2(start, end, source); return { kind: "Ignored", value: substring(source, loc), loc }; } // src/lib/insert-whitespace.ts import { Token as Token3, TokenKind as TokenKind2 } from "graphql"; function insertWhitespace(source, lines, sections) { var _a, _b; const lastLine = lines.at(-1); if (!lastLine) return sections; const SOF = new Token3(TokenKind2.SOF, 0, 0, 0, 0, null); const EOF = new Token3( TokenKind2.EOF, lastLine.end, lastLine.end, lastLine.line + 1, lastLine.column + ((_b = (_a = lastLine.value) == null ? void 0 : _a.length) != null ? _b : 0), lastLine ); if (!sections.length) { return [ignored(source, SOF, EOF)]; } const lastSection = sections.at(-1); const trailingRange = (lastSection == null ? void 0 : lastSection.loc) ? linesBetween(lines, lastSection.loc.endToken, EOF) : void 0; const trailing = trailingRange ? ignored(source, trailingRange[0], trailingRange[1]) : void 0; const withWhitespace = [ ...sections.map((section, index) => { var _a2, _b2, _c, _d; const before = (_c = (_b2 = (_a2 = sections[index - 1]) == null ? void 0 : _a2.loc) == null ? void 0 : _b2.endToken) != null ? _c : SOF; const leadingRange = section.loc ? linesBetween(lines, before, (_d = section.loc) == null ? void 0 : _d.startToken) : void 0; const leading = leadingRange ? ignored(source, leadingRange[0], leadingRange[1]) : void 0; return [leading, section]; }).flat(), trailing ].filter(Boolean); return withWhitespace; } function linesBetween(lines, before, after) { const startIndex = before.line - 1 + 1; const endIndex = after.line - 1 - 1; if (startIndex < 0 || endIndex < 0) return void 0; if (startIndex > endIndex) return void 0; const start = lines[startIndex]; const end = lines[endIndex]; return start && end ? [start, end] : void 0; } // src/lib/landmarks.ts import { TokenKind as TokenKind4 } from "graphql"; // src/lib/lexer.ts import { Token as Token4, TokenKind as TokenKind3 } from "graphql"; function restoreLexer(lexer, snapshot) { lexer.lastToken = snapshot.lastToken; lexer.token = snapshot.token; lexer.line = snapshot.line; lexer.lineStart = snapshot.lineStart; } function snapshotLexer(lexer) { const { lastToken, token, line, lineStart } = lexer; return { lastToken: new Token4( lastToken.kind, lastToken.start, lastToken.end, lastToken.line, lastToken.column, lastToken.prev, lastToken.value ), token: new Token4( token.kind, token.start, token.end, token.line, token.column, token.prev, token.value ), line, lineStart }; } function safeAdvanceToEOF(lexer) { while (lexer.token.kind !== TokenKind3.EOF) { safeAdvance(lexer); } } function strictAdvanceToEOF(lexer) { while (lexer.token.kind !== TokenKind3.EOF) { if (lexer.token.kind !== TokenKind3.COMMENT) { throw new Error(`Unexpected token kind "${lexer.token.kind}"`); } lexer.advance(); } } function safeAdvance(lexer) { try { lexer.advance(); } catch { const lines = splitLines(lexer.source); const EOF = new Token4( TokenKind3.EOF, lexer.source.body.length, lexer.source.body.length, lines.length + 1, 1, lexer.token ); lexer.lastToken = EOF; lexer.token = EOF; } } // src/lib/landmarks.ts function findLandmarks(source, lines) { const landmarks = []; for (const line of lines) { const operation = tryParseOperation(source, line); if (operation) landmarks.push(operation); const fragment = tryParseFragment(source, line); if (fragment) landmarks.push(fragment); } return landmarks; } function findNextLandmark(landmarks, lineNumber = 1) { return landmarks.find( (landmark) => { var _a; return landmark.loc && ((_a = landmark.loc) == null ? void 0 : _a.startToken.line) >= lineNumber; } ); } function safeAdvanceToLandmark(lexer, landmark) { while (landmark.loc ? lexer.token.kind !== TokenKind4.EOF && lexer.token.line < landmark.loc.startToken.line : lexer.token.kind !== TokenKind4.EOF) { safeAdvance(lexer); } } function strictAdvanceToLandmark(lexer, landmark) { while ((landmark == null ? void 0 : landmark.loc) ? lexer.token.kind !== TokenKind4.EOF && lexer.token.line < landmark.loc.startToken.line : lexer.token.kind !== TokenKind4.EOF) { if (lexer.token.kind !== TokenKind4.COMMENT) { throw new Error(`Unexpected token kind "${lexer.token.kind}"`); } lexer.advance(); } } var OPERATION = /^\s*(query\s*|mutation\s*|subscription\s*)(\w+)?.*{/; function tryParseOperation(source, line) { var _a, _b, _c; if (!line.value) return false; const match = line.value.match(OPERATION); if (match == null) return false; const operation = (_b = (_a = match[1]) == null ? void 0 : _a.trim()) != null ? _b : "query"; const name = (_c = match[2]) == null ? void 0 : _c.trim(); return invalidOperationDefinition(source, operation, name, line, line); } var FRAGMENT = /^\s*fragment\s+(\w+)\s+on\s+(\w+)\s*/; function tryParseFragment(source, line) { if (!line.value) return false; const match = line.value.match(FRAGMENT); if (match == null) return false; const name = match[1]; const typeCondition = match[2]; return invalidFragment(source, name, typeCondition, line, line); } // src/resilient-parser.ts var ResilientParser = class extends Parser { constructor(source, options = {}) { source = typeof source === "string" ? new Source6(source) : source; super(source, options); this._lines = splitLines(source); this._landmarks = findLandmarks(source, this._lines); } parseExtendedDocument() { const definitions = this.zeroToMany( TokenKind5.SOF, this.parseSection, TokenKind5.EOF ); const sections = insertWhitespace( this._lexer.source, this._lines, definitions ); return { kind: "ExtendedDocument", sections }; } parseSection() { if (this.peek(TokenKind5.NAME)) { switch (this._lexer.token.value) { case "query": case "mutation": case "subscription": return this.tryParseOperationDefinition(); case "fragment": return this.tryParseFragmentDefinition(); } } else if (this.peek(TokenKind5.BRACE_L)) { return this.tryParseOperationDefinition(); } return this.parseInvalid(); } tryParseOperationDefinition() { const snapshot = snapshotLexer(this._lexer); try { const definition = this.parseOperationDefinition(); const next = findNextLandmark(this._landmarks, this._lexer.token.line); if (next) { strictAdvanceToLandmark(this._lexer, next); } else { strictAdvanceToEOF(this._lexer); } return definition; } catch (error) { restoreLexer(this._lexer, snapshot); const start = this._lexer.token; const invalidOperation = start.kind === TokenKind5.BRACE_L ? invalidShorthandOperationDefinition( this._lexer.source, start, start ) : tryParseOperation(this._lexer.source, this._lines[start.line - 1]); if (!invalidOperation) return this.parseInvalid(); const next = findNextLandmark( this._landmarks, this._lexer.token.line + 1 ); if (next) { safeAdvanceToLandmark(this._lexer, next); } else { safeAdvanceToEOF(this._lexer); } const end = this._lexer.lastToken; const loc = new Location3(start, end, this._lexer.source); return { ...invalidOperation, value: substring(this._lexer.source, loc), loc }; } } tryParseFragmentDefinition() { const snapshot = snapshotLexer(this._lexer); try { const fragment = this.parseFragmentDefinition(); const next = findNextLandmark(this._landmarks, this._lexer.token.line); if (next) { strictAdvanceToLandmark(this._lexer, next); } else { strictAdvanceToEOF(this._lexer); } return fragment; } catch (error) { restoreLexer(this._lexer, snapshot); const start = this._lexer.token; const invalidFragment2 = tryParseFragment( this._lexer.source, this._lines[start.line - 1] ); if (!invalidFragment2) return this.parseInvalid(); const next = findNextLandmark( this._landmarks, this._lexer.token.line + 1 ); if (next) { safeAdvanceToLandmark(this._lexer, next); } else { safeAdvanceToEOF(this._lexer); } const end = this._lexer.lastToken; const loc = new Location3(start, end, this._lexer.source); return { ...invalidFragment2, value: substring(this._lexer.source, loc), loc }; } } parseInvalid() { const start = this._lexer.token; const next = findNextLandmark(this._landmarks, this._lexer.token.line + 1); if (next) { safeAdvanceToLandmark(this._lexer, next); } else { safeAdvanceToEOF(this._lexer); } const end = this._lexer.lastToken; return invalid(this._lexer.source, start, end); } // @override parseSelectionSet() { const start = this._lexer.token; return { kind: Kind.SELECTION_SET, selections: this.zeroToMany( TokenKind5.BRACE_L, this.parseSelection, TokenKind5.BRACE_R ), loc: this.loc(start) }; } /** * Returns a list of parse nodes that may be empty, determined by the parseFn. * This list begins with a lex token of openKind and ends with a lex token of closeKind. * Advances the parser to the next lex token after the closing token. */ zeroToMany(openKind, parseFn, closeKind) { this.expectToken(openKind); const nodes = []; while (!this.expectOptionalToken(closeKind)) { nodes.push(parseFn.call(this)); } return nodes; } }; // src/analyze.ts function analyze(source, options) { const parser = new ResilientParser(source, options); return parser.parseExtendedDocument(); } // src/interpolate.ts function interpolate(document, reference) { const documentOutline = document.sections.filter(isRelevant); const referenceOutline = reference.sections.filter(isRelevant); if (documentOutline.length !== referenceOutline.length) return document; const sections = document.sections.map((section) => { if (section.kind === "InvalidOperationDefinition") { const index = documentOutline.indexOf(section); const isAnonymous = !section.name; const replacement = isAnonymous ? referenceOutline.find(findAnonymousOperation(section, index)) : referenceOutline.find(findNamedOperation(section)); if (replacement) { return { ...replacement, loc: section.loc }; } } if (section.kind === "InvalidFragmentDefinition") { const replacement = referenceOutline.find(findFragment(section)); if (replacement) { return { ...replacement, loc: section.loc }; } } return section; }); return { kind: "ExtendedDocument", sections }; } function isRelevant(section) { return section.kind === "OperationDefinition" || section.kind === "InvalidOperationDefinition" || section.kind === "FragmentDefinition" || section.kind === "InvalidFragmentDefinition"; } function findAnonymousOperation(operation, index) { return (relevant, relevantIndex) => { return index === relevantIndex && relevant.kind === "OperationDefinition" && relevant.operation === operation.operation && !relevant.name; }; } function findNamedOperation(operation) { return (relevant) => { var _a; return relevant.kind === "OperationDefinition" && relevant.operation === operation.operation && !!relevant.name && relevant.name.value === ((_a = operation.name) == null ? void 0 : _a.value); }; } function findFragment(fragment) { return (relevant) => { return relevant.kind === "FragmentDefinition" && relevant.name.value === fragment.name.value; }; } // src/print.ts import { Kind as Kind3, visit as visit2 } from "graphql"; // src/graphql-js/language/characterClasses.ts function isWhiteSpace(code) { return code === 9 || code === 32; } // src/graphql-js/language/blockString.ts function printBlockString(value, options) { const escapedValue = value.replace(/"""/g, '\\"""'); const lines = escapedValue.split(/\r\n|[\n\r]/g); const isSingleLine = lines.length === 1; const forceLeadingNewLine = lines.length > 1 && lines.slice(1).every((line) => line.length === 0 || isWhiteSpace(line.charCodeAt(0))); const hasTrailingTripleQuotes = escapedValue.endsWith('\\"""'); const hasTrailingQuote = value.endsWith('"') && !hasTrailingTripleQuotes; const hasTrailingSlash = value.endsWith("\\"); const forceTrailingNewline = hasTrailingQuote || hasTrailingSlash; const printAsMultipleLines = !(options == null ? void 0 : options.minimize) && // add leading and trailing new lines only if it improves readability (!isSingleLine || value.length > 70 || forceTrailingNewline || forceLeadingNewLine || hasTrailingTripleQuotes); let result = ""; const skipLeadingNewLine = isSingleLine && isWhiteSpace(value.charCodeAt(0)); if (printAsMultipleLines && !skipLeadingNewLine || forceLeadingNewLine) { result += "\n"; } result += escapedValue; if (printAsMultipleLines || forceTrailingNewline) { result += "\n"; } return '"""' + result + '"""'; } // src/graphql-js/language/printString.ts function printString(str) { return `"${str.replace(escapedRegExp, escapedReplacer)}"`; } var escapedRegExp = /[\x00-\x1f\x22\x5c\x7f-\x9f]/g; function escapedReplacer(str) { return escapeSequences[str.charCodeAt(0)]; } var escapeSequences = [ "\\u0000", "\\u0001", "\\u0002", "\\u0003", "\\u0004", "\\u0005", "\\u0006", "\\u0007", "\\b", "\\t", "\\n", "\\u000B", "\\f", "\\r", "\\u000E", "\\u000F", "\\u0010", "\\u0011", "\\u0012", "\\u0013", "\\u0014", "\\u0015", "\\u0016", "\\u0017", "\\u0018", "\\u0019", "\\u001A", "\\u001B", "\\u001C", "\\u001D", "\\u001E", "\\u001F", "", "", '\\"', "", "", "", "", "", "", "", "", "", "", "", "", "", // 2F "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // 3F "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // 4F "", "", "", "", "", "", "", "", "", "", "", "", "\\\\", "", "", "", // 5F "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", // 6F "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "\\u007F", "\\u0080", "\\u0081", "\\u0082", "\\u0083", "\\u0084", "\\u0085", "\\u0086", "\\u0087", "\\u0088", "\\u0089", "\\u008A", "\\u008B", "\\u008C", "\\u008D", "\\u008E", "\\u008F", "\\u0090", "\\u0091", "\\u0092", "\\u0093", "\\u0094", "\\u0095", "\\u0096", "\\u0097", "\\u0098", "\\u0099", "\\u009A", "\\u009B", "\\u009C", "\\u009D", "\\u009E", "\\u009F" ]; // src/graphql-js/language/printer.ts import { visit } from "graphql"; import { isNode } from "graphql/language/ast.js"; function print(ast) { return visit(ast, printDocASTReducer); } var MAX_LINE_LENGTH = 80; var printDocASTReducer = { Name: { leave: (node) => node.value }, Variable: { leave: (node) => "$" + node.name }, // Document Document: { leave: (node) => join(node.definitions, "\n\n") }, OperationDefinition: { leave(node) { const inlineVarDefs = wrap( "(", join(node.variableDefinitions, ", "), ")" ); const inlinePrefix = join( [ node.operation, join([node.name, inlineVarDefs]), join(node.directives, " ") ], " " ); if (inlinePrefix.length + 2 <= MAX_LINE_LENGTH) { return (inlinePrefix === "query" ? "" : inlinePrefix + " ") + node.selectionSet; } const blockVarDefs = node.variableDefinitions ? block(node.variableDefinitions, "(", ")", "") : ""; const blockPrefix = join( [ node.operation, join([node.name, blockVarDefs]), join(node.directives, " ") ], " " ); return blockPrefix + " " + node.selectionSet; } }, VariableDefinition: { leave: ({ variable, type, defaultValue, directives }) => variable + ": " + type + wrap(" = ", defaultValue) + wrap(" ", join(directives, " ")) }, SelectionSet: { leave: ({ selections }) => block(selections) }, Field: { leave({ alias, name, arguments: args, // nullabilityAssertion, directives, selectionSet }) { const prefix = join([wrap("", alias, ": "), name], ""); let argsLine = prefix + wrap("(", join(args, ", "), ")"); if (argsLine.length > MAX_LINE_LENGTH) { argsLine = prefix + wrap("(\n", indent(join(args, "\n")), "\n)"); } return join([ argsLine, // Note: Client Controlled Nullability is experimental and may be // changed or removed in the future. // nullabilityAssertion, wrap(" ", join(directives, " ")), wrap(" ", selectionSet) ]); } }, Argument: { leave: ({ name, value }) => name + ": " + value }, // Nullability Modifiers // ListNullabilityOperator: { // leave({ nullabilityAssertion }) { // return join(['[', nullabilityAssertion, ']']); // }, // }, // NonNullAssertion: { // leave({ nullabilityAssertion }) { // return join([nullabilityAssertion, '!']); // }, // }, // ErrorBoundary: { // leave({ nullabilityAssertion }) { // return join([nullabilityAssertion, '?']); // }, // }, // Fragments FragmentSpread: { leave: ({ name, directives }) => "..." + name + wrap(" ", join(directives, " ")) }, InlineFragment: { leave: ({ typeCondition, directives, selectionSet }) => join( [ "...", wrap("on ", typeCondition), join(directives, " "), selectionSet ], " " ) }, FragmentDefinition: { leave: ({ name, typeCondition, variableDefinitions, directives, selectionSet }) => ( // Note: fragment variable definitions are experimental and may be changed // or removed in the future. `fragment ${name}${wrap("(", join(variableDefinitions, ", "), ")")} on ${typeCondition} ${wrap("", join(directives, " "), " ")}` + selectionSet ) }, // Value IntValue: { leave: ({ value }) => value }, FloatValue: { leave: ({ value }) => value }, StringValue: { leave: ({ value, block: isBlockString }) => isBlockString === true ? printBlockString(value) : printString(value) }, BooleanValue: { leave: ({ value }) => value ? "true" : "false" }, NullValue: { leave: () => "null" }, EnumValue: { leave: ({ value }) => value }, ListValue: { leave: ({ values }) => { const valuesLine = "[" + join(values, ", ") + "]"; if (valuesLine.length > MAX_LINE_LENGTH) { return "[\n" + indent(join(values, "\n")) + "\n]"; } return valuesLine; } }, ObjectValue: { leave: ({ fields }, key, parent, path, ancestors) => { const depth = [...ancestors, parent].filter( (ancestor) => isNode(ancestor) && ancestor.kind === "ObjectValue" ).length; const parentKey = isNode(parent) && parent.kind === "ObjectField" ? parent.name.value : void 0; const fieldsLine = "{ " + join(fields, ", ") + " }"; const isWrapped = fieldsLine.indexOf("\n") >= 0; const TOP_LEVEL_DEPTH = 2; const INDENT = 2; const approximateInset = (depth + TOP_LEVEL_DEPTH) * INDENT + (parentKey ? parentKey.length : 0); return isWrapped || approximateInset + fieldsLine.length > MAX_LINE_LENGTH ? block(fields) : fieldsLine; } }, ObjectField: { leave: ({ name, value }) => name + ": " + value }, // Directive Directive: { leave: ({ name, arguments: args }) => "@" + name + wrap("(", join(args, ", "), ")") }, // Type NamedType: { leave: ({ name }) => name }, ListType: { leave: ({ type }) => "[" + type + "]" }, NonNullType: { leave: ({ type }) => type + "!" }, // Type System Definitions SchemaDefinition: { leave: ({ description, directives, operationTypes }) => wrap("", description, "\n") + join(["schema", join(directives, " "), block(operationTypes)], " ") }, OperationTypeDefinition: { leave: ({ operation, type }) => operation + ": " + type }, ScalarTypeDefinition: { leave: ({ description, name, directives }) => wrap("", description, "\n") + join(["scalar", name, join(directives, " ")], " ") }, ObjectTypeDefinition: { leave: ({ description, name, interfaces, directives, fields }) => wrap("", description, "\n") + join( [ "type", name, wrap("implements ", join(interfaces, " & ")), join(directives, " "), block(fields) ], " " ) }, FieldDefinition: { leave: ({ description, name, arguments: args, type, directives }) => wrap("", description, "\n") + name + (hasMultilineItems(args) ? wrap("(\n", indent(join(args, "\n")), "\n)") : wrap("(", join(args, ", "), ")")) + ": " + type + wrap(" ", join(directives, " ")) }, InputValueDefinition: { leave: ({ description, name, type, defaultValue, directives }) => wrap("", description, "\n") + join( [name + ": " + type, wrap("= ", defaultValue), join(directives, " ")], " " ) }, InterfaceTypeDefinition: { leave: ({ description, name, interfaces, directives, fields }) => wrap("", description, "\n") + join( [ "interface", name, wrap("implements ", join(interfaces, " & ")), join(directives, " "), block(fields) ], " " ) }, UnionTypeDefinition: { leave: ({ description, name, directives, types }) => wrap("", description, "\n") + join( ["union", name, join(directives, " "), wrap("= ", join(types, " | "))], " " ) }, EnumTypeDefinition: { leave: ({ description, name, directives, values }) => wrap("", description, "\n") + join(["enum", name, join(directives, " "), block(values)], " ") }, EnumValueDefinition: { leave: ({ description, name, directives }) => wrap("", description, "\n") + join([name, join(directives, " ")], " ") }, InputObjectTypeDefinition: { leave: ({ description, name, directives, fields }) => wrap("", description, "\n") + join(["input", name, join(directives, " "), block(fields)], " ") }, DirectiveDefinition: { leave: ({ description, name, arguments: args, repeatable, locations }) => wrap("", description, "\n") + "directive @" + name + (hasMultilineItems(args) ? wrap("(\n", indent(join(args, "\n")), "\n)") : wrap("(", join(args, ", "), ")")) + (repeatable ? " repeatable" : "") + " on " + join(locations, " | ") }, SchemaExtension: { leave: ({ directives, operationTypes }) => join( ["extend schema", join(directives, " "), block(operationTypes)], " " ) }, ScalarTypeExtension: { leave: ({ name, directives }) => join(["extend scalar", name, join(directives, " ")], " ") }, ObjectTypeExtension: { leave: ({ name, interfaces, directives, fields }) => join( [ "extend type", name, wrap("implements ", join(interfaces, " & ")), join(directives, " "), block(fields) ], " " ) }, InterfaceTypeExtension: { leave: ({ name, interfaces, directives, fields }) => join( [ "extend interface", name, wrap("implements ", join(interfaces, " & ")), join(directives, " "), block(fields) ], " " ) }, UnionTypeExtension: { leave: ({ name, directives, types }) => join( [ "extend union", name, join(directives, " "), wrap("= ", join(types, " | ")) ], " " ) }, EnumTypeExtension: { leave: ({ name, directives, values }) => join(["extend enum", name, join(directives, " "), block(values)], " ") }, InputObjectTypeExtension: { leave: ({ name, directives, fields }) => join(["extend input", name, join(directives, " "), block(fields)], " ") } }; function join(maybeArray, separator = "") { var _a; return (_a = maybeArray == null ? void 0 : maybeArray.filter((x) => x).join(separator)) != null ? _a : ""; } function block(array, start = "{", end = "}", separator = "") { return wrap(`${start} `, indent(join(array, `${separator} `)), ` ${end}`); } function wrap(start, maybeString, end = "") { return maybeString != null && maybeString !== "" ? start + maybeString + end : ""; } function indent(str) { return wrap(" ", str.replace(/\n/g, "\n ")); } function hasMultilineItems(maybeArray) { var _a; return (_a = maybeArray == null ? void 0 : maybeArray.some((str) => str.includes("\n"))) != null ? _a : false; } // src/lib/trailing-newline.ts function ensureTrailingNewline(value) { return value.endsWith("\n") ? value : `${value} `; } function trimTrailingNewlines(value) { const match = value.match(/\n+$/); if (match == null) return value; const trimmed = value.substring(0, match.index); return trimmed; } // src/lib/trim-trailing-whitespace.ts function trimTrailingWhitespace(value) { return value.split(/\r?\n/g).map((line) => line.trimEnd()).join("\n"); } // src/print.ts function print2(ast) { if (!isExtendedDocumentNode(ast)) { return ensureTrailingNewline( ast.kind === Kind3.DOCUMENT ? resilientPrint(ast) : print(ast) ); } const output = []; for (const section of ast.sections) { if (section.kind === "Ignored" || section.kind === "Invalid" || section.kind === "InvalidOperationDefinition" || section.kind === "InvalidFragmentDefinition") { output.push(section.value); continue; } output.push(resilientPrint(section)); } return ensureTrailingNewline(output.join("\n")); } var TEMPORARY_FIELD = { kind: Kind3.FIELD, name: { kind: Kind3.NAME, value: "TEMPORARY_FIELD" } }; function resilientPrint(node) { const document = node.kind === Kind3.DOCUMENT ? node : { kind: Kind3.DOCUMENT, definitions: [node] }; const temporaryDocument = visit2(document, { OperationDefinition(node2) { if (node2.selectionSet.selections.length > 0) return; return { ...node2, selectionSet: { kind: Kind3.SELECTION_SET, selections: [TEMPORARY_FIELD] } }; }, Field(node2) { var _a; if (((_a = node2.selectionSet) == null ? void 0 : _a.selections.length) !== 0) return; return { ...node2, selectionSet: { kind: Kind3.SELECTION_SET, selections: [TEMPORARY_FIELD] } }; }, InlineFragment(node2) { if (node2.selectionSet.selections.length > 0) return; return { ...node2, selectionSet: { kind: Kind3.SELECTION_SET, selections: [TEMPORARY_FIELD] } }; } }); const valid = print(temporaryDocument); const value = trimTrailingNewlines( trimTrailingWhitespace(valid.replace(/TEMPORARY_FIELD/g, "")) ); return value; } // src/visit.ts import { BREAK, visit as graphqlVisit } from "graphql"; function visit3(root, visitor) { var _a, _b; if (!isExtendedNode(root)) { return graphqlVisit(root, visitor); } let node = root; const enterLeave = getEnterLeaveForKind(visitor, root.kind); const entered = (_a = enterLeave.enter) == null ? void 0 : _a.call( enterLeave, isExtendedDocumentNode(node) ? cloneDocument(node) : node, void 0, void 0, [], [] ); if (entered === BREAK) { return node; } if (entered === null) { return null; } if (entered !== void 0) { node = entered; } if (isExtendedDocumentNode(node)) { const visited = []; let broke = false; for (const section of node.sections) { const result = visit3(section, visitor); if (result === BREAK) { broke = true; break; } visited.push(result); } const sections = visited.flat().filter((section) => section !== null); node = { kind: "ExtendedDocument", sections }; if (broke) { return node; } } const left = (_b = enterLeave.leave) == null ? void 0 : _b.call( enterLeave, isExtendedDocumentNode(node) ? cloneDocument(node) : node, void 0, void 0, [], [] ); if (left === BREAK) { return node; } if (left === null) { return null; } if (left !== void 0) { node = left; } return node; } function cloneDocument(document) { return { kind: "ExtendedDocument", sections: [...document.sections] }; } function getEnterLeaveForKind(visitor, kind) { const kindVisitor = visitor[kind]; if (typeof kindVisitor === "object") { return kindVisitor; } else if (typeof kindVisitor === "function") { return { enter: kindVisitor, leave: void 0 }; } return { enter: visitor.enter, leave: visitor.leave }; } export { analyze, interpolate, isExtendedDocumentNode, print2 as print, visit3 as visit }; // @license MIT