@takazudo/mdx-formatter 0.4.1 → 0.4.3
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/dist/hybrid-formatter.d.ts +14 -0
- package/dist/hybrid-formatter.js +151 -61
- package/dist/index.js +13 -2
- package/package.json +1 -1
|
@@ -11,10 +11,19 @@ export declare class HybridFormatter {
|
|
|
11
11
|
private ast;
|
|
12
12
|
private positionMap;
|
|
13
13
|
private indentDetector;
|
|
14
|
+
private readonly htmlFormatter;
|
|
14
15
|
constructor(content: string, settings?: FormatterSettings | null);
|
|
15
16
|
parseAST(content: string): Root;
|
|
16
17
|
fixStandaloneClosingTags(content: string): string;
|
|
17
18
|
buildPositionMap(): PositionMapEntry[];
|
|
19
|
+
/**
|
|
20
|
+
* Get the list of components to ignore during formatting.
|
|
21
|
+
*/
|
|
22
|
+
private get ignoreComponents();
|
|
23
|
+
/**
|
|
24
|
+
* Check if a JSX node should be processed (correct type, has position, not HTML, not ignored).
|
|
25
|
+
*/
|
|
26
|
+
private isFormattableJsxNode;
|
|
18
27
|
/**
|
|
19
28
|
* Detect indentation from content and update formatter settings
|
|
20
29
|
*/
|
|
@@ -64,6 +73,11 @@ export declare class HybridFormatter {
|
|
|
64
73
|
*/
|
|
65
74
|
preprocessYamlForParsing(yamlText: string): string;
|
|
66
75
|
collectYamlFormatOperations(operations: FormatterOperation[]): void;
|
|
76
|
+
/**
|
|
77
|
+
* Remove replaceLines/replaceHtmlBlock operations that are strictly contained
|
|
78
|
+
* within a wider replacement range (parent wins over child). Mutates the array in place.
|
|
79
|
+
*/
|
|
80
|
+
private filterOverlappingReplacements;
|
|
67
81
|
getLineAtPosition(charPos: number): number;
|
|
68
82
|
applyOperation(lines: string[], op: FormatterOperation): void;
|
|
69
83
|
}
|
package/dist/hybrid-formatter.js
CHANGED
|
@@ -20,12 +20,14 @@ export class HybridFormatter {
|
|
|
20
20
|
ast;
|
|
21
21
|
positionMap;
|
|
22
22
|
indentDetector;
|
|
23
|
+
htmlFormatter;
|
|
23
24
|
constructor(content, settings = null) {
|
|
24
25
|
this.originalContent = content;
|
|
25
26
|
this.content = content; // Use content directly without preprocessing
|
|
26
27
|
this.lines = this.content.split('\n');
|
|
27
28
|
this.settings = settings ? deepCloneSettings(settings) : deepCloneSettings(formatterSettings);
|
|
28
29
|
this.indentDetector = null;
|
|
30
|
+
this.htmlFormatter = new HtmlBlockFormatter(this.settings.formatHtmlBlocksInMdx || {});
|
|
29
31
|
// Auto-detect indentation if enabled
|
|
30
32
|
if (this.settings.autoDetectIndent && this.settings.autoDetectIndent.enabled) {
|
|
31
33
|
this.detectAndApplyIndentation();
|
|
@@ -96,6 +98,29 @@ export class HybridFormatter {
|
|
|
96
98
|
}
|
|
97
99
|
return map;
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Get the list of components to ignore during formatting.
|
|
103
|
+
*/
|
|
104
|
+
get ignoreComponents() {
|
|
105
|
+
return this.settings.formatMultiLineJsx.ignoreComponents || [];
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Check if a JSX node should be processed (correct type, has position, not HTML, not ignored).
|
|
109
|
+
*/
|
|
110
|
+
isFormattableJsxNode(node) {
|
|
111
|
+
if ((node.type !== 'mdxJsxFlowElement' && node.type !== 'mdxJsxTextElement') ||
|
|
112
|
+
!node.position) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
const jsxNode = node;
|
|
116
|
+
if (jsxNode.name && this.htmlFormatter.isHtmlElement(jsxNode.name)) {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
if (jsxNode.name && this.ignoreComponents.includes(jsxNode.name)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
99
124
|
/**
|
|
100
125
|
* Detect indentation from content and update formatter settings
|
|
101
126
|
*/
|
|
@@ -185,9 +210,34 @@ export class HybridFormatter {
|
|
|
185
210
|
if (this.settings.formatHtmlBlocksInMdx && this.settings.formatHtmlBlocksInMdx.enabled) {
|
|
186
211
|
await this.collectHtmlBlockOperations(operations);
|
|
187
212
|
}
|
|
213
|
+
// When parent and child JSX elements both produce replaceLines operations
|
|
214
|
+
// with overlapping ranges, keep only the wider range (parent).
|
|
215
|
+
this.filterOverlappingReplacements(operations);
|
|
216
|
+
// Collect line ranges covered by replaceLines/replaceHtmlBlock operations.
|
|
217
|
+
// Other operations (insertLine, indentLine) that fall within these ranges
|
|
218
|
+
// must be dropped to prevent duplication — the replacement already rewrites
|
|
219
|
+
// the entire range.
|
|
220
|
+
const replacedRanges = [];
|
|
221
|
+
for (const op of operations) {
|
|
222
|
+
if ((op.type === 'replaceLines' || op.type === 'replaceHtmlBlock') && 'endLine' in op) {
|
|
223
|
+
replacedRanges.push([op.startLine, op.endLine]);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const isInsideReplacedRange = (line) => {
|
|
227
|
+
return replacedRanges.some(([start, end]) => line >= start && line <= end);
|
|
228
|
+
};
|
|
229
|
+
// Filter out operations that conflict with replaceLines ranges
|
|
230
|
+
const filteredOperations = operations.filter((op) => {
|
|
231
|
+
if (op.type === 'replaceLines' || op.type === 'replaceHtmlBlock') {
|
|
232
|
+
return true; // Always keep replacement operations
|
|
233
|
+
}
|
|
234
|
+
// Drop insertLine / indentLine / fixListIndent if they target a line
|
|
235
|
+
// inside a range that will be completely replaced
|
|
236
|
+
return !isInsideReplacedRange(op.startLine);
|
|
237
|
+
});
|
|
188
238
|
// Sort operations by position (reverse order to preserve positions)
|
|
189
239
|
// Also sort by operation type to ensure replacements happen before insertions at the same line
|
|
190
|
-
|
|
240
|
+
filteredOperations.sort((a, b) => {
|
|
191
241
|
if (b.startLine !== a.startLine) {
|
|
192
242
|
return b.startLine - a.startLine;
|
|
193
243
|
}
|
|
@@ -204,7 +254,7 @@ export class HybridFormatter {
|
|
|
204
254
|
// Apply operations to lines with deduplication
|
|
205
255
|
const resultLines = [...this.lines];
|
|
206
256
|
const appliedOperations = new Set();
|
|
207
|
-
for (const op of
|
|
257
|
+
for (const op of filteredOperations) {
|
|
208
258
|
// Create a unique key for this operation
|
|
209
259
|
const endLine = 'endLine' in op ? op.endLine : op.startLine;
|
|
210
260
|
const opKey = `${op.type}-${op.startLine}-${endLine}`;
|
|
@@ -220,8 +270,6 @@ export class HybridFormatter {
|
|
|
220
270
|
return result.replace(/\n{3,}/g, '\n\n');
|
|
221
271
|
}
|
|
222
272
|
collectSpacingOperations(operations) {
|
|
223
|
-
// Initialize HTML formatter to check if elements are HTML
|
|
224
|
-
const htmlFormatter = new HtmlBlockFormatter(this.settings.formatHtmlBlocksInMdx || {});
|
|
225
273
|
visit(this.ast, (node) => {
|
|
226
274
|
// Add spacing after headings
|
|
227
275
|
if (node.type === 'heading' && node.position) {
|
|
@@ -239,13 +287,8 @@ export class HybridFormatter {
|
|
|
239
287
|
}
|
|
240
288
|
}
|
|
241
289
|
// FIXED: Add spacing after JSX components when followed by text or another JSX component
|
|
242
|
-
if ((node
|
|
243
|
-
node.position) {
|
|
290
|
+
if (this.isFormattableJsxNode(node)) {
|
|
244
291
|
const jsxNode = node;
|
|
245
|
-
// Skip HTML elements - they will be handled by HTML formatter
|
|
246
|
-
if (jsxNode.name && htmlFormatter.isHtmlElement(jsxNode.name)) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
292
|
const endLine = jsxNode.position.end.line - 1;
|
|
250
293
|
// Skip JSX elements inside table rows
|
|
251
294
|
const currentLineContent = this.lines[endLine];
|
|
@@ -364,21 +407,9 @@ export class HybridFormatter {
|
|
|
364
407
|
});
|
|
365
408
|
}
|
|
366
409
|
collectJsxFormatOperations(operations) {
|
|
367
|
-
// Initialize HTML formatter to check if elements are HTML
|
|
368
|
-
const htmlFormatter = new HtmlBlockFormatter(this.settings.formatHtmlBlocksInMdx || {});
|
|
369
410
|
visit(this.ast, (node) => {
|
|
370
|
-
if ((node
|
|
371
|
-
node.position) {
|
|
411
|
+
if (this.isFormattableJsxNode(node)) {
|
|
372
412
|
const jsxNode = node;
|
|
373
|
-
// Skip HTML elements - they will be handled by HTML formatter
|
|
374
|
-
if (jsxNode.name && htmlFormatter.isHtmlElement(jsxNode.name)) {
|
|
375
|
-
return;
|
|
376
|
-
}
|
|
377
|
-
// Skip components in the ignore list
|
|
378
|
-
const ignoreComponents = this.settings.formatMultiLineJsx.ignoreComponents || [];
|
|
379
|
-
if (jsxNode.name && ignoreComponents.includes(jsxNode.name)) {
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
413
|
const startLine = jsxNode.position.start.line - 1;
|
|
383
414
|
const endLine = jsxNode.position.end.line - 1;
|
|
384
415
|
// Extract the original JSX text
|
|
@@ -400,8 +431,7 @@ export class HybridFormatter {
|
|
|
400
431
|
}
|
|
401
432
|
needsJsxFormatting(node, originalText) {
|
|
402
433
|
// Check if component is in ignore list
|
|
403
|
-
|
|
404
|
-
if (node.name && ignoreComponents.includes(node.name)) {
|
|
434
|
+
if (node.name && this.ignoreComponents.includes(node.name)) {
|
|
405
435
|
return false;
|
|
406
436
|
}
|
|
407
437
|
const attributes = node.attributes || [];
|
|
@@ -420,6 +450,30 @@ export class HybridFormatter {
|
|
|
420
450
|
const expectedIndent = this.indentDetector
|
|
421
451
|
? this.indentDetector.getIndentString()
|
|
422
452
|
: ' '.repeat(this.settings.formatMultiLineJsx.indentSize || 2);
|
|
453
|
+
// Determine where the opening tag ends so we only check attribute lines.
|
|
454
|
+
// For self-closing elements the opening tag ends at /> or the last line.
|
|
455
|
+
// For non-self-closing elements the opening tag ends at the first line
|
|
456
|
+
// containing a bare > (not />).
|
|
457
|
+
let openingTagEndLine = lines.length - 1;
|
|
458
|
+
const hasClosingTag = originalText.includes(`</${node.name}>`);
|
|
459
|
+
if (hasClosingTag) {
|
|
460
|
+
let braceDepth = 0;
|
|
461
|
+
for (let i = 0; i < lines.length; i++) {
|
|
462
|
+
const line = lines[i];
|
|
463
|
+
// Track brace depth to avoid matching > inside expressions like {a > b}
|
|
464
|
+
for (const ch of line) {
|
|
465
|
+
if (ch === '{')
|
|
466
|
+
braceDepth++;
|
|
467
|
+
if (ch === '}')
|
|
468
|
+
braceDepth--;
|
|
469
|
+
}
|
|
470
|
+
const trimmed = line.trim();
|
|
471
|
+
if (braceDepth === 0 && trimmed.endsWith('>') && !trimmed.endsWith('/>')) {
|
|
472
|
+
openingTagEndLine = i;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
423
477
|
// Check for attributes split across lines incorrectly
|
|
424
478
|
// Like: <ExImg src="..." className="..."
|
|
425
479
|
// alt="..." />
|
|
@@ -428,24 +482,20 @@ export class HybridFormatter {
|
|
|
428
482
|
// Has attributes on first line but doesn't close
|
|
429
483
|
return true;
|
|
430
484
|
}
|
|
431
|
-
//
|
|
432
|
-
for (let i = 1; i
|
|
485
|
+
// Only check lines within the opening tag (not children content)
|
|
486
|
+
for (let i = 1; i <= openingTagEndLine; i++) {
|
|
433
487
|
const trimmed = lines[i].trim();
|
|
488
|
+
// Check if /> is on its own line (this is always incorrect)
|
|
434
489
|
if (trimmed === '/>') {
|
|
435
|
-
// /> should never be on its own line in JSX/MDX
|
|
436
490
|
return true;
|
|
437
491
|
}
|
|
438
|
-
}
|
|
439
|
-
// Check indentation on subsequent lines
|
|
440
|
-
for (let i = 1; i < lines.length; i++) {
|
|
441
|
-
const line = lines[i];
|
|
442
|
-
const trimmed = line.trim();
|
|
443
492
|
// Skip empty lines or closing tag
|
|
444
493
|
if (!trimmed || trimmed.startsWith(`</${node.name}`)) {
|
|
445
494
|
continue;
|
|
446
495
|
}
|
|
447
496
|
// Check proper indentation for attribute lines
|
|
448
497
|
// Attributes should be indented by exactly one indent level
|
|
498
|
+
const line = lines[i];
|
|
449
499
|
if (!line.startsWith(expectedIndent)) {
|
|
450
500
|
return true;
|
|
451
501
|
}
|
|
@@ -548,9 +598,19 @@ export class HybridFormatter {
|
|
|
548
598
|
}
|
|
549
599
|
// Add children content if not self-closing
|
|
550
600
|
if (!selfClosing) {
|
|
601
|
+
// Check if this is a block component that needs empty lines
|
|
602
|
+
const blockComponents = this.settings.addEmptyLinesInBlockJsx?.blockComponents || [];
|
|
603
|
+
const isBlockComponent = this.settings.addEmptyLinesInBlockJsx?.enabled !== false && blockComponents.includes(name);
|
|
551
604
|
// Extract children content from original
|
|
552
605
|
const childrenText = this.extractChildrenText(node, originalText);
|
|
553
606
|
if (childrenText) {
|
|
607
|
+
// Add empty line after opening tag for block components
|
|
608
|
+
if (isBlockComponent) {
|
|
609
|
+
const firstContentLine = childrenText.split('\n')[0];
|
|
610
|
+
if (firstContentLine && firstContentLine.trim() !== '') {
|
|
611
|
+
lines.push('');
|
|
612
|
+
}
|
|
613
|
+
}
|
|
554
614
|
// Check if this is a container component that needs indented content
|
|
555
615
|
const containerComponents = this.settings.indentJsxContent.containerComponents || [];
|
|
556
616
|
const isContainer = containerComponents.includes(name);
|
|
@@ -566,6 +626,13 @@ export class HybridFormatter {
|
|
|
566
626
|
else {
|
|
567
627
|
lines.push(...childrenText.split('\n'));
|
|
568
628
|
}
|
|
629
|
+
// Add empty line before closing tag for block components
|
|
630
|
+
if (isBlockComponent) {
|
|
631
|
+
const lastContentLine = lines[lines.length - 1];
|
|
632
|
+
if (lastContentLine && lastContentLine.trim() !== '') {
|
|
633
|
+
lines.push('');
|
|
634
|
+
}
|
|
635
|
+
}
|
|
569
636
|
}
|
|
570
637
|
// Closing tag
|
|
571
638
|
lines.push(`</${name}>`);
|
|
@@ -756,15 +823,13 @@ export class HybridFormatter {
|
|
|
756
823
|
* Collect HTML block formatting operations using HtmlBlockFormatter
|
|
757
824
|
*/
|
|
758
825
|
async collectHtmlBlockOperations(operations) {
|
|
759
|
-
// Initialize the HTML formatter
|
|
760
|
-
const htmlFormatter = new HtmlBlockFormatter(this.settings.formatHtmlBlocksInMdx);
|
|
761
826
|
// Collect all HTML nodes first, tracking parent-child relationships
|
|
762
827
|
const htmlNodes = [];
|
|
763
828
|
const processedRanges = [];
|
|
764
829
|
visit(this.ast, 'mdxJsxFlowElement', (node) => {
|
|
765
830
|
const jsxNode = node;
|
|
766
831
|
// Check if this is an HTML element (not a JSX component)
|
|
767
|
-
if (jsxNode.name && htmlFormatter.isHtmlElement(jsxNode.name)) {
|
|
832
|
+
if (jsxNode.name && this.htmlFormatter.isHtmlElement(jsxNode.name)) {
|
|
768
833
|
const startLine = jsxNode.position.start.line;
|
|
769
834
|
const endLine = jsxNode.position.end.line;
|
|
770
835
|
// Check if this node is within an already processed range
|
|
@@ -788,7 +853,7 @@ export class HybridFormatter {
|
|
|
788
853
|
const htmlContent = this.extractHtmlFromNode(node);
|
|
789
854
|
if (htmlContent) {
|
|
790
855
|
// Format just this HTML block using Prettier
|
|
791
|
-
const formatted = await htmlFormatter.formatWithPrettier(htmlContent);
|
|
856
|
+
const formatted = await this.htmlFormatter.formatWithPrettier(htmlContent);
|
|
792
857
|
// Only add operation if formatting changed the content
|
|
793
858
|
if (formatted !== htmlContent) {
|
|
794
859
|
operations.push({
|
|
@@ -818,18 +883,10 @@ export class HybridFormatter {
|
|
|
818
883
|
}
|
|
819
884
|
collectJsxIndentOperations(operations) {
|
|
820
885
|
const containerNames = this.settings.indentJsxContent.containerComponents || [];
|
|
821
|
-
// Initialize HTML formatter to check if elements are HTML
|
|
822
|
-
const htmlFormatter = new HtmlBlockFormatter(this.settings.formatHtmlBlocksInMdx || {});
|
|
823
|
-
// Get components to ignore
|
|
824
|
-
const ignoreComponents = this.settings.formatMultiLineJsx.ignoreComponents || [];
|
|
825
886
|
visit(this.ast, (node) => {
|
|
826
|
-
if (
|
|
887
|
+
if (this.isFormattableJsxNode(node)) {
|
|
827
888
|
const jsxNode = node;
|
|
828
|
-
if (containerNames.includes(jsxNode.name || '')
|
|
829
|
-
// Skip HTML elements - they are handled by HTML formatter
|
|
830
|
-
!htmlFormatter.isHtmlElement(jsxNode.name || '') &&
|
|
831
|
-
// Skip ignored components
|
|
832
|
-
!ignoreComponents.includes(jsxNode.name || '')) {
|
|
889
|
+
if (containerNames.includes(jsxNode.name || '')) {
|
|
833
890
|
const startLine = jsxNode.position.start.line - 1;
|
|
834
891
|
const endLine = jsxNode.position.end.line - 1;
|
|
835
892
|
// Check if content needs indentation
|
|
@@ -855,19 +912,10 @@ export class HybridFormatter {
|
|
|
855
912
|
}
|
|
856
913
|
collectBlockJsxEmptyLineOperations(operations) {
|
|
857
914
|
const blockComponents = this.settings.addEmptyLinesInBlockJsx.blockComponents || [];
|
|
858
|
-
// Initialize HTML formatter to check if elements are HTML
|
|
859
|
-
const htmlFormatter = new HtmlBlockFormatter(this.settings.formatHtmlBlocksInMdx || {});
|
|
860
|
-
// Get components to ignore
|
|
861
|
-
const ignoreComponents = this.settings.formatMultiLineJsx.ignoreComponents || [];
|
|
862
915
|
visit(this.ast, (node) => {
|
|
863
|
-
if ((node
|
|
864
|
-
node.position) {
|
|
916
|
+
if (this.isFormattableJsxNode(node)) {
|
|
865
917
|
const jsxNode = node;
|
|
866
|
-
if (blockComponents.includes(jsxNode.name || '')
|
|
867
|
-
// Skip HTML elements - they are handled by HTML formatter
|
|
868
|
-
!htmlFormatter.isHtmlElement(jsxNode.name || '') &&
|
|
869
|
-
// Skip ignored components
|
|
870
|
-
!ignoreComponents.includes(jsxNode.name || '')) {
|
|
918
|
+
if (blockComponents.includes(jsxNode.name || '')) {
|
|
871
919
|
const startLine = jsxNode.position.start.line - 1;
|
|
872
920
|
const endLine = jsxNode.position.end.line - 1;
|
|
873
921
|
// Handle single-line components
|
|
@@ -893,14 +941,24 @@ export class HybridFormatter {
|
|
|
893
941
|
}
|
|
894
942
|
return;
|
|
895
943
|
}
|
|
944
|
+
// Find the actual end of the opening tag (may span multiple lines
|
|
945
|
+
// for elements with attributes like <Danger\n title="..."\n>)
|
|
946
|
+
let openingTagEndLine = startLine;
|
|
947
|
+
for (let i = startLine; i <= endLine; i++) {
|
|
948
|
+
const trimmed = this.lines[i].trim();
|
|
949
|
+
if (trimmed.endsWith('>') && !trimmed.endsWith('/>') && !trimmed.startsWith('</')) {
|
|
950
|
+
openingTagEndLine = i;
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
896
954
|
// Check if there's an empty line after the opening tag
|
|
897
|
-
if (
|
|
898
|
-
const lineAfterOpening = this.lines[
|
|
955
|
+
if (openingTagEndLine + 1 < this.lines.length) {
|
|
956
|
+
const lineAfterOpening = this.lines[openingTagEndLine + 1];
|
|
899
957
|
if (lineAfterOpening.trim() !== '') {
|
|
900
958
|
// Add empty line after opening tag
|
|
901
959
|
operations.push({
|
|
902
960
|
type: 'insertLine',
|
|
903
|
-
startLine:
|
|
961
|
+
startLine: openingTagEndLine + 1,
|
|
904
962
|
content: '',
|
|
905
963
|
});
|
|
906
964
|
}
|
|
@@ -1019,6 +1077,38 @@ export class HybridFormatter {
|
|
|
1019
1077
|
}
|
|
1020
1078
|
});
|
|
1021
1079
|
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Remove replaceLines/replaceHtmlBlock operations that are strictly contained
|
|
1082
|
+
* within a wider replacement range (parent wins over child). Mutates the array in place.
|
|
1083
|
+
*/
|
|
1084
|
+
filterOverlappingReplacements(operations) {
|
|
1085
|
+
const replaceOps = [];
|
|
1086
|
+
for (const op of operations) {
|
|
1087
|
+
if ((op.type === 'replaceLines' || op.type === 'replaceHtmlBlock') && 'endLine' in op) {
|
|
1088
|
+
replaceOps.push(op);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
// Find ops that are strictly contained within another op's range
|
|
1092
|
+
const dropped = new Set();
|
|
1093
|
+
for (const inner of replaceOps) {
|
|
1094
|
+
for (const outer of replaceOps) {
|
|
1095
|
+
if (inner === outer)
|
|
1096
|
+
continue;
|
|
1097
|
+
if (inner.startLine >= outer.startLine &&
|
|
1098
|
+
inner.endLine <= outer.endLine &&
|
|
1099
|
+
(inner.startLine !== outer.startLine || inner.endLine !== outer.endLine)) {
|
|
1100
|
+
dropped.add(inner);
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
// Remove dropped operations in place
|
|
1106
|
+
for (let i = operations.length - 1; i >= 0; i--) {
|
|
1107
|
+
if (dropped.has(operations[i])) {
|
|
1108
|
+
operations.splice(i, 1);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1022
1112
|
getLineAtPosition(charPos) {
|
|
1023
1113
|
for (let i = 0; i < this.positionMap.length; i++) {
|
|
1024
1114
|
if (charPos >= this.positionMap[i].start && charPos <= this.positionMap[i].end) {
|
package/dist/index.js
CHANGED
|
@@ -19,8 +19,19 @@ export function detectMdx(content) {
|
|
|
19
19
|
export async function format(content, options = {}) {
|
|
20
20
|
try {
|
|
21
21
|
const settings = loadConfig(options);
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
let result = content;
|
|
23
|
+
// Some rule interactions (e.g., list indent normalization + addEmptyLinesInBlockJsx)
|
|
24
|
+
// may require multiple passes to converge. 3 iterations is sufficient for all known
|
|
25
|
+
// cases (most files converge in 1, edge cases in 2).
|
|
26
|
+
const MAX_ITERATIONS = 3;
|
|
27
|
+
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
28
|
+
const formatter = new HybridFormatter(result, settings);
|
|
29
|
+
const formatted = await formatter.format();
|
|
30
|
+
if (formatted === result)
|
|
31
|
+
break;
|
|
32
|
+
result = formatted;
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
24
35
|
}
|
|
25
36
|
catch {
|
|
26
37
|
// Silently return original content if formatting fails
|