-
Notifications
You must be signed in to change notification settings - Fork 2
/
printers.ts
160 lines (135 loc) · 4.29 KB
/
printers.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import type { Node, TemplateLiteral } from "estree";
import type {
AstPath,
Options,
Parser,
ParserOptions,
Plugin,
Printer,
} from "prettier";
import { printers as estreePrinters } from "prettier/plugins/estree.mjs";
import { embeddedLanguages } from "./embedded/index.js";
import { parsers } from "./parsers.js";
import {
assumeAs,
compareTagExpressionToTagString,
createCommentsInOptionsGenerator,
createEmbeddedDoc,
createTagsInOptionsGenerator,
parseCommentFromTemplateLiteralAstPath,
parseTagFromTemplateLiteralAstPath,
} from "./utils.js";
const { estree: estreePrinter } = estreePrinters;
const { embed: builtInEmbed } = estreePrinter;
// the embed method in plugin printers
// https://prettier.io/docs/en/plugins.html#optional-embed
// we override the built-in one with this
// so that we can add hooks to support other languages
const embed: Printer["embed"] = (path: AstPath<Node>, options: Options) => {
const { node } = path;
// skip all non-template-literal nodes
if (
node.type !== "TemplateLiteral" ||
node.quasis.some(({ value: { cooked } }) => cooked === null)
) {
return null;
}
assumeAs<{ node: TemplateLiteral }>(path);
// check if the template literal node has a leading block comment,
// if it does, the inner value of the block comment is returned,
// if it does not, `undefined` is returned.
const comment = parseCommentFromTemplateLiteralAstPath(path);
// template literal node has a leading comment block
if (typeof comment === "string") {
const commentsInOptionsGenerator = createCommentsInOptionsGenerator(
options,
comment,
);
for (const embeddedLanguage of embeddedLanguages) {
let hit = false;
for (const commentInOptions of commentsInOptionsGenerator(
embeddedLanguage,
)) {
if (comment === commentInOptions) {
hit = true;
break;
}
}
if (!hit) {
continue;
}
return createEmbeddedDoc(
node,
embeddedLanguage,
comment,
"comment",
options,
);
}
// unknown comment block
// fallback to built-in behavior
return builtInEmbed?.(path, options) ?? null;
}
// check if the template literal node has a tag,
// if it does and the tag is a simple identifier, the identifier name is returned as a string
// if it does but the tag is a complex expression, the expression is returned as an expression node
// if it does not, `undefined` is returned.
const tag = parseTagFromTemplateLiteralAstPath(path);
// template literal node has a simple identifier tag
if (typeof tag === "string") {
const tagsInOptionsGenerator = createTagsInOptionsGenerator(options, tag);
for (const embeddedLanguage of embeddedLanguages) {
let hit = false;
for (const tagInOptions of tagsInOptionsGenerator(embeddedLanguage)) {
if (tag === tagInOptions) {
hit = true;
break;
}
}
if (!hit) {
continue;
}
return createEmbeddedDoc(node, embeddedLanguage, tag, "tag", options);
}
// unknown tag
// fallback to built-in behavior
return builtInEmbed?.(path, options) ?? null;
}
// template literal node has a complex expression tag
if (tag !== undefined) {
const parse = (text: string) =>
(parsers[options.parser as keyof typeof parsers] as Parser<Node>).parse(
text,
options as ParserOptions<Node>,
);
const tagsInOptionsGenerator = createTagsInOptionsGenerator(options);
for (const embeddedLanguage of embeddedLanguages) {
let stringFormTag: string | undefined = undefined;
for (const tagInOptions of tagsInOptionsGenerator(embeddedLanguage)) {
if (compareTagExpressionToTagString(tag, tagInOptions, parse)) {
stringFormTag = tagInOptions;
break;
}
}
if (stringFormTag === undefined) {
continue;
}
return createEmbeddedDoc(
node,
embeddedLanguage,
stringFormTag,
"tag",
options,
);
}
}
// fallback to built-in behavior
return builtInEmbed?.(path, options) ?? null;
};
// extends estree printer to parse embedded lanaguges in js/ts files
export const printers: Plugin["printers"] = {
estree: {
...estreePrinter,
embed,
},
};