extendSchema.js.flow 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770
  1. // @flow strict
  2. import objectValues from '../polyfills/objectValues';
  3. import keyMap from '../jsutils/keyMap';
  4. import inspect from '../jsutils/inspect';
  5. import mapValue from '../jsutils/mapValue';
  6. import invariant from '../jsutils/invariant';
  7. import devAssert from '../jsutils/devAssert';
  8. import type { DirectiveLocationEnum } from '../language/directiveLocation';
  9. import type {
  10. Location,
  11. DocumentNode,
  12. StringValueNode,
  13. TypeNode,
  14. NamedTypeNode,
  15. SchemaDefinitionNode,
  16. SchemaExtensionNode,
  17. TypeDefinitionNode,
  18. InterfaceTypeDefinitionNode,
  19. InterfaceTypeExtensionNode,
  20. ObjectTypeDefinitionNode,
  21. ObjectTypeExtensionNode,
  22. UnionTypeDefinitionNode,
  23. UnionTypeExtensionNode,
  24. FieldDefinitionNode,
  25. InputObjectTypeDefinitionNode,
  26. InputObjectTypeExtensionNode,
  27. InputValueDefinitionNode,
  28. EnumTypeDefinitionNode,
  29. EnumTypeExtensionNode,
  30. EnumValueDefinitionNode,
  31. DirectiveDefinitionNode,
  32. ScalarTypeDefinitionNode,
  33. ScalarTypeExtensionNode,
  34. } from '../language/ast';
  35. import { Kind } from '../language/kinds';
  36. import { TokenKind } from '../language/tokenKind';
  37. import { dedentBlockStringValue } from '../language/blockString';
  38. import {
  39. isTypeDefinitionNode,
  40. isTypeExtensionNode,
  41. } from '../language/predicates';
  42. import { assertValidSDLExtension } from '../validation/validate';
  43. import { getDirectiveValues } from '../execution/values';
  44. import type {
  45. GraphQLSchemaValidationOptions,
  46. GraphQLSchemaNormalizedConfig,
  47. } from '../type/schema';
  48. import type {
  49. GraphQLType,
  50. GraphQLNamedType,
  51. GraphQLFieldConfigMap,
  52. GraphQLEnumValueConfigMap,
  53. GraphQLInputFieldConfigMap,
  54. GraphQLFieldConfigArgumentMap,
  55. } from '../type/definition';
  56. import { assertSchema, GraphQLSchema } from '../type/schema';
  57. import { specifiedScalarTypes, isSpecifiedScalarType } from '../type/scalars';
  58. import { introspectionTypes, isIntrospectionType } from '../type/introspection';
  59. import {
  60. GraphQLDirective,
  61. GraphQLDeprecatedDirective,
  62. GraphQLSpecifiedByDirective,
  63. } from '../type/directives';
  64. import {
  65. isScalarType,
  66. isObjectType,
  67. isInterfaceType,
  68. isUnionType,
  69. isListType,
  70. isNonNullType,
  71. isEnumType,
  72. isInputObjectType,
  73. GraphQLList,
  74. GraphQLNonNull,
  75. GraphQLScalarType,
  76. GraphQLObjectType,
  77. GraphQLInterfaceType,
  78. GraphQLUnionType,
  79. GraphQLEnumType,
  80. GraphQLInputObjectType,
  81. } from '../type/definition';
  82. import { valueFromAST } from './valueFromAST';
  83. type Options = {|
  84. ...GraphQLSchemaValidationOptions,
  85. /**
  86. * Descriptions are defined as preceding string literals, however an older
  87. * experimental version of the SDL supported preceding comments as
  88. * descriptions. Set to true to enable this deprecated behavior.
  89. * This option is provided to ease adoption and will be removed in v16.
  90. *
  91. * Default: false
  92. */
  93. commentDescriptions?: boolean,
  94. /**
  95. * Set to true to assume the SDL is valid.
  96. *
  97. * Default: false
  98. */
  99. assumeValidSDL?: boolean,
  100. |};
  101. /**
  102. * Produces a new schema given an existing schema and a document which may
  103. * contain GraphQL type extensions and definitions. The original schema will
  104. * remain unaltered.
  105. *
  106. * Because a schema represents a graph of references, a schema cannot be
  107. * extended without effectively making an entire copy. We do not know until it's
  108. * too late if subgraphs remain unchanged.
  109. *
  110. * This algorithm copies the provided schema, applying extensions while
  111. * producing the copy. The original schema remains unaltered.
  112. *
  113. * Accepts options as a third argument:
  114. *
  115. * - commentDescriptions:
  116. * Provide true to use preceding comments as the description.
  117. *
  118. */
  119. export function extendSchema(
  120. schema: GraphQLSchema,
  121. documentAST: DocumentNode,
  122. options?: Options,
  123. ): GraphQLSchema {
  124. assertSchema(schema);
  125. devAssert(
  126. documentAST != null && documentAST.kind === Kind.DOCUMENT,
  127. 'Must provide valid Document AST.',
  128. );
  129. if (options?.assumeValid !== true && options?.assumeValidSDL !== true) {
  130. assertValidSDLExtension(documentAST, schema);
  131. }
  132. const schemaConfig = schema.toConfig();
  133. const extendedConfig = extendSchemaImpl(schemaConfig, documentAST, options);
  134. return schemaConfig === extendedConfig
  135. ? schema
  136. : new GraphQLSchema(extendedConfig);
  137. }
  138. /**
  139. * @internal
  140. */
  141. export function extendSchemaImpl(
  142. schemaConfig: GraphQLSchemaNormalizedConfig,
  143. documentAST: DocumentNode,
  144. options?: Options,
  145. ): GraphQLSchemaNormalizedConfig {
  146. // Collect the type definitions and extensions found in the document.
  147. const typeDefs: Array<TypeDefinitionNode> = [];
  148. const typeExtensionsMap = Object.create(null);
  149. // New directives and types are separate because a directives and types can
  150. // have the same name. For example, a type named "skip".
  151. const directiveDefs: Array<DirectiveDefinitionNode> = [];
  152. let schemaDef: ?SchemaDefinitionNode;
  153. // Schema extensions are collected which may add additional operation types.
  154. const schemaExtensions: Array<SchemaExtensionNode> = [];
  155. for (const def of documentAST.definitions) {
  156. if (def.kind === Kind.SCHEMA_DEFINITION) {
  157. schemaDef = def;
  158. } else if (def.kind === Kind.SCHEMA_EXTENSION) {
  159. schemaExtensions.push(def);
  160. } else if (isTypeDefinitionNode(def)) {
  161. typeDefs.push(def);
  162. } else if (isTypeExtensionNode(def)) {
  163. const extendedTypeName = def.name.value;
  164. const existingTypeExtensions = typeExtensionsMap[extendedTypeName];
  165. typeExtensionsMap[extendedTypeName] = existingTypeExtensions
  166. ? existingTypeExtensions.concat([def])
  167. : [def];
  168. } else if (def.kind === Kind.DIRECTIVE_DEFINITION) {
  169. directiveDefs.push(def);
  170. }
  171. }
  172. // If this document contains no new types, extensions, or directives then
  173. // return the same unmodified GraphQLSchema instance.
  174. if (
  175. Object.keys(typeExtensionsMap).length === 0 &&
  176. typeDefs.length === 0 &&
  177. directiveDefs.length === 0 &&
  178. schemaExtensions.length === 0 &&
  179. schemaDef == null
  180. ) {
  181. return schemaConfig;
  182. }
  183. const typeMap = Object.create(null);
  184. for (const existingType of schemaConfig.types) {
  185. typeMap[existingType.name] = extendNamedType(existingType);
  186. }
  187. for (const typeNode of typeDefs) {
  188. const name = typeNode.name.value;
  189. typeMap[name] = stdTypeMap[name] ?? buildType(typeNode);
  190. }
  191. const operationTypes = {
  192. // Get the extended root operation types.
  193. query: schemaConfig.query && replaceNamedType(schemaConfig.query),
  194. mutation: schemaConfig.mutation && replaceNamedType(schemaConfig.mutation),
  195. subscription:
  196. schemaConfig.subscription && replaceNamedType(schemaConfig.subscription),
  197. // Then, incorporate schema definition and all schema extensions.
  198. ...(schemaDef && getOperationTypes([schemaDef])),
  199. ...getOperationTypes(schemaExtensions),
  200. };
  201. // Then produce and return a Schema config with these types.
  202. return {
  203. description: schemaDef?.description?.value,
  204. ...operationTypes,
  205. types: objectValues(typeMap),
  206. directives: [
  207. ...schemaConfig.directives.map(replaceDirective),
  208. ...directiveDefs.map(buildDirective),
  209. ],
  210. extensions: undefined,
  211. astNode: schemaDef ?? schemaConfig.astNode,
  212. extensionASTNodes: schemaConfig.extensionASTNodes.concat(schemaExtensions),
  213. assumeValid: options?.assumeValid ?? false,
  214. };
  215. // Below are functions used for producing this schema that have closed over
  216. // this scope and have access to the schema, cache, and newly defined types.
  217. function replaceType(type) {
  218. if (isListType(type)) {
  219. return new GraphQLList(replaceType(type.ofType));
  220. } else if (isNonNullType(type)) {
  221. return new GraphQLNonNull(replaceType(type.ofType));
  222. }
  223. return replaceNamedType(type);
  224. }
  225. function replaceNamedType<T: GraphQLNamedType>(type: T): T {
  226. // Note: While this could make early assertions to get the correctly
  227. // typed values, that would throw immediately while type system
  228. // validation with validateSchema() will produce more actionable results.
  229. return ((typeMap[type.name]: any): T);
  230. }
  231. function replaceDirective(directive: GraphQLDirective): GraphQLDirective {
  232. const config = directive.toConfig();
  233. return new GraphQLDirective({
  234. ...config,
  235. args: mapValue(config.args, extendArg),
  236. });
  237. }
  238. function extendNamedType(type: GraphQLNamedType): GraphQLNamedType {
  239. if (isIntrospectionType(type) || isSpecifiedScalarType(type)) {
  240. // Builtin types are not extended.
  241. return type;
  242. }
  243. if (isScalarType(type)) {
  244. return extendScalarType(type);
  245. }
  246. if (isObjectType(type)) {
  247. return extendObjectType(type);
  248. }
  249. if (isInterfaceType(type)) {
  250. return extendInterfaceType(type);
  251. }
  252. if (isUnionType(type)) {
  253. return extendUnionType(type);
  254. }
  255. if (isEnumType(type)) {
  256. return extendEnumType(type);
  257. }
  258. // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
  259. if (isInputObjectType(type)) {
  260. return extendInputObjectType(type);
  261. }
  262. // istanbul ignore next (Not reachable. All possible types have been considered)
  263. invariant(false, 'Unexpected type: ' + inspect((type: empty)));
  264. }
  265. function extendInputObjectType(
  266. type: GraphQLInputObjectType,
  267. ): GraphQLInputObjectType {
  268. const config = type.toConfig();
  269. const extensions = typeExtensionsMap[config.name] ?? [];
  270. return new GraphQLInputObjectType({
  271. ...config,
  272. fields: () => ({
  273. ...mapValue(config.fields, (field) => ({
  274. ...field,
  275. type: replaceType(field.type),
  276. })),
  277. ...buildInputFieldMap(extensions),
  278. }),
  279. extensionASTNodes: config.extensionASTNodes.concat(extensions),
  280. });
  281. }
  282. function extendEnumType(type: GraphQLEnumType): GraphQLEnumType {
  283. const config = type.toConfig();
  284. const extensions = typeExtensionsMap[type.name] ?? [];
  285. return new GraphQLEnumType({
  286. ...config,
  287. values: {
  288. ...config.values,
  289. ...buildEnumValueMap(extensions),
  290. },
  291. extensionASTNodes: config.extensionASTNodes.concat(extensions),
  292. });
  293. }
  294. function extendScalarType(type: GraphQLScalarType): GraphQLScalarType {
  295. const config = type.toConfig();
  296. const extensions = typeExtensionsMap[config.name] ?? [];
  297. let specifiedByUrl = config.specifiedByUrl;
  298. for (const extensionNode of extensions) {
  299. specifiedByUrl = getSpecifiedByUrl(extensionNode) ?? specifiedByUrl;
  300. }
  301. return new GraphQLScalarType({
  302. ...config,
  303. specifiedByUrl,
  304. extensionASTNodes: config.extensionASTNodes.concat(extensions),
  305. });
  306. }
  307. function extendObjectType(type: GraphQLObjectType): GraphQLObjectType {
  308. const config = type.toConfig();
  309. const extensions = typeExtensionsMap[config.name] ?? [];
  310. return new GraphQLObjectType({
  311. ...config,
  312. interfaces: () => [
  313. ...type.getInterfaces().map(replaceNamedType),
  314. ...buildInterfaces(extensions),
  315. ],
  316. fields: () => ({
  317. ...mapValue(config.fields, extendField),
  318. ...buildFieldMap(extensions),
  319. }),
  320. extensionASTNodes: config.extensionASTNodes.concat(extensions),
  321. });
  322. }
  323. function extendInterfaceType(
  324. type: GraphQLInterfaceType,
  325. ): GraphQLInterfaceType {
  326. const config = type.toConfig();
  327. const extensions = typeExtensionsMap[config.name] ?? [];
  328. return new GraphQLInterfaceType({
  329. ...config,
  330. interfaces: () => [
  331. ...type.getInterfaces().map(replaceNamedType),
  332. ...buildInterfaces(extensions),
  333. ],
  334. fields: () => ({
  335. ...mapValue(config.fields, extendField),
  336. ...buildFieldMap(extensions),
  337. }),
  338. extensionASTNodes: config.extensionASTNodes.concat(extensions),
  339. });
  340. }
  341. function extendUnionType(type: GraphQLUnionType): GraphQLUnionType {
  342. const config = type.toConfig();
  343. const extensions = typeExtensionsMap[config.name] ?? [];
  344. return new GraphQLUnionType({
  345. ...config,
  346. types: () => [
  347. ...type.getTypes().map(replaceNamedType),
  348. ...buildUnionTypes(extensions),
  349. ],
  350. extensionASTNodes: config.extensionASTNodes.concat(extensions),
  351. });
  352. }
  353. function extendField(field) {
  354. return {
  355. ...field,
  356. type: replaceType(field.type),
  357. args: mapValue(field.args, extendArg),
  358. };
  359. }
  360. function extendArg(arg) {
  361. return {
  362. ...arg,
  363. type: replaceType(arg.type),
  364. };
  365. }
  366. function getOperationTypes(
  367. nodes: $ReadOnlyArray<SchemaDefinitionNode | SchemaExtensionNode>,
  368. ): {|
  369. query: ?GraphQLObjectType,
  370. mutation: ?GraphQLObjectType,
  371. subscription: ?GraphQLObjectType,
  372. |} {
  373. const opTypes = {};
  374. for (const node of nodes) {
  375. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  376. const operationTypesNodes = node.operationTypes ?? [];
  377. for (const operationType of operationTypesNodes) {
  378. opTypes[operationType.operation] = getNamedType(operationType.type);
  379. }
  380. }
  381. // Note: While this could make early assertions to get the correctly
  382. // typed values below, that would throw immediately while type system
  383. // validation with validateSchema() will produce more actionable results.
  384. return (opTypes: any);
  385. }
  386. function getNamedType(node: NamedTypeNode): GraphQLNamedType {
  387. const name = node.name.value;
  388. const type = stdTypeMap[name] ?? typeMap[name];
  389. if (type === undefined) {
  390. throw new Error(`Unknown type: "${name}".`);
  391. }
  392. return type;
  393. }
  394. function getWrappedType(node: TypeNode): GraphQLType {
  395. if (node.kind === Kind.LIST_TYPE) {
  396. return new GraphQLList(getWrappedType(node.type));
  397. }
  398. if (node.kind === Kind.NON_NULL_TYPE) {
  399. return new GraphQLNonNull(getWrappedType(node.type));
  400. }
  401. return getNamedType(node);
  402. }
  403. function buildDirective(node: DirectiveDefinitionNode): GraphQLDirective {
  404. const locations = node.locations.map(
  405. ({ value }) => ((value: any): DirectiveLocationEnum),
  406. );
  407. return new GraphQLDirective({
  408. name: node.name.value,
  409. description: getDescription(node, options),
  410. locations,
  411. isRepeatable: node.repeatable,
  412. args: buildArgumentMap(node.arguments),
  413. astNode: node,
  414. });
  415. }
  416. function buildFieldMap(
  417. nodes: $ReadOnlyArray<
  418. | InterfaceTypeDefinitionNode
  419. | InterfaceTypeExtensionNode
  420. | ObjectTypeDefinitionNode
  421. | ObjectTypeExtensionNode,
  422. >,
  423. ): GraphQLFieldConfigMap<mixed, mixed> {
  424. const fieldConfigMap = Object.create(null);
  425. for (const node of nodes) {
  426. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  427. const nodeFields = node.fields ?? [];
  428. for (const field of nodeFields) {
  429. fieldConfigMap[field.name.value] = {
  430. // Note: While this could make assertions to get the correctly typed
  431. // value, that would throw immediately while type system validation
  432. // with validateSchema() will produce more actionable results.
  433. type: (getWrappedType(field.type): any),
  434. description: getDescription(field, options),
  435. args: buildArgumentMap(field.arguments),
  436. deprecationReason: getDeprecationReason(field),
  437. astNode: field,
  438. };
  439. }
  440. }
  441. return fieldConfigMap;
  442. }
  443. function buildArgumentMap(
  444. args: ?$ReadOnlyArray<InputValueDefinitionNode>,
  445. ): GraphQLFieldConfigArgumentMap {
  446. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  447. const argsNodes = args ?? [];
  448. const argConfigMap = Object.create(null);
  449. for (const arg of argsNodes) {
  450. // Note: While this could make assertions to get the correctly typed
  451. // value, that would throw immediately while type system validation
  452. // with validateSchema() will produce more actionable results.
  453. const type: any = getWrappedType(arg.type);
  454. argConfigMap[arg.name.value] = {
  455. type,
  456. description: getDescription(arg, options),
  457. defaultValue: valueFromAST(arg.defaultValue, type),
  458. astNode: arg,
  459. };
  460. }
  461. return argConfigMap;
  462. }
  463. function buildInputFieldMap(
  464. nodes: $ReadOnlyArray<
  465. InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode,
  466. >,
  467. ): GraphQLInputFieldConfigMap {
  468. const inputFieldMap = Object.create(null);
  469. for (const node of nodes) {
  470. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  471. const fieldsNodes = node.fields ?? [];
  472. for (const field of fieldsNodes) {
  473. // Note: While this could make assertions to get the correctly typed
  474. // value, that would throw immediately while type system validation
  475. // with validateSchema() will produce more actionable results.
  476. const type: any = getWrappedType(field.type);
  477. inputFieldMap[field.name.value] = {
  478. type,
  479. description: getDescription(field, options),
  480. defaultValue: valueFromAST(field.defaultValue, type),
  481. astNode: field,
  482. };
  483. }
  484. }
  485. return inputFieldMap;
  486. }
  487. function buildEnumValueMap(
  488. nodes: $ReadOnlyArray<EnumTypeDefinitionNode | EnumTypeExtensionNode>,
  489. ): GraphQLEnumValueConfigMap {
  490. const enumValueMap = Object.create(null);
  491. for (const node of nodes) {
  492. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  493. const valuesNodes = node.values ?? [];
  494. for (const value of valuesNodes) {
  495. enumValueMap[value.name.value] = {
  496. description: getDescription(value, options),
  497. deprecationReason: getDeprecationReason(value),
  498. astNode: value,
  499. };
  500. }
  501. }
  502. return enumValueMap;
  503. }
  504. function buildInterfaces(
  505. nodes: $ReadOnlyArray<
  506. | InterfaceTypeDefinitionNode
  507. | InterfaceTypeExtensionNode
  508. | ObjectTypeDefinitionNode
  509. | ObjectTypeExtensionNode,
  510. >,
  511. ): Array<GraphQLInterfaceType> {
  512. const interfaces = [];
  513. for (const node of nodes) {
  514. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  515. const interfacesNodes = node.interfaces ?? [];
  516. for (const type of interfacesNodes) {
  517. // Note: While this could make assertions to get the correctly typed
  518. // values below, that would throw immediately while type system
  519. // validation with validateSchema() will produce more actionable
  520. // results.
  521. interfaces.push((getNamedType(type): any));
  522. }
  523. }
  524. return interfaces;
  525. }
  526. function buildUnionTypes(
  527. nodes: $ReadOnlyArray<UnionTypeDefinitionNode | UnionTypeExtensionNode>,
  528. ): Array<GraphQLObjectType> {
  529. const types = [];
  530. for (const node of nodes) {
  531. // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
  532. const typeNodes = node.types ?? [];
  533. for (const type of typeNodes) {
  534. // Note: While this could make assertions to get the correctly typed
  535. // values below, that would throw immediately while type system
  536. // validation with validateSchema() will produce more actionable
  537. // results.
  538. types.push((getNamedType(type): any));
  539. }
  540. }
  541. return types;
  542. }
  543. function buildType(astNode: TypeDefinitionNode): GraphQLNamedType {
  544. const name = astNode.name.value;
  545. const description = getDescription(astNode, options);
  546. const extensionNodes = typeExtensionsMap[name] ?? [];
  547. switch (astNode.kind) {
  548. case Kind.OBJECT_TYPE_DEFINITION: {
  549. const extensionASTNodes = (extensionNodes: any);
  550. const allNodes = [astNode, ...extensionASTNodes];
  551. return new GraphQLObjectType({
  552. name,
  553. description,
  554. interfaces: () => buildInterfaces(allNodes),
  555. fields: () => buildFieldMap(allNodes),
  556. astNode,
  557. extensionASTNodes,
  558. });
  559. }
  560. case Kind.INTERFACE_TYPE_DEFINITION: {
  561. const extensionASTNodes = (extensionNodes: any);
  562. const allNodes = [astNode, ...extensionASTNodes];
  563. return new GraphQLInterfaceType({
  564. name,
  565. description,
  566. interfaces: () => buildInterfaces(allNodes),
  567. fields: () => buildFieldMap(allNodes),
  568. astNode,
  569. extensionASTNodes,
  570. });
  571. }
  572. case Kind.ENUM_TYPE_DEFINITION: {
  573. const extensionASTNodes = (extensionNodes: any);
  574. const allNodes = [astNode, ...extensionASTNodes];
  575. return new GraphQLEnumType({
  576. name,
  577. description,
  578. values: buildEnumValueMap(allNodes),
  579. astNode,
  580. extensionASTNodes,
  581. });
  582. }
  583. case Kind.UNION_TYPE_DEFINITION: {
  584. const extensionASTNodes = (extensionNodes: any);
  585. const allNodes = [astNode, ...extensionASTNodes];
  586. return new GraphQLUnionType({
  587. name,
  588. description,
  589. types: () => buildUnionTypes(allNodes),
  590. astNode,
  591. extensionASTNodes,
  592. });
  593. }
  594. case Kind.SCALAR_TYPE_DEFINITION: {
  595. const extensionASTNodes = (extensionNodes: any);
  596. return new GraphQLScalarType({
  597. name,
  598. description,
  599. specifiedByUrl: getSpecifiedByUrl(astNode),
  600. astNode,
  601. extensionASTNodes,
  602. });
  603. }
  604. case Kind.INPUT_OBJECT_TYPE_DEFINITION: {
  605. const extensionASTNodes = (extensionNodes: any);
  606. const allNodes = [astNode, ...extensionASTNodes];
  607. return new GraphQLInputObjectType({
  608. name,
  609. description,
  610. fields: () => buildInputFieldMap(allNodes),
  611. astNode,
  612. extensionASTNodes,
  613. });
  614. }
  615. }
  616. // istanbul ignore next (Not reachable. All possible type definition nodes have been considered)
  617. invariant(
  618. false,
  619. 'Unexpected type definition node: ' + inspect((astNode: empty)),
  620. );
  621. }
  622. }
  623. const stdTypeMap = keyMap(
  624. specifiedScalarTypes.concat(introspectionTypes),
  625. (type) => type.name,
  626. );
  627. /**
  628. * Given a field or enum value node, returns the string value for the
  629. * deprecation reason.
  630. */
  631. function getDeprecationReason(
  632. node: EnumValueDefinitionNode | FieldDefinitionNode,
  633. ): ?string {
  634. const deprecated = getDirectiveValues(GraphQLDeprecatedDirective, node);
  635. return (deprecated?.reason: any);
  636. }
  637. /**
  638. * Given a scalar node, returns the string value for the specifiedByUrl.
  639. */
  640. function getSpecifiedByUrl(
  641. node: ScalarTypeDefinitionNode | ScalarTypeExtensionNode,
  642. ): ?string {
  643. const specifiedBy = getDirectiveValues(GraphQLSpecifiedByDirective, node);
  644. return (specifiedBy?.url: any);
  645. }
  646. /**
  647. * Given an ast node, returns its string description.
  648. * @deprecated: provided to ease adoption and will be removed in v16.
  649. *
  650. * Accepts options as a second argument:
  651. *
  652. * - commentDescriptions:
  653. * Provide true to use preceding comments as the description.
  654. *
  655. */
  656. export function getDescription(
  657. node: { +description?: StringValueNode, +loc?: Location, ... },
  658. options: ?{ commentDescriptions?: boolean, ... },
  659. ): void | string {
  660. if (node.description) {
  661. return node.description.value;
  662. }
  663. if (options?.commentDescriptions === true) {
  664. const rawValue = getLeadingCommentBlock(node);
  665. if (rawValue !== undefined) {
  666. return dedentBlockStringValue('\n' + rawValue);
  667. }
  668. }
  669. }
  670. function getLeadingCommentBlock(node): void | string {
  671. const loc = node.loc;
  672. if (!loc) {
  673. return;
  674. }
  675. const comments = [];
  676. let token = loc.startToken.prev;
  677. while (
  678. token != null &&
  679. token.kind === TokenKind.COMMENT &&
  680. token.next &&
  681. token.prev &&
  682. token.line + 1 === token.next.line &&
  683. token.line !== token.prev.line
  684. ) {
  685. const value = String(token.value);
  686. comments.push(value);
  687. token = token.prev;
  688. }
  689. return comments.length > 0 ? comments.reverse().join('\n') : undefined;
  690. }