Skip to content

Commit

Permalink
feat: support graphql (no plugins required).
Browse files Browse the repository at this point in the history
  • Loading branch information
Sec-ant committed Dec 13, 2023
1 parent 5ea2e1b commit 0cc17f0
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 0 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
7 changes: 7 additions & 0 deletions src/embedded/graphql/embedded-language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const embeddedLanguage = "embeddedGraphql";

declare module "../types.js" {
interface EmbeddedLanguagesHolder {
[embeddedLanguage]: void;
}
}
85 changes: 85 additions & 0 deletions src/embedded/graphql/embedder.ts
Original file line number Diff line number Diff line change
@@ -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<Options> = 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;
}
}
3 changes: 3 additions & 0 deletions src/embedded/graphql/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from "./embedded-language.js";
export * from "./embedder.js";
export * from "./options.js";
47 changes: 47 additions & 0 deletions src/embedded/graphql/options.ts
Original file line number Diff line number Diff line change
@@ -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<typeof DEFAULT_IDENTIFIERS>;
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<string, { category: CoreCategoryType }>;

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 {}
}

0 comments on commit 0cc17f0

Please sign in to comment.