@spyglassmc/mcdoc 0.3.35 → 0.3.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/formatter/index.d.ts +3 -0
- package/lib/formatter/index.js +673 -0
- package/lib/index.js +2 -0
- package/lib/parser/index.d.ts +5 -0
- package/lib/parser/index.js +1 -1
- package/package.json +3 -3
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
import { indentFormatter } from '@spyglassmc/core';
|
|
2
|
+
import { AttributeTreeClosure } from '../parser/index.js';
|
|
3
|
+
// These formatters operate under the assumption that each AST node's children are in the same order as they appear when formatted.
|
|
4
|
+
// With the exception of comments, because those can be moved to the parent if they're at odd locations
|
|
5
|
+
// (for example a comment between the identifier and '{' of a struct will be moved to before the struct).
|
|
6
|
+
const nodeTypesAllowingComments = new Set([
|
|
7
|
+
'mcdoc:module',
|
|
8
|
+
'mcdoc:struct/block',
|
|
9
|
+
'mcdoc:enum/block',
|
|
10
|
+
'mcdoc:doc_comments',
|
|
11
|
+
'mcdoc:type/tuple',
|
|
12
|
+
'mcdoc:type/union',
|
|
13
|
+
]);
|
|
14
|
+
const nodeTypesAllowingTrailingComments = new Set([
|
|
15
|
+
'mcdoc:module',
|
|
16
|
+
'mcdoc:struct/block',
|
|
17
|
+
'mcdoc:enum/block',
|
|
18
|
+
'mcdoc:doc_comments',
|
|
19
|
+
]);
|
|
20
|
+
const prelimNodeTypes = new Set([
|
|
21
|
+
'mcdoc:attribute',
|
|
22
|
+
'mcdoc:doc_comments',
|
|
23
|
+
]);
|
|
24
|
+
function formatChildren(node, ctx, childFormatInfo, excludeLastChildSuffix = false, onlyFormatPrelimType) {
|
|
25
|
+
const allowsComments = nodeTypesAllowingComments.has(node.type);
|
|
26
|
+
const allowsTrailingComments = nodeTypesAllowingTrailingComments.has(node.type);
|
|
27
|
+
const children = allowsComments ? liftChildComments(node.children) : node.children;
|
|
28
|
+
const lastNonComment = children.findLastIndex((child) => child.type !== 'comment');
|
|
29
|
+
const lastFormattedChild = onlyFormatPrelimType !== undefined
|
|
30
|
+
? children.findLastIndex((child) => child.type === onlyFormatPrelimType)
|
|
31
|
+
// Exclude trailing comments, so trailing comments don't change
|
|
32
|
+
// whether the node in front of the comments has the suffix
|
|
33
|
+
: children.findLastIndex((child) => child.type !== 'comment');
|
|
34
|
+
const content = children.map((child, i) => {
|
|
35
|
+
// Only format prelim when it's supposed to be formatted
|
|
36
|
+
// and don't format other nodes when they're not supposed to be formatted.
|
|
37
|
+
if (onlyFormatPrelimType !== undefined && child.type !== onlyFormatPrelimType) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
if (onlyFormatPrelimType === undefined && prelimNodeTypes.has(child.type)) {
|
|
41
|
+
return '';
|
|
42
|
+
}
|
|
43
|
+
if (child.type === 'comment' && !allowsComments) {
|
|
44
|
+
// Don't format comments if the type doesn't allow them.
|
|
45
|
+
// A parent type that does allow comments should have already included them.
|
|
46
|
+
return '';
|
|
47
|
+
}
|
|
48
|
+
if (i > lastNonComment && !allowsTrailingComments) {
|
|
49
|
+
// Don't format trailing comments if the type doesn't allow them.
|
|
50
|
+
// A parent type that does allow comments should have already included them.
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
const info = childFormatInfo[child.type];
|
|
54
|
+
const value = ctx.meta.getFormatter(child.type)(child, info?.indentSelf ? indentFormatter(ctx) : ctx);
|
|
55
|
+
const prefix = info?.prefix ?? '';
|
|
56
|
+
const hasSuffix = info?.suffix !== undefined
|
|
57
|
+
&& (!excludeLastChildSuffix || lastFormattedChild !== i);
|
|
58
|
+
const suffix = hasSuffix ? info.suffix : '';
|
|
59
|
+
const formatted = `${prefix}${value}${suffix}`;
|
|
60
|
+
return formatted;
|
|
61
|
+
}).join('');
|
|
62
|
+
return content;
|
|
63
|
+
}
|
|
64
|
+
const formatterIgnoreAttributeName = 'formatter_ignore';
|
|
65
|
+
function shouldFormatterIgnore(node) {
|
|
66
|
+
return node.children?.some((child) => {
|
|
67
|
+
if (child.type !== 'mcdoc:attribute') {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
return child.children.some((attributeChild) => {
|
|
71
|
+
return attributeChild.type === 'mcdoc:identifier'
|
|
72
|
+
&& attributeChild.value === formatterIgnoreAttributeName;
|
|
73
|
+
});
|
|
74
|
+
}) ?? false;
|
|
75
|
+
}
|
|
76
|
+
function getUnformatted(node, ctx) {
|
|
77
|
+
return ctx.doc.getText({
|
|
78
|
+
start: ctx.doc.positionAt(node.range.start),
|
|
79
|
+
end: ctx.doc.positionAt(node.range.end),
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function hasMultilineChild(node, isRecursiveCall = false, alwaysIncludeComments = false) {
|
|
83
|
+
if (!node.children) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
for (let i = 0; i < node.children.length; i++) {
|
|
87
|
+
const child = node.children[i];
|
|
88
|
+
if (child.type === 'comment') {
|
|
89
|
+
if (alwaysIncludeComments) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (nodeTypesAllowingComments.has(node.type)) {
|
|
93
|
+
if (nodeTypesAllowingTrailingComments.has(node.type)) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
// Only return true if there's a non-comment node after the comment, so
|
|
97
|
+
// the comment isn't moved outside the node.
|
|
98
|
+
// We only need to check whether i+1 is not a comment instead of checking
|
|
99
|
+
// all of the nodes after i, because even in the case where i+1 is a comment,
|
|
100
|
+
// but i+2 is a non-comment, the check will still succeed true in the next iteration.
|
|
101
|
+
if (i < node.children.length - 1 && node.children[i + 1].type !== 'comment') {
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (child.type === 'mcdoc:struct') {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (child.type === 'mcdoc:enum') {
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
if (isRecursiveCall && child.type === 'mcdoc:attribute') {
|
|
113
|
+
// Attributes only count as multiline for a recursive call, because attributes found
|
|
114
|
+
// during the first call would be put in front of the node and not inside it.
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (hasMultilineChild(child, true, alwaysIncludeComments || i < node.children.length - 1)) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const maxDynamicInlineLength = 80; // Kind of arbitrary
|
|
124
|
+
function formatDynamicMultiline(node, formatter) {
|
|
125
|
+
if (hasMultilineChild(node)) {
|
|
126
|
+
return formatter(true);
|
|
127
|
+
}
|
|
128
|
+
const inlineFormat = formatter(false);
|
|
129
|
+
if (inlineFormat.length > maxDynamicInlineLength) {
|
|
130
|
+
return formatter(true);
|
|
131
|
+
}
|
|
132
|
+
return inlineFormat;
|
|
133
|
+
}
|
|
134
|
+
function formatWithPrelim(node, ctx, putAttributesOnSeparateLine, putDocOnSeparateLine, contentFormatter) {
|
|
135
|
+
if (shouldFormatterIgnore(node)) {
|
|
136
|
+
return getUnformatted(node, ctx);
|
|
137
|
+
}
|
|
138
|
+
function attributeFormatter(doMultiline, attributeCtx) {
|
|
139
|
+
const childFormatInfo = {
|
|
140
|
+
'mcdoc:attribute': {
|
|
141
|
+
suffix: doMultiline ? `\n${attributeCtx.indent()}` : ' ',
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
return formatChildren(node, attributeCtx, childFormatInfo,
|
|
145
|
+
// Last suffix is excluded, because it is specified separately through `putAttributesOnSeparateLine`.
|
|
146
|
+
// This makes it possible to put all attributes on one line but still add a newline at the end.
|
|
147
|
+
true, 'mcdoc:attribute');
|
|
148
|
+
}
|
|
149
|
+
const shouldIndentPrelim = !putDocOnSeparateLine
|
|
150
|
+
&& node.children.some((child) => child.type === 'mcdoc:doc_comments');
|
|
151
|
+
function processFormattedAttributes(formattedAttributes, areAttributesMultiline) {
|
|
152
|
+
const formattedDocComments = formatChildren(node, putDocOnSeparateLine ? ctx : indentFormatter(ctx), {}, false, 'mcdoc:doc_comments');
|
|
153
|
+
const hasAdditionalIndent = shouldIndentPrelim
|
|
154
|
+
|| (!putAttributesOnSeparateLine && areAttributesMultiline);
|
|
155
|
+
if (formattedAttributes !== '') {
|
|
156
|
+
if (hasAdditionalIndent) {
|
|
157
|
+
formattedAttributes += `\n${ctx.indent(1)}`;
|
|
158
|
+
}
|
|
159
|
+
else if (putAttributesOnSeparateLine) {
|
|
160
|
+
formattedAttributes += `\n${ctx.indent()}`;
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
formattedAttributes += ' ';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const prelim = (hasAdditionalIndent ? `\n${ctx.indent(1)}` : '')
|
|
167
|
+
+ formattedDocComments + formattedAttributes;
|
|
168
|
+
const content = contentFormatter(hasAdditionalIndent
|
|
169
|
+
? indentFormatter(ctx)
|
|
170
|
+
: ctx);
|
|
171
|
+
return prelim + content;
|
|
172
|
+
}
|
|
173
|
+
const multilineChild = node.children?.some((child) => {
|
|
174
|
+
child.type === 'mcdoc:attribute' && hasMultilineChild(child);
|
|
175
|
+
});
|
|
176
|
+
if (multilineChild) {
|
|
177
|
+
return processFormattedAttributes(attributeFormatter(true, putAttributesOnSeparateLine ? ctx : indentFormatter(ctx)), true);
|
|
178
|
+
}
|
|
179
|
+
const inlineAttributes = attributeFormatter(false, ctx);
|
|
180
|
+
if (inlineAttributes.length > maxDynamicInlineLength) {
|
|
181
|
+
return processFormattedAttributes(attributeFormatter(true, putAttributesOnSeparateLine ? ctx : indentFormatter(ctx)), true);
|
|
182
|
+
}
|
|
183
|
+
return processFormattedAttributes((shouldIndentPrelim ? ctx.indent(1) : '') + inlineAttributes, false);
|
|
184
|
+
}
|
|
185
|
+
function liftChildComments(children) {
|
|
186
|
+
// Get mutable children list so comments from children that don't allow comments can be added to the list.
|
|
187
|
+
const mutableChildren = [...children];
|
|
188
|
+
for (let i = 0; i < mutableChildren.length; i++) {
|
|
189
|
+
const child = mutableChildren[i];
|
|
190
|
+
const { beforeNode, afterNode } = findComments(child);
|
|
191
|
+
// Add comments and advance i to not iterate over them again
|
|
192
|
+
mutableChildren.splice(i, 0, ...beforeNode);
|
|
193
|
+
i += beforeNode.length;
|
|
194
|
+
mutableChildren.splice(i + 1, 0, ...afterNode);
|
|
195
|
+
i += afterNode.length;
|
|
196
|
+
}
|
|
197
|
+
return mutableChildren;
|
|
198
|
+
}
|
|
199
|
+
function findComments(node) {
|
|
200
|
+
const result = {
|
|
201
|
+
beforeNode: [],
|
|
202
|
+
afterNode: [],
|
|
203
|
+
};
|
|
204
|
+
if (!node.children) {
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
let currentCommentSequence = [];
|
|
208
|
+
node.children.forEach((child) => {
|
|
209
|
+
if (child.type === 'comment') {
|
|
210
|
+
currentCommentSequence.push(child);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
result.beforeNode.push(...currentCommentSequence);
|
|
214
|
+
currentCommentSequence = [];
|
|
215
|
+
const childComments = findComments(child);
|
|
216
|
+
if (nodeTypesAllowingComments.has(child.type)) {
|
|
217
|
+
// The child will format its own comments, so we don't need to lift them.
|
|
218
|
+
// Comments at the end of the child are still lifted for some nodes, because they can't be distinguished from comments
|
|
219
|
+
// that come after the child.
|
|
220
|
+
if (!nodeTypesAllowingTrailingComments.has(child.type)) {
|
|
221
|
+
currentCommentSequence = childComments.afterNode;
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
result.beforeNode.push(...childComments.beforeNode);
|
|
226
|
+
currentCommentSequence = childComments.afterNode;
|
|
227
|
+
});
|
|
228
|
+
result.afterNode.push(...currentCommentSequence);
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
function getTypeNodes(children) {
|
|
232
|
+
return children.filter((child) => child.type.startsWith('mcdoc:type/') || child.type === 'mcdoc:enum'
|
|
233
|
+
|| child.type === 'mcdoc:struct');
|
|
234
|
+
}
|
|
235
|
+
const module = (node, ctx) => {
|
|
236
|
+
const children = liftChildComments(node.children);
|
|
237
|
+
return children.map((child, i) => {
|
|
238
|
+
function addNewlineSeparator(formatted) {
|
|
239
|
+
if (i === children.length - 1) {
|
|
240
|
+
return formatted;
|
|
241
|
+
}
|
|
242
|
+
return `${formatted}\n`;
|
|
243
|
+
}
|
|
244
|
+
const formatted = ctx.meta.getFormatter(child.type)(child, ctx);
|
|
245
|
+
if (shouldFormatterIgnore(child)) {
|
|
246
|
+
// With formatterIgnore, the whitespace after a node is already included in the string, so no newlines have to be added
|
|
247
|
+
return formatted;
|
|
248
|
+
}
|
|
249
|
+
if (child.type === 'comment') {
|
|
250
|
+
return `${formatted}\n`;
|
|
251
|
+
}
|
|
252
|
+
if (child.type === 'mcdoc:use_statement' && children[i + 1]?.type === 'mcdoc:use_statement') {
|
|
253
|
+
return `${formatted}\n`;
|
|
254
|
+
}
|
|
255
|
+
// With an empty line between nodes
|
|
256
|
+
// (comments don't have them, because they probably refer to the next child)
|
|
257
|
+
// (use statements don't have them between each other, because use statements are supposed to be grouped together)
|
|
258
|
+
return addNewlineSeparator(`${formatted}\n`);
|
|
259
|
+
}).join('');
|
|
260
|
+
};
|
|
261
|
+
const useStatement = (node, ctx) => {
|
|
262
|
+
const hasAlias = node.children.some((child) => child.type === 'mcdoc:identifier');
|
|
263
|
+
return formatChildren(node, ctx, {
|
|
264
|
+
'mcdoc:literal': { suffix: ' ' },
|
|
265
|
+
'mcdoc:path': { suffix: hasAlias ? ' ' : '' },
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
const injection = (node, ctx) => {
|
|
269
|
+
return formatChildren(node, ctx, {
|
|
270
|
+
'mcdoc:literal': { suffix: ' ' },
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
const struct = (node, ctx) => {
|
|
274
|
+
const isTopLevel = node.parent?.type === 'mcdoc:module';
|
|
275
|
+
return formatWithPrelim(node, ctx, isTopLevel, isTopLevel, (contentCtx) => {
|
|
276
|
+
return formatChildren(node, contentCtx, {
|
|
277
|
+
'mcdoc:literal': { suffix: ' ' },
|
|
278
|
+
'mcdoc:identifier': { suffix: ' ' },
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
};
|
|
282
|
+
const structInjection = (node, ctx) => {
|
|
283
|
+
return formatChildren(node, ctx, {
|
|
284
|
+
'mcdoc:literal': { suffix: ' ' },
|
|
285
|
+
'mcdoc:path': { suffix: ' ' },
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
const structBlock = (node, ctx) => {
|
|
289
|
+
if (node.children.length === 0) {
|
|
290
|
+
return '{}';
|
|
291
|
+
}
|
|
292
|
+
const content = formatChildren(node, ctx, {
|
|
293
|
+
'comment': { prefix: ctx.indent(1), suffix: '\n', indentSelf: true },
|
|
294
|
+
'mcdoc:struct/field/pair': { suffix: ',\n', indentSelf: true },
|
|
295
|
+
'mcdoc:struct/field/spread': { suffix: ',\n', indentSelf: true },
|
|
296
|
+
});
|
|
297
|
+
return `{\n${content}${ctx.indent()}}`;
|
|
298
|
+
};
|
|
299
|
+
const structPairField = (node, ctx) => {
|
|
300
|
+
const keySuffix = `${node.isOptional ? '?' : ''}: `;
|
|
301
|
+
return ctx.indent() + formatWithPrelim(node, ctx, true, true, (contentCtx) => {
|
|
302
|
+
return formatChildren(node, contentCtx, {
|
|
303
|
+
'mcdoc:struct/map_key': { suffix: keySuffix },
|
|
304
|
+
'mcdoc:identifier': { suffix: keySuffix },
|
|
305
|
+
'string': { suffix: keySuffix },
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
const structMapKey = (node, ctx) => {
|
|
310
|
+
const typeNode = getTypeNodes(node.children)[0];
|
|
311
|
+
return formatChildren(node, ctx, {
|
|
312
|
+
[typeNode.type]: { prefix: '[', suffix: ']' },
|
|
313
|
+
});
|
|
314
|
+
};
|
|
315
|
+
const structSpreadField = (node, ctx) => {
|
|
316
|
+
const typeNode = getTypeNodes(node.children)[0];
|
|
317
|
+
return ctx.indent() + formatWithPrelim(node, ctx, true, true, (contentCtx) => {
|
|
318
|
+
return formatChildren(node, contentCtx, {
|
|
319
|
+
[typeNode.type]: { prefix: '...' },
|
|
320
|
+
});
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
const _enum = (node, ctx) => {
|
|
324
|
+
const isTopLevel = node.parent?.type === 'mcdoc:module';
|
|
325
|
+
return formatWithPrelim(node, ctx, isTopLevel, isTopLevel, (contentCtx) => {
|
|
326
|
+
return node.children.map((child) => {
|
|
327
|
+
if (child.type === 'comment') {
|
|
328
|
+
// Don't format comments if the type doesn't allow them.
|
|
329
|
+
// A parent type that does allow comments should have already included them.
|
|
330
|
+
return '';
|
|
331
|
+
}
|
|
332
|
+
if (child.type === 'mcdoc:attribute') {
|
|
333
|
+
// Formatted separately
|
|
334
|
+
return '';
|
|
335
|
+
}
|
|
336
|
+
const formatted = contentCtx.meta.getFormatter(child.type)(child, contentCtx);
|
|
337
|
+
if (child.type === 'mcdoc:identifier') {
|
|
338
|
+
return formatted + ' ';
|
|
339
|
+
}
|
|
340
|
+
if (child.type !== 'mcdoc:literal' || child.value === 'enum') {
|
|
341
|
+
return formatted;
|
|
342
|
+
}
|
|
343
|
+
// Add parentheses to type
|
|
344
|
+
return `(${formatted}) `;
|
|
345
|
+
}).join('');
|
|
346
|
+
});
|
|
347
|
+
};
|
|
348
|
+
const enumInjection = (node, ctx) => {
|
|
349
|
+
return node.children.map((child) => {
|
|
350
|
+
if (child.type === 'comment') {
|
|
351
|
+
// Don't format comments if the type doesn't allow them.
|
|
352
|
+
// A parent type that does allow comments should have already included them.
|
|
353
|
+
return '';
|
|
354
|
+
}
|
|
355
|
+
const formatted = ctx.meta.getFormatter(child.type)(child, ctx);
|
|
356
|
+
if (child.type === 'mcdoc:path') {
|
|
357
|
+
return formatted + ' ';
|
|
358
|
+
}
|
|
359
|
+
if (child.type !== 'mcdoc:literal' || child.value === 'enum') {
|
|
360
|
+
return formatted;
|
|
361
|
+
}
|
|
362
|
+
// Add parentheses to type
|
|
363
|
+
return `(${formatted}) `;
|
|
364
|
+
}).join('');
|
|
365
|
+
};
|
|
366
|
+
const enumBlock = (node, ctx) => {
|
|
367
|
+
if (node.children.length === 0) {
|
|
368
|
+
return '{}';
|
|
369
|
+
}
|
|
370
|
+
const content = formatChildren(node, ctx, {
|
|
371
|
+
'comment': { prefix: ctx.indent(1), suffix: '\n', indentSelf: true },
|
|
372
|
+
'mcdoc:enum/field': { prefix: ctx.indent(1), suffix: ',\n', indentSelf: true },
|
|
373
|
+
});
|
|
374
|
+
return `{\n${content}${ctx.indent()}}`;
|
|
375
|
+
};
|
|
376
|
+
const enumField = (node, ctx) => {
|
|
377
|
+
return formatWithPrelim(node, ctx, false, true, (contentCtx) => {
|
|
378
|
+
return formatChildren(node, contentCtx, {
|
|
379
|
+
'mcdoc:identifier': { suffix: ' = ' },
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
};
|
|
383
|
+
const tupleType = (node, ctx) => {
|
|
384
|
+
const typeNode = getTypeNodes(node.children);
|
|
385
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
386
|
+
return formatDynamicMultiline(node, (doMultiline) => {
|
|
387
|
+
const childFormatInfo = {
|
|
388
|
+
'comment': {
|
|
389
|
+
prefix: contentCtx.indent(1),
|
|
390
|
+
suffix: `\n`,
|
|
391
|
+
},
|
|
392
|
+
};
|
|
393
|
+
const typeFormatInfo = {
|
|
394
|
+
prefix: doMultiline ? contentCtx.indent(1) : '',
|
|
395
|
+
suffix: ',' + (doMultiline ? `\n` : ' '),
|
|
396
|
+
indentSelf: true,
|
|
397
|
+
};
|
|
398
|
+
for (const child of typeNode) {
|
|
399
|
+
childFormatInfo[child.type] = typeFormatInfo;
|
|
400
|
+
}
|
|
401
|
+
const content = formatChildren(node, contentCtx, childFormatInfo, !doMultiline);
|
|
402
|
+
return doMultiline ? `[\n${content}${contentCtx.indent()}]` : `[${content}]`;
|
|
403
|
+
});
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
const unionType = (node, ctx) => {
|
|
407
|
+
const typeNode = getTypeNodes(node.children);
|
|
408
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
409
|
+
return formatDynamicMultiline(node, (doMultiline) => {
|
|
410
|
+
const childFormatInfo = {
|
|
411
|
+
'comment': {
|
|
412
|
+
prefix: contentCtx.indent(1),
|
|
413
|
+
suffix: `\n`,
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
const typeFormatInfo = {
|
|
417
|
+
prefix: doMultiline ? contentCtx.indent(1) : '',
|
|
418
|
+
suffix: ' |' + (doMultiline ? `\n` : ' '),
|
|
419
|
+
indentSelf: true,
|
|
420
|
+
};
|
|
421
|
+
for (const child of typeNode) {
|
|
422
|
+
childFormatInfo[child.type] = typeFormatInfo;
|
|
423
|
+
}
|
|
424
|
+
const content = formatChildren(node, contentCtx, childFormatInfo, !doMultiline);
|
|
425
|
+
return doMultiline ? `(\n${content}${contentCtx.indent()})` : `(${content})`;
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
};
|
|
429
|
+
const referenceType = (node, ctx) => {
|
|
430
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
431
|
+
return formatChildren(node, contentCtx, {});
|
|
432
|
+
});
|
|
433
|
+
};
|
|
434
|
+
const path = (node, ctx) => {
|
|
435
|
+
const formatted = formatChildren(node, ctx, {
|
|
436
|
+
'mcdoc:literal': { prefix: '::' },
|
|
437
|
+
'mcdoc:identifier': { prefix: '::' },
|
|
438
|
+
});
|
|
439
|
+
if (!node.isAbsolute) {
|
|
440
|
+
return formatted.substring(2); // Remove the leading '::' for relative paths
|
|
441
|
+
}
|
|
442
|
+
return formatted;
|
|
443
|
+
};
|
|
444
|
+
const stringType = (node, ctx) => {
|
|
445
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
446
|
+
return formatChildren(node, contentCtx, {
|
|
447
|
+
'mcdoc:int_range': { prefix: ' @ ' },
|
|
448
|
+
});
|
|
449
|
+
});
|
|
450
|
+
};
|
|
451
|
+
const primitiveArrayType = (node, ctx) => {
|
|
452
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
453
|
+
return formatChildren(node, contentCtx, {
|
|
454
|
+
'mcdoc:int_range': { prefix: ' @ ' },
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
};
|
|
458
|
+
const listType = (node, ctx) => {
|
|
459
|
+
const typeNode = getTypeNodes(node.children)[0];
|
|
460
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
461
|
+
return formatChildren(node, contentCtx, {
|
|
462
|
+
[typeNode.type]: { prefix: '[', suffix: ']' },
|
|
463
|
+
'mcdoc:int_range': { prefix: ' @ ' },
|
|
464
|
+
});
|
|
465
|
+
});
|
|
466
|
+
};
|
|
467
|
+
const typeAlias = (node, ctx) => {
|
|
468
|
+
const typeNode = getTypeNodes(node.children)[0];
|
|
469
|
+
return formatWithPrelim(node, ctx, true, true, (contentCtx) => {
|
|
470
|
+
return formatChildren(node, contentCtx, {
|
|
471
|
+
[typeNode.type]: { prefix: ' = ' },
|
|
472
|
+
'mcdoc:literal': { suffix: ' ' },
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
};
|
|
476
|
+
const dispatcherType = (node, ctx) => {
|
|
477
|
+
return formatWithPrelim(node, ctx, true, true, (contentCtx) => {
|
|
478
|
+
return formatChildren(node, contentCtx, {});
|
|
479
|
+
});
|
|
480
|
+
};
|
|
481
|
+
const dispatchStatement = (node, ctx) => {
|
|
482
|
+
return formatWithPrelim(node, ctx, true, true, (contentCtx) => {
|
|
483
|
+
return formatChildren(node, contentCtx, {
|
|
484
|
+
'mcdoc:literal': { suffix: ' ' },
|
|
485
|
+
'mcdoc:index_body': { suffix: ' ' },
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
};
|
|
489
|
+
const indexBody = (node, ctx) => {
|
|
490
|
+
return formatDynamicMultiline(node, (doMultiline) => {
|
|
491
|
+
const indexFormatInfo = {
|
|
492
|
+
prefix: doMultiline ? ctx.indent(1) : '',
|
|
493
|
+
suffix: ',' + (doMultiline ? `\n` : ' '),
|
|
494
|
+
indentSelf: true,
|
|
495
|
+
};
|
|
496
|
+
const content = formatChildren(node, ctx, {
|
|
497
|
+
'mcdoc:dynamic_index': indexFormatInfo,
|
|
498
|
+
'mcdoc:identifier': indexFormatInfo,
|
|
499
|
+
'mcdoc:literal': indexFormatInfo,
|
|
500
|
+
'resource_location': indexFormatInfo,
|
|
501
|
+
'string': indexFormatInfo,
|
|
502
|
+
}, !doMultiline);
|
|
503
|
+
return doMultiline ? `[\n${content}${ctx.indent()}]` : `[${content}]`;
|
|
504
|
+
});
|
|
505
|
+
};
|
|
506
|
+
const dynamicIndex = (node, ctx) => {
|
|
507
|
+
const path = node.children.map((child) => {
|
|
508
|
+
if (child.type === 'comment') {
|
|
509
|
+
// Don't format comments if the type doesn't allow them.
|
|
510
|
+
// A parent type that does allow comments should have already included them.
|
|
511
|
+
return '';
|
|
512
|
+
}
|
|
513
|
+
return ctx.meta.getFormatter(child.type)(child, ctx);
|
|
514
|
+
}).join('.');
|
|
515
|
+
return `[${path}]`;
|
|
516
|
+
};
|
|
517
|
+
const attribute = (node, ctx) => {
|
|
518
|
+
const hasTypeValue = getTypeNodes(node.children).length !== 0;
|
|
519
|
+
return formatChildren(node, ctx, {
|
|
520
|
+
'mcdoc:identifier': { prefix: '#[', suffix: hasTypeValue ? '=' : '' },
|
|
521
|
+
}) + ']';
|
|
522
|
+
};
|
|
523
|
+
const attributeTree = (node, ctx) => {
|
|
524
|
+
const content = formatChildren(node, ctx, {});
|
|
525
|
+
return `${node.delim}${content}${AttributeTreeClosure[node.delim]}`;
|
|
526
|
+
};
|
|
527
|
+
const attributeTreePosValues = (node, ctx) => {
|
|
528
|
+
const typeNode = getTypeNodes(node.children);
|
|
529
|
+
return formatDynamicMultiline(node, (doMultiline) => {
|
|
530
|
+
const childFormatInfo = {
|
|
531
|
+
prefix: doMultiline ? ctx.indent(1) : '',
|
|
532
|
+
suffix: ',' + (doMultiline ? `\n` : ' '),
|
|
533
|
+
indentSelf: true,
|
|
534
|
+
};
|
|
535
|
+
const childFormatInfoMap = {
|
|
536
|
+
'mcdoc:attribute/tree': childFormatInfo,
|
|
537
|
+
};
|
|
538
|
+
for (const child of typeNode) {
|
|
539
|
+
childFormatInfoMap[child.type] = childFormatInfo;
|
|
540
|
+
}
|
|
541
|
+
const content = formatChildren(node, ctx, childFormatInfoMap, !doMultiline);
|
|
542
|
+
return doMultiline ? `\n${content}${ctx.indent()}` : content;
|
|
543
|
+
});
|
|
544
|
+
};
|
|
545
|
+
const attributeTreeNamedValues = (node, ctx) => {
|
|
546
|
+
const typeNode = getTypeNodes(node.children);
|
|
547
|
+
return formatDynamicMultiline(node, (doMultiline) => {
|
|
548
|
+
const childFormatInfo = {
|
|
549
|
+
prefix: '=',
|
|
550
|
+
suffix: ',' + (doMultiline ? `\n` : ' '),
|
|
551
|
+
indentSelf: true,
|
|
552
|
+
};
|
|
553
|
+
const childFormatInfoMap = {
|
|
554
|
+
'mcdoc:attribute/tree': childFormatInfo,
|
|
555
|
+
'mcdoc:identifier': { prefix: doMultiline ? ctx.indent(1) : '' },
|
|
556
|
+
'string': { prefix: doMultiline ? ctx.indent(1) : '' },
|
|
557
|
+
};
|
|
558
|
+
for (const child of typeNode) {
|
|
559
|
+
childFormatInfoMap[child.type] = childFormatInfo;
|
|
560
|
+
}
|
|
561
|
+
const content = formatChildren(node, ctx, childFormatInfoMap, !doMultiline);
|
|
562
|
+
return doMultiline ? `\n${content}${ctx.indent()}` : content;
|
|
563
|
+
});
|
|
564
|
+
};
|
|
565
|
+
const typeArgBlock = (node, ctx) => {
|
|
566
|
+
const typeNode = getTypeNodes(node.children);
|
|
567
|
+
const childFormatInfo = {};
|
|
568
|
+
for (const child of typeNode) {
|
|
569
|
+
childFormatInfo[child.type] = { suffix: ', ' };
|
|
570
|
+
}
|
|
571
|
+
const content = formatChildren(node, ctx, childFormatInfo, true);
|
|
572
|
+
return `<${content}>`;
|
|
573
|
+
};
|
|
574
|
+
const typeParamBlock = (node, ctx) => {
|
|
575
|
+
const content = formatChildren(node, ctx, {
|
|
576
|
+
'mcdoc:type_param': { suffix: ', ' },
|
|
577
|
+
}, true);
|
|
578
|
+
return `<${content}>`;
|
|
579
|
+
};
|
|
580
|
+
const typeParam = (node, ctx) => {
|
|
581
|
+
return formatChildren(node, ctx, {});
|
|
582
|
+
};
|
|
583
|
+
const literalType = (node, ctx) => {
|
|
584
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
585
|
+
return formatChildren(node, contentCtx, {});
|
|
586
|
+
});
|
|
587
|
+
};
|
|
588
|
+
const typedNumber = (node, ctx) => {
|
|
589
|
+
return formatChildren(node, ctx, {});
|
|
590
|
+
};
|
|
591
|
+
const numericType = (node, ctx) => {
|
|
592
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
593
|
+
return formatChildren(node, contentCtx, {
|
|
594
|
+
'mcdoc:int_range': { prefix: ' @ ' },
|
|
595
|
+
'mcdoc:float_range': { prefix: ' @ ' },
|
|
596
|
+
});
|
|
597
|
+
});
|
|
598
|
+
};
|
|
599
|
+
const intRange = (node, ctx) => {
|
|
600
|
+
return formatChildren(node, ctx, {});
|
|
601
|
+
};
|
|
602
|
+
const floatRange = (node, ctx) => {
|
|
603
|
+
return formatChildren(node, ctx, {});
|
|
604
|
+
};
|
|
605
|
+
const anyType = (node, ctx) => {
|
|
606
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
607
|
+
return formatChildren(node, contentCtx, {});
|
|
608
|
+
});
|
|
609
|
+
};
|
|
610
|
+
const booleanType = (node, ctx) => {
|
|
611
|
+
return formatWithPrelim(node, ctx, false, false, (contentCtx) => {
|
|
612
|
+
return formatChildren(node, contentCtx, {});
|
|
613
|
+
});
|
|
614
|
+
};
|
|
615
|
+
const literal = (node) => {
|
|
616
|
+
return node.value;
|
|
617
|
+
};
|
|
618
|
+
const identifier = (node) => {
|
|
619
|
+
return node.value;
|
|
620
|
+
};
|
|
621
|
+
const docComments = (node, ctx) => {
|
|
622
|
+
if (node.children.length === 0) {
|
|
623
|
+
return '';
|
|
624
|
+
}
|
|
625
|
+
return formatChildren(node, ctx, {
|
|
626
|
+
comment: { suffix: ctx.indent() }, // No need to add new lines, because DocCommentNodes include them
|
|
627
|
+
});
|
|
628
|
+
};
|
|
629
|
+
export function registerMcdocFormatter(meta) {
|
|
630
|
+
meta.registerFormatter('mcdoc:module', module);
|
|
631
|
+
meta.registerFormatter('mcdoc:use_statement', useStatement);
|
|
632
|
+
meta.registerFormatter('mcdoc:injection', injection);
|
|
633
|
+
meta.registerFormatter('mcdoc:struct', struct);
|
|
634
|
+
meta.registerFormatter('mcdoc:injection/struct', structInjection);
|
|
635
|
+
meta.registerFormatter('mcdoc:struct/block', structBlock);
|
|
636
|
+
meta.registerFormatter('mcdoc:struct/field/pair', structPairField);
|
|
637
|
+
meta.registerFormatter('mcdoc:struct/map_key', structMapKey);
|
|
638
|
+
meta.registerFormatter('mcdoc:struct/field/spread', structSpreadField);
|
|
639
|
+
meta.registerFormatter('mcdoc:enum', _enum);
|
|
640
|
+
meta.registerFormatter('mcdoc:injection/enum', enumInjection);
|
|
641
|
+
meta.registerFormatter('mcdoc:enum/block', enumBlock);
|
|
642
|
+
meta.registerFormatter('mcdoc:enum/field', enumField);
|
|
643
|
+
meta.registerFormatter('mcdoc:type/tuple', tupleType);
|
|
644
|
+
meta.registerFormatter('mcdoc:type/union', unionType);
|
|
645
|
+
meta.registerFormatter('mcdoc:type/reference', referenceType);
|
|
646
|
+
meta.registerFormatter('mcdoc:path', path);
|
|
647
|
+
meta.registerFormatter('mcdoc:type/string', stringType);
|
|
648
|
+
meta.registerFormatter('mcdoc:type/primitive_array', primitiveArrayType);
|
|
649
|
+
meta.registerFormatter('mcdoc:type/list', listType);
|
|
650
|
+
meta.registerFormatter('mcdoc:type_alias', typeAlias);
|
|
651
|
+
meta.registerFormatter('mcdoc:type/dispatcher', dispatcherType);
|
|
652
|
+
meta.registerFormatter('mcdoc:dispatch_statement', dispatchStatement);
|
|
653
|
+
meta.registerFormatter('mcdoc:index_body', indexBody);
|
|
654
|
+
meta.registerFormatter('mcdoc:dynamic_index', dynamicIndex);
|
|
655
|
+
meta.registerFormatter('mcdoc:attribute', attribute);
|
|
656
|
+
meta.registerFormatter('mcdoc:attribute/tree', attributeTree);
|
|
657
|
+
meta.registerFormatter('mcdoc:attribute/tree/pos', attributeTreePosValues);
|
|
658
|
+
meta.registerFormatter('mcdoc:attribute/tree/named', attributeTreeNamedValues);
|
|
659
|
+
meta.registerFormatter('mcdoc:type_arg_block', typeArgBlock);
|
|
660
|
+
meta.registerFormatter('mcdoc:type_param_block', typeParamBlock);
|
|
661
|
+
meta.registerFormatter('mcdoc:type_param', typeParam);
|
|
662
|
+
meta.registerFormatter('mcdoc:type/literal', literalType);
|
|
663
|
+
meta.registerFormatter('mcdoc:typed_number', typedNumber);
|
|
664
|
+
meta.registerFormatter('mcdoc:type/numeric_type', numericType);
|
|
665
|
+
meta.registerFormatter('mcdoc:int_range', intRange);
|
|
666
|
+
meta.registerFormatter('mcdoc:float_range', floatRange);
|
|
667
|
+
meta.registerFormatter('mcdoc:type/any', anyType);
|
|
668
|
+
meta.registerFormatter('mcdoc:type/boolean', booleanType);
|
|
669
|
+
meta.registerFormatter('mcdoc:literal', literal);
|
|
670
|
+
meta.registerFormatter('mcdoc:identifier', identifier);
|
|
671
|
+
meta.registerFormatter('mcdoc:doc_comments', docComments);
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as binder from './binder/index.js';
|
|
2
2
|
import * as colorizer from './colorizer/index.js';
|
|
3
|
+
import * as formatter from './formatter/index.js';
|
|
3
4
|
import * as parser from './parser/index.js';
|
|
4
5
|
import { registerBuiltinAttributes } from './runtime/attribute/builtin.js';
|
|
5
6
|
import * as uri_professors from './uri_processors.js';
|
|
@@ -18,5 +19,6 @@ export const initialize = ({ meta }) => {
|
|
|
18
19
|
meta.setUriSorter(uri_professors.uriSorter);
|
|
19
20
|
binder.registerMcdocBinders(meta);
|
|
20
21
|
colorizer.registerMcdocColorizer(meta);
|
|
22
|
+
formatter.registerMcdocFormatter(meta);
|
|
21
23
|
};
|
|
22
24
|
//# sourceMappingURL=index.js.map
|
package/lib/parser/index.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ export declare function resLoc(options: ResourceLocationOptions): InfalliblePars
|
|
|
16
16
|
export declare const string: InfallibleParser<StringNode>;
|
|
17
17
|
export declare const identifier: InfallibleParser<IdentifierNode>;
|
|
18
18
|
export declare const path: InfallibleParser<PathNode>;
|
|
19
|
+
export declare const AttributeTreeClosure: Readonly<{
|
|
20
|
+
'(': ")";
|
|
21
|
+
'[': "]";
|
|
22
|
+
'{': "}";
|
|
23
|
+
}>;
|
|
19
24
|
export declare const attribute: Parser<AttributeNode>;
|
|
20
25
|
export declare const docComment: Parser<CommentNode>;
|
|
21
26
|
export declare const docComments: InfallibleParser<DocCommentsNode>;
|
package/lib/parser/index.js
CHANGED
|
@@ -232,7 +232,7 @@ const treeBody = any([
|
|
|
232
232
|
]),
|
|
233
233
|
syntax([attributeTreePosValues, optional(marker(','))]),
|
|
234
234
|
]);
|
|
235
|
-
const AttributeTreeClosure = Object.freeze({ '(': ')', '[': ']', '{': '}' });
|
|
235
|
+
export const AttributeTreeClosure = Object.freeze({ '(': ')', '[': ']', '{': '}' });
|
|
236
236
|
const attributeTree = (src, ctx) => {
|
|
237
237
|
const delim = src.trySkip('(')
|
|
238
238
|
? '('
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@spyglassmc/mcdoc",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.36",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"url": "https://github.com/SpyglassMC/Spyglass/issues"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@spyglassmc/core": "0.4.
|
|
29
|
-
"@spyglassmc/locales": "0.3.
|
|
28
|
+
"@spyglassmc/core": "0.4.32",
|
|
29
|
+
"@spyglassmc/locales": "0.3.17"
|
|
30
30
|
}
|
|
31
31
|
}
|