123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782 |
- // @flow strict
- import objectValues from '../polyfills/objectValues';
- import keyMap from '../jsutils/keyMap';
- import inspect from '../jsutils/inspect';
- import mapValue from '../jsutils/mapValue';
- import invariant from '../jsutils/invariant';
- import devAssert from '../jsutils/devAssert';
- import type { DirectiveLocationEnum } from '../language/directiveLocation';
- import type {
- Location,
- DocumentNode,
- StringValueNode,
- TypeNode,
- NamedTypeNode,
- SchemaDefinitionNode,
- SchemaExtensionNode,
- TypeDefinitionNode,
- InterfaceTypeDefinitionNode,
- InterfaceTypeExtensionNode,
- ObjectTypeDefinitionNode,
- ObjectTypeExtensionNode,
- UnionTypeDefinitionNode,
- UnionTypeExtensionNode,
- FieldDefinitionNode,
- InputObjectTypeDefinitionNode,
- InputObjectTypeExtensionNode,
- InputValueDefinitionNode,
- EnumTypeDefinitionNode,
- EnumTypeExtensionNode,
- EnumValueDefinitionNode,
- DirectiveDefinitionNode,
- ScalarTypeDefinitionNode,
- ScalarTypeExtensionNode,
- } from '../language/ast';
- import { Kind } from '../language/kinds';
- import { TokenKind } from '../language/tokenKind';
- import { dedentBlockStringValue } from '../language/blockString';
- import {
- isTypeDefinitionNode,
- isTypeExtensionNode,
- } from '../language/predicates';
- import { assertValidSDLExtension } from '../validation/validate';
- import { getDirectiveValues } from '../execution/values';
- import type {
- GraphQLSchemaValidationOptions,
- GraphQLSchemaNormalizedConfig,
- } from '../type/schema';
- import type {
- GraphQLType,
- GraphQLNamedType,
- GraphQLFieldConfig,
- GraphQLFieldConfigMap,
- GraphQLArgumentConfig,
- GraphQLFieldConfigArgumentMap,
- GraphQLEnumValueConfigMap,
- GraphQLInputFieldConfigMap,
- } from '../type/definition';
- import { assertSchema, GraphQLSchema } from '../type/schema';
- import { specifiedScalarTypes, isSpecifiedScalarType } from '../type/scalars';
- import { introspectionTypes, isIntrospectionType } from '../type/introspection';
- import {
- GraphQLDirective,
- GraphQLDeprecatedDirective,
- GraphQLSpecifiedByDirective,
- } from '../type/directives';
- import {
- isScalarType,
- isObjectType,
- isInterfaceType,
- isUnionType,
- isListType,
- isNonNullType,
- isEnumType,
- isInputObjectType,
- GraphQLList,
- GraphQLNonNull,
- GraphQLScalarType,
- GraphQLObjectType,
- GraphQLInterfaceType,
- GraphQLUnionType,
- GraphQLEnumType,
- GraphQLInputObjectType,
- } from '../type/definition';
- import { valueFromAST } from './valueFromAST';
- type Options = {|
- ...GraphQLSchemaValidationOptions,
- /**
- * Descriptions are defined as preceding string literals, however an older
- * experimental version of the SDL supported preceding comments as
- * descriptions. Set to true to enable this deprecated behavior.
- * This option is provided to ease adoption and will be removed in v16.
- *
- * Default: false
- */
- commentDescriptions?: boolean,
- /**
- * Set to true to assume the SDL is valid.
- *
- * Default: false
- */
- assumeValidSDL?: boolean,
- |};
- /**
- * Produces a new schema given an existing schema and a document which may
- * contain GraphQL type extensions and definitions. The original schema will
- * remain unaltered.
- *
- * Because a schema represents a graph of references, a schema cannot be
- * extended without effectively making an entire copy. We do not know until it's
- * too late if subgraphs remain unchanged.
- *
- * This algorithm copies the provided schema, applying extensions while
- * producing the copy. The original schema remains unaltered.
- *
- * Accepts options as a third argument:
- *
- * - commentDescriptions:
- * Provide true to use preceding comments as the description.
- *
- */
- export function extendSchema(
- schema: GraphQLSchema,
- documentAST: DocumentNode,
- options?: Options,
- ): GraphQLSchema {
- assertSchema(schema);
- devAssert(
- documentAST != null && documentAST.kind === Kind.DOCUMENT,
- 'Must provide valid Document AST.',
- );
- if (options?.assumeValid !== true && options?.assumeValidSDL !== true) {
- assertValidSDLExtension(documentAST, schema);
- }
- const schemaConfig = schema.toConfig();
- const extendedConfig = extendSchemaImpl(schemaConfig, documentAST, options);
- return schemaConfig === extendedConfig
- ? schema
- : new GraphQLSchema(extendedConfig);
- }
- /**
- * @internal
- */
- export function extendSchemaImpl(
- schemaConfig: GraphQLSchemaNormalizedConfig,
- documentAST: DocumentNode,
- options?: Options,
- ): GraphQLSchemaNormalizedConfig {
- // Collect the type definitions and extensions found in the document.
- const typeDefs: Array<TypeDefinitionNode> = [];
- const typeExtensionsMap = Object.create(null);
- // New directives and types are separate because a directives and types can
- // have the same name. For example, a type named "skip".
- const directiveDefs: Array<DirectiveDefinitionNode> = [];
- let schemaDef: ?SchemaDefinitionNode;
- // Schema extensions are collected which may add additional operation types.
- const schemaExtensions: Array<SchemaExtensionNode> = [];
- for (const def of documentAST.definitions) {
- if (def.kind === Kind.SCHEMA_DEFINITION) {
- schemaDef = def;
- } else if (def.kind === Kind.SCHEMA_EXTENSION) {
- schemaExtensions.push(def);
- } else if (isTypeDefinitionNode(def)) {
- typeDefs.push(def);
- } else if (isTypeExtensionNode(def)) {
- const extendedTypeName = def.name.value;
- const existingTypeExtensions = typeExtensionsMap[extendedTypeName];
- typeExtensionsMap[extendedTypeName] = existingTypeExtensions
- ? existingTypeExtensions.concat([def])
- : [def];
- } else if (def.kind === Kind.DIRECTIVE_DEFINITION) {
- directiveDefs.push(def);
- }
- }
- // If this document contains no new types, extensions, or directives then
- // return the same unmodified GraphQLSchema instance.
- if (
- Object.keys(typeExtensionsMap).length === 0 &&
- typeDefs.length === 0 &&
- directiveDefs.length === 0 &&
- schemaExtensions.length === 0 &&
- schemaDef == null
- ) {
- return schemaConfig;
- }
- const typeMap = Object.create(null);
- for (const existingType of schemaConfig.types) {
- typeMap[existingType.name] = extendNamedType(existingType);
- }
- for (const typeNode of typeDefs) {
- const name = typeNode.name.value;
- typeMap[name] = stdTypeMap[name] ?? buildType(typeNode);
- }
- const operationTypes = {
- // Get the extended root operation types.
- query: schemaConfig.query && replaceNamedType(schemaConfig.query),
- mutation: schemaConfig.mutation && replaceNamedType(schemaConfig.mutation),
- subscription:
- schemaConfig.subscription && replaceNamedType(schemaConfig.subscription),
- // Then, incorporate schema definition and all schema extensions.
- ...(schemaDef && getOperationTypes([schemaDef])),
- ...getOperationTypes(schemaExtensions),
- };
- // Then produce and return a Schema config with these types.
- return {
- description: schemaDef?.description?.value,
- ...operationTypes,
- types: objectValues(typeMap),
- directives: [
- ...schemaConfig.directives.map(replaceDirective),
- ...directiveDefs.map(buildDirective),
- ],
- extensions: undefined,
- astNode: schemaDef ?? schemaConfig.astNode,
- extensionASTNodes: schemaConfig.extensionASTNodes.concat(schemaExtensions),
- assumeValid: options?.assumeValid ?? false,
- };
- // Below are functions used for producing this schema that have closed over
- // this scope and have access to the schema, cache, and newly defined types.
- function replaceType<T: GraphQLType>(type: T): T {
- if (isListType(type)) {
- // $FlowFixMe[incompatible-return]
- return new GraphQLList(replaceType(type.ofType));
- }
- if (isNonNullType(type)) {
- // $FlowFixMe[incompatible-return]
- return new GraphQLNonNull(replaceType(type.ofType));
- }
- return replaceNamedType(type);
- }
- function replaceNamedType<T: GraphQLNamedType>(type: T): T {
- // Note: While this could make early assertions to get the correctly
- // typed values, that would throw immediately while type system
- // validation with validateSchema() will produce more actionable results.
- return ((typeMap[type.name]: any): T);
- }
- function replaceDirective(directive: GraphQLDirective): GraphQLDirective {
- const config = directive.toConfig();
- return new GraphQLDirective({
- ...config,
- args: mapValue(config.args, extendArg),
- });
- }
- function extendNamedType(type: GraphQLNamedType): GraphQLNamedType {
- if (isIntrospectionType(type) || isSpecifiedScalarType(type)) {
- // Builtin types are not extended.
- return type;
- }
- if (isScalarType(type)) {
- return extendScalarType(type);
- }
- if (isObjectType(type)) {
- return extendObjectType(type);
- }
- if (isInterfaceType(type)) {
- return extendInterfaceType(type);
- }
- if (isUnionType(type)) {
- return extendUnionType(type);
- }
- if (isEnumType(type)) {
- return extendEnumType(type);
- }
- // istanbul ignore else (See: 'https://github.com/graphql/graphql-js/issues/2618')
- if (isInputObjectType(type)) {
- return extendInputObjectType(type);
- }
- // istanbul ignore next (Not reachable. All possible types have been considered)
- invariant(false, 'Unexpected type: ' + inspect((type: empty)));
- }
- function extendInputObjectType(
- type: GraphQLInputObjectType,
- ): GraphQLInputObjectType {
- const config = type.toConfig();
- const extensions = typeExtensionsMap[config.name] ?? [];
- return new GraphQLInputObjectType({
- ...config,
- fields: () => ({
- ...mapValue(config.fields, (field) => ({
- ...field,
- type: replaceType(field.type),
- })),
- ...buildInputFieldMap(extensions),
- }),
- extensionASTNodes: config.extensionASTNodes.concat(extensions),
- });
- }
- function extendEnumType(type: GraphQLEnumType): GraphQLEnumType {
- const config = type.toConfig();
- const extensions = typeExtensionsMap[type.name] ?? [];
- return new GraphQLEnumType({
- ...config,
- values: {
- ...config.values,
- ...buildEnumValueMap(extensions),
- },
- extensionASTNodes: config.extensionASTNodes.concat(extensions),
- });
- }
- function extendScalarType(type: GraphQLScalarType): GraphQLScalarType {
- const config = type.toConfig();
- const extensions = typeExtensionsMap[config.name] ?? [];
- let specifiedByUrl = config.specifiedByUrl;
- for (const extensionNode of extensions) {
- specifiedByUrl = getSpecifiedByUrl(extensionNode) ?? specifiedByUrl;
- }
- return new GraphQLScalarType({
- ...config,
- specifiedByUrl,
- extensionASTNodes: config.extensionASTNodes.concat(extensions),
- });
- }
- function extendObjectType(type: GraphQLObjectType): GraphQLObjectType {
- const config = type.toConfig();
- const extensions = typeExtensionsMap[config.name] ?? [];
- return new GraphQLObjectType({
- ...config,
- interfaces: () => [
- ...type.getInterfaces().map(replaceNamedType),
- ...buildInterfaces(extensions),
- ],
- fields: () => ({
- ...mapValue(config.fields, extendField),
- ...buildFieldMap(extensions),
- }),
- extensionASTNodes: config.extensionASTNodes.concat(extensions),
- });
- }
- function extendInterfaceType(
- type: GraphQLInterfaceType,
- ): GraphQLInterfaceType {
- const config = type.toConfig();
- const extensions = typeExtensionsMap[config.name] ?? [];
- return new GraphQLInterfaceType({
- ...config,
- interfaces: () => [
- ...type.getInterfaces().map(replaceNamedType),
- ...buildInterfaces(extensions),
- ],
- fields: () => ({
- ...mapValue(config.fields, extendField),
- ...buildFieldMap(extensions),
- }),
- extensionASTNodes: config.extensionASTNodes.concat(extensions),
- });
- }
- function extendUnionType(type: GraphQLUnionType): GraphQLUnionType {
- const config = type.toConfig();
- const extensions = typeExtensionsMap[config.name] ?? [];
- return new GraphQLUnionType({
- ...config,
- types: () => [
- ...type.getTypes().map(replaceNamedType),
- ...buildUnionTypes(extensions),
- ],
- extensionASTNodes: config.extensionASTNodes.concat(extensions),
- });
- }
- function extendField(
- field: GraphQLFieldConfig<mixed, mixed>,
- ): GraphQLFieldConfig<mixed, mixed> {
- return {
- ...field,
- type: replaceType(field.type),
- // $FlowFixMe[incompatible-call]
- args: mapValue(field.args, extendArg),
- };
- }
- function extendArg(arg: GraphQLArgumentConfig) {
- return {
- ...arg,
- type: replaceType(arg.type),
- };
- }
- function getOperationTypes(
- nodes: $ReadOnlyArray<SchemaDefinitionNode | SchemaExtensionNode>,
- ): {|
- query: ?GraphQLObjectType,
- mutation: ?GraphQLObjectType,
- subscription: ?GraphQLObjectType,
- |} {
- const opTypes = {};
- for (const node of nodes) {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const operationTypesNodes = node.operationTypes ?? [];
- for (const operationType of operationTypesNodes) {
- opTypes[operationType.operation] = getNamedType(operationType.type);
- }
- }
- // Note: While this could make early assertions to get the correctly
- // typed values below, that would throw immediately while type system
- // validation with validateSchema() will produce more actionable results.
- return (opTypes: any);
- }
- function getNamedType(node: NamedTypeNode): GraphQLNamedType {
- const name = node.name.value;
- const type = stdTypeMap[name] ?? typeMap[name];
- if (type === undefined) {
- throw new Error(`Unknown type: "${name}".`);
- }
- return type;
- }
- function getWrappedType(node: TypeNode): GraphQLType {
- if (node.kind === Kind.LIST_TYPE) {
- return new GraphQLList(getWrappedType(node.type));
- }
- if (node.kind === Kind.NON_NULL_TYPE) {
- return new GraphQLNonNull(getWrappedType(node.type));
- }
- return getNamedType(node);
- }
- function buildDirective(node: DirectiveDefinitionNode): GraphQLDirective {
- const locations = node.locations.map(
- ({ value }) => ((value: any): DirectiveLocationEnum),
- );
- return new GraphQLDirective({
- name: node.name.value,
- description: getDescription(node, options),
- locations,
- isRepeatable: node.repeatable,
- args: buildArgumentMap(node.arguments),
- astNode: node,
- });
- }
- function buildFieldMap(
- nodes: $ReadOnlyArray<
- | InterfaceTypeDefinitionNode
- | InterfaceTypeExtensionNode
- | ObjectTypeDefinitionNode
- | ObjectTypeExtensionNode,
- >,
- ): GraphQLFieldConfigMap<mixed, mixed> {
- const fieldConfigMap = Object.create(null);
- for (const node of nodes) {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const nodeFields = node.fields ?? [];
- for (const field of nodeFields) {
- fieldConfigMap[field.name.value] = {
- // Note: While this could make assertions to get the correctly typed
- // value, that would throw immediately while type system validation
- // with validateSchema() will produce more actionable results.
- type: (getWrappedType(field.type): any),
- description: getDescription(field, options),
- args: buildArgumentMap(field.arguments),
- deprecationReason: getDeprecationReason(field),
- astNode: field,
- };
- }
- }
- return fieldConfigMap;
- }
- function buildArgumentMap(
- args: ?$ReadOnlyArray<InputValueDefinitionNode>,
- ): GraphQLFieldConfigArgumentMap {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const argsNodes = args ?? [];
- const argConfigMap = Object.create(null);
- for (const arg of argsNodes) {
- // Note: While this could make assertions to get the correctly typed
- // value, that would throw immediately while type system validation
- // with validateSchema() will produce more actionable results.
- const type: any = getWrappedType(arg.type);
- argConfigMap[arg.name.value] = {
- type,
- description: getDescription(arg, options),
- defaultValue: valueFromAST(arg.defaultValue, type),
- deprecationReason: getDeprecationReason(arg),
- astNode: arg,
- };
- }
- return argConfigMap;
- }
- function buildInputFieldMap(
- nodes: $ReadOnlyArray<
- InputObjectTypeDefinitionNode | InputObjectTypeExtensionNode,
- >,
- ): GraphQLInputFieldConfigMap {
- const inputFieldMap = Object.create(null);
- for (const node of nodes) {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const fieldsNodes = node.fields ?? [];
- for (const field of fieldsNodes) {
- // Note: While this could make assertions to get the correctly typed
- // value, that would throw immediately while type system validation
- // with validateSchema() will produce more actionable results.
- const type: any = getWrappedType(field.type);
- inputFieldMap[field.name.value] = {
- type,
- description: getDescription(field, options),
- defaultValue: valueFromAST(field.defaultValue, type),
- deprecationReason: getDeprecationReason(field),
- astNode: field,
- };
- }
- }
- return inputFieldMap;
- }
- function buildEnumValueMap(
- nodes: $ReadOnlyArray<EnumTypeDefinitionNode | EnumTypeExtensionNode>,
- ): GraphQLEnumValueConfigMap {
- const enumValueMap = Object.create(null);
- for (const node of nodes) {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const valuesNodes = node.values ?? [];
- for (const value of valuesNodes) {
- enumValueMap[value.name.value] = {
- description: getDescription(value, options),
- deprecationReason: getDeprecationReason(value),
- astNode: value,
- };
- }
- }
- return enumValueMap;
- }
- function buildInterfaces(
- nodes: $ReadOnlyArray<
- | InterfaceTypeDefinitionNode
- | InterfaceTypeExtensionNode
- | ObjectTypeDefinitionNode
- | ObjectTypeExtensionNode,
- >,
- ): Array<GraphQLInterfaceType> {
- const interfaces = [];
- for (const node of nodes) {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const interfacesNodes = node.interfaces ?? [];
- for (const type of interfacesNodes) {
- // Note: While this could make assertions to get the correctly typed
- // values below, that would throw immediately while type system
- // validation with validateSchema() will produce more actionable
- // results.
- interfaces.push((getNamedType(type): any));
- }
- }
- return interfaces;
- }
- function buildUnionTypes(
- nodes: $ReadOnlyArray<UnionTypeDefinitionNode | UnionTypeExtensionNode>,
- ): Array<GraphQLObjectType> {
- const types = [];
- for (const node of nodes) {
- // istanbul ignore next (See: 'https://github.com/graphql/graphql-js/issues/2203')
- const typeNodes = node.types ?? [];
- for (const type of typeNodes) {
- // Note: While this could make assertions to get the correctly typed
- // values below, that would throw immediately while type system
- // validation with validateSchema() will produce more actionable
- // results.
- types.push((getNamedType(type): any));
- }
- }
- return types;
- }
- function buildType(astNode: TypeDefinitionNode): GraphQLNamedType {
- const name = astNode.name.value;
- const description = getDescription(astNode, options);
- const extensionNodes = typeExtensionsMap[name] ?? [];
- switch (astNode.kind) {
- case Kind.OBJECT_TYPE_DEFINITION: {
- const extensionASTNodes = (extensionNodes: any);
- const allNodes = [astNode, ...extensionASTNodes];
- return new GraphQLObjectType({
- name,
- description,
- interfaces: () => buildInterfaces(allNodes),
- fields: () => buildFieldMap(allNodes),
- astNode,
- extensionASTNodes,
- });
- }
- case Kind.INTERFACE_TYPE_DEFINITION: {
- const extensionASTNodes = (extensionNodes: any);
- const allNodes = [astNode, ...extensionASTNodes];
- return new GraphQLInterfaceType({
- name,
- description,
- interfaces: () => buildInterfaces(allNodes),
- fields: () => buildFieldMap(allNodes),
- astNode,
- extensionASTNodes,
- });
- }
- case Kind.ENUM_TYPE_DEFINITION: {
- const extensionASTNodes = (extensionNodes: any);
- const allNodes = [astNode, ...extensionASTNodes];
- return new GraphQLEnumType({
- name,
- description,
- values: buildEnumValueMap(allNodes),
- astNode,
- extensionASTNodes,
- });
- }
- case Kind.UNION_TYPE_DEFINITION: {
- const extensionASTNodes = (extensionNodes: any);
- const allNodes = [astNode, ...extensionASTNodes];
- return new GraphQLUnionType({
- name,
- description,
- types: () => buildUnionTypes(allNodes),
- astNode,
- extensionASTNodes,
- });
- }
- case Kind.SCALAR_TYPE_DEFINITION: {
- const extensionASTNodes = (extensionNodes: any);
- return new GraphQLScalarType({
- name,
- description,
- specifiedByUrl: getSpecifiedByUrl(astNode),
- astNode,
- extensionASTNodes,
- });
- }
- case Kind.INPUT_OBJECT_TYPE_DEFINITION: {
- const extensionASTNodes = (extensionNodes: any);
- const allNodes = [astNode, ...extensionASTNodes];
- return new GraphQLInputObjectType({
- name,
- description,
- fields: () => buildInputFieldMap(allNodes),
- astNode,
- extensionASTNodes,
- });
- }
- }
- // istanbul ignore next (Not reachable. All possible type definition nodes have been considered)
- invariant(
- false,
- 'Unexpected type definition node: ' + inspect((astNode: empty)),
- );
- }
- }
- const stdTypeMap = keyMap(
- specifiedScalarTypes.concat(introspectionTypes),
- (type) => type.name,
- );
- /**
- * Given a field or enum value node, returns the string value for the
- * deprecation reason.
- */
- function getDeprecationReason(
- node:
- | EnumValueDefinitionNode
- | FieldDefinitionNode
- | InputValueDefinitionNode,
- ): ?string {
- const deprecated = getDirectiveValues(GraphQLDeprecatedDirective, node);
- return (deprecated?.reason: any);
- }
- /**
- * Given a scalar node, returns the string value for the specifiedByUrl.
- */
- function getSpecifiedByUrl(
- node: ScalarTypeDefinitionNode | ScalarTypeExtensionNode,
- ): ?string {
- const specifiedBy = getDirectiveValues(GraphQLSpecifiedByDirective, node);
- return (specifiedBy?.url: any);
- }
- /**
- * Given an ast node, returns its string description.
- * @deprecated: provided to ease adoption and will be removed in v16.
- *
- * Accepts options as a second argument:
- *
- * - commentDescriptions:
- * Provide true to use preceding comments as the description.
- *
- */
- export function getDescription(
- node: { +description?: StringValueNode, +loc?: Location, ... },
- options: ?{ commentDescriptions?: boolean, ... },
- ): void | string {
- if (node.description) {
- return node.description.value;
- }
- if (options?.commentDescriptions === true) {
- const rawValue = getLeadingCommentBlock(node);
- if (rawValue !== undefined) {
- return dedentBlockStringValue('\n' + rawValue);
- }
- }
- }
- function getLeadingCommentBlock(node): void | string {
- const loc = node.loc;
- if (!loc) {
- return;
- }
- const comments = [];
- let token = loc.startToken.prev;
- while (
- token != null &&
- token.kind === TokenKind.COMMENT &&
- token.next &&
- token.prev &&
- token.line + 1 === token.next.line &&
- token.line !== token.prev.line
- ) {
- const value = String(token.value);
- comments.push(value);
- token = token.prev;
- }
- return comments.length > 0 ? comments.reverse().join('\n') : undefined;
- }
|