From 0cc17f0f594ed874fd357f6c547af5a1479d3db8 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Sun, 3 Dec 2023 23:53:26 +0800 Subject: [PATCH] feat: support `graphql` (no plugins required). --- README.md | 8 +++ src/embedded/graphql/embedded-language.ts | 7 ++ src/embedded/graphql/embedder.ts | 85 +++++++++++++++++++++++ src/embedded/graphql/index.ts | 3 + src/embedded/graphql/options.ts | 47 +++++++++++++ 5 files changed, 150 insertions(+) create mode 100644 src/embedded/graphql/embedded-language.ts create mode 100644 src/embedded/graphql/embedder.ts create mode 100644 src/embedded/graphql/index.ts create mode 100644 src/embedded/graphql/options.ts diff --git a/README.md b/README.md index 089324a..3be0372 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,14 @@ Formatting embedded ECMAScript code doesn't require other plugins and uses the p Formatting embedded GLSL code requires [`prettier-plugin-glsl`](https://github.com/NaridaL/glsl-language-toolkit/tree/main/packages/prettier-plugin-glsl) to be loaded as well. +#### GraphQL + +| Option | Default | Description | +| :--------------------------: | :------------------------------------------: | ----------------------------------------------------------------------------------------------------- | +| `embeddedGraphqlIdentifiers` | [`[...]`](./src/embedded/graphql/options.ts) | Tag or comment identifiers that make their subsequent template literals be identified as GraphQL code | + +Formatting embedded GraphQL code doesn't require other plugins and uses the parsers and printers provided by Prettier natively. This can override the native embedded language formatting for GraphQL code. If you want to keep the native behavior, set `embeddedGraphqlIdentifiers` to `[]` or other identifiers. + #### HTML | Option | Default | Description | diff --git a/src/embedded/graphql/embedded-language.ts b/src/embedded/graphql/embedded-language.ts new file mode 100644 index 0000000..cd10541 --- /dev/null +++ b/src/embedded/graphql/embedded-language.ts @@ -0,0 +1,7 @@ +export const embeddedLanguage = "embeddedGraphql"; + +declare module "../types.js" { + interface EmbeddedLanguagesHolder { + [embeddedLanguage]: void; + } +} diff --git a/src/embedded/graphql/embedder.ts b/src/embedded/graphql/embedder.ts new file mode 100644 index 0000000..4bd2fc1 --- /dev/null +++ b/src/embedded/graphql/embedder.ts @@ -0,0 +1,85 @@ +import type { Options } from "prettier"; +import { builders } from "prettier/doc"; +import type { Embedder } from "../../types.js"; +import { + preparePlaceholder, + printTemplateExpressions, + simpleRehydrateDoc, +} from "../utils.js"; +import { embeddedLanguage } from "./embedded-language.js"; + +const { line, group, indent, softline } = builders; + +export const embedder: Embedder = async ( + textToDoc, + print, + path, + options, + { identifier, embeddedOverrideOptions }, +) => { + options = { + ...options, + ...embeddedOverrideOptions, + }; + + const { node } = path; + + const { createPlaceholder, placeholderRegex } = preparePlaceholder(); + + const text = node.quasis + .map((quasi, index, { length }) => + index === length - 1 + ? quasi.value.cooked + : quasi.value.cooked + createPlaceholder(index), + ) + .join(""); + + const leadingWhitespaces = text.match(/^\s+/)?.[0] ?? ""; + const trailingWhitespaces = text.match(/\s+$/)?.[0] ?? ""; + + const trimmedText = text.slice( + leadingWhitespaces.length, + -trailingWhitespaces.length || undefined, + ); + + const expressionDocs = printTemplateExpressions(path, print); + + const doc = await textToDoc(trimmedText, { + ...options, + parser: "graphql", + }); + + const contentDoc = simpleRehydrateDoc(doc, placeholderRegex, expressionDocs); + + if (options.preserveEmbeddedExteriorWhitespaces?.includes(identifier)) { + // TODO: should we label the doc with { hug: false } ? + // https://github.com/prettier/prettier/blob/5cfb76ee50cf286cab267cf3cb7a26e749c995f7/src/language-js/embed/html.js#L88 + return group([ + "`", + leadingWhitespaces, + options.noEmbeddedMultiLineIndentation?.includes(identifier) + ? [group(contentDoc)] + : indent([group(contentDoc)]), + trailingWhitespaces, + "`", + ]); + } + + const leadingLineBreak = leadingWhitespaces.length ? line : softline; + const trailingLineBreak = trailingWhitespaces.length ? line : softline; + + return group([ + "`", + options.noEmbeddedMultiLineIndentation?.includes(identifier) + ? [leadingLineBreak, group(contentDoc)] + : indent([leadingLineBreak, group(contentDoc)]), + trailingLineBreak, + "`", + ]); +}; + +declare module "../types.js" { + interface EmbeddedEmbedders { + [embeddedLanguage]: typeof embedder; + } +} diff --git a/src/embedded/graphql/index.ts b/src/embedded/graphql/index.ts new file mode 100644 index 0000000..cee8055 --- /dev/null +++ b/src/embedded/graphql/index.ts @@ -0,0 +1,3 @@ +export * from "./embedded-language.js"; +export * from "./embedder.js"; +export * from "./options.js"; diff --git a/src/embedded/graphql/options.ts b/src/embedded/graphql/options.ts new file mode 100644 index 0000000..64b8867 --- /dev/null +++ b/src/embedded/graphql/options.ts @@ -0,0 +1,47 @@ +import type { CoreCategoryType, SupportOptions } from "prettier"; +import { + makeIdentifiersOptionName, + type AutocompleteStringList, + type StringListToInterfaceKey, +} from "../utils.js"; +import { embeddedLanguage } from "./embedded-language.js"; + +/** References: + * - https://github.com/github-linguist/linguist/blob/7ca3799b8b5f1acde1dd7a8dfb7ae849d3dfb4cd/lib/linguist/languages.yml#L2578 + */ +const DEFAULT_IDENTIFIERS = ["graphql", "gql"] as const; +type Identifiers = AutocompleteStringList; +type DefaultIdentifiersHolder = StringListToInterfaceKey< + typeof DEFAULT_IDENTIFIERS +>; + +const EMBEDDED_LANGUAGE_IDENTIFIERS = + makeIdentifiersOptionName(embeddedLanguage); + +export interface PrettierPluginDepsOptions { + /* prettier built-in options */ +} + +export const options = { + [EMBEDDED_LANGUAGE_IDENTIFIERS]: { + category: "Global", + type: "string", + array: true, + default: [{ value: [...DEFAULT_IDENTIFIERS] }], + description: "Specify embedded GraphQL language identifiers.", + }, +} satisfies SupportOptions & Record; + +type Options = typeof options; + +declare module "../types.js" { + interface EmbeddedOptions extends Options {} + interface EmbeddedDefaultIdentifiersHolder extends DefaultIdentifiersHolder {} + interface PrettierPluginEmbedOptions { + [EMBEDDED_LANGUAGE_IDENTIFIERS]?: Identifiers; + } +} + +declare module "prettier" { + export interface Options extends PrettierPluginDepsOptions {} +}