@takazudo/mdx-formatter 0.2.0 → 0.4.0
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 +10 -0
- package/dist/hybrid-formatter.js +85 -7
- package/dist/settings.js +3 -0
- package/dist/types.d.ts +2 -0
- package/package.json +2 -1
|
@@ -29,6 +29,10 @@ export declare class HybridFormatter {
|
|
|
29
29
|
collectJsxFormatOperations(operations: FormatterOperation[]): void;
|
|
30
30
|
needsJsxFormatting(node: MdxJsxElement, originalText: string): boolean;
|
|
31
31
|
formatJsxElement(node: MdxJsxElement, originalText: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Check if template literal indentation should be preserved based on settings.
|
|
34
|
+
*/
|
|
35
|
+
shouldPreserveTemplateLiteral(): boolean;
|
|
32
36
|
getAttributeString(attr: MdxJsxAttribute, originalText: string): string;
|
|
33
37
|
extractAttributeExpression(attrName: string, originalText: string): string | null;
|
|
34
38
|
extractExpressionValue(expr: MdxJsxAttributeValueExpression): string;
|
|
@@ -53,6 +57,12 @@ export declare class HybridFormatter {
|
|
|
53
57
|
extractHtmlFromNode(node: MdxJsxElement): string | null;
|
|
54
58
|
collectJsxIndentOperations(operations: FormatterOperation[]): void;
|
|
55
59
|
collectBlockJsxEmptyLineOperations(operations: FormatterOperation[]): void;
|
|
60
|
+
/**
|
|
61
|
+
* Pre-process YAML text to fix values that would cause parsing failures
|
|
62
|
+
* or silent data corruption. Detects unquoted values containing special
|
|
63
|
+
* YAML characters and wraps them in double quotes.
|
|
64
|
+
*/
|
|
65
|
+
preprocessYamlForParsing(yamlText: string): string;
|
|
56
66
|
collectYamlFormatOperations(operations: FormatterOperation[]): void;
|
|
57
67
|
getLineAtPosition(charPos: number): number;
|
|
58
68
|
applyOperation(lines: string[], op: FormatterOperation): void;
|
package/dist/hybrid-formatter.js
CHANGED
|
@@ -499,15 +499,21 @@ export class HybridFormatter {
|
|
|
499
499
|
// Add each attribute on its own line with proper indent
|
|
500
500
|
for (const attr of attributes) {
|
|
501
501
|
const attrStr = this.getAttributeString(attr, originalText);
|
|
502
|
-
// Handle multi-line expression values (like arrays)
|
|
502
|
+
// Handle multi-line expression values (like arrays, template literals)
|
|
503
503
|
if (attrStr.includes('\n')) {
|
|
504
504
|
const attrLines = attrStr.split('\n');
|
|
505
505
|
lines.push(`${indent}${attrLines[0]}`);
|
|
506
|
+
// Check if this is a template literal expression (backtick string)
|
|
507
|
+
// Template literal content has meaningful indentation that must be preserved
|
|
508
|
+
const isTemplateLiteral = this.shouldPreserveTemplateLiteral() && attrLines[0].includes('={`');
|
|
506
509
|
// Add subsequent lines with additional indentation for expression content
|
|
507
510
|
for (let i = 1; i < attrLines.length; i++) {
|
|
508
|
-
// Check if this line is part of the expression or the closing
|
|
509
511
|
const line = attrLines[i];
|
|
510
|
-
if (
|
|
512
|
+
if (isTemplateLiteral) {
|
|
513
|
+
// Preserve original indentation inside template literals
|
|
514
|
+
lines.push(line);
|
|
515
|
+
}
|
|
516
|
+
else if (line.trim().endsWith(']}') || line.trim() === ']}') {
|
|
511
517
|
// Closing of array expression
|
|
512
518
|
lines.push(`${indent}${line.trim()}`);
|
|
513
519
|
}
|
|
@@ -561,6 +567,12 @@ export class HybridFormatter {
|
|
|
561
567
|
}
|
|
562
568
|
return lines.join('\n');
|
|
563
569
|
}
|
|
570
|
+
/**
|
|
571
|
+
* Check if template literal indentation should be preserved based on settings.
|
|
572
|
+
*/
|
|
573
|
+
shouldPreserveTemplateLiteral() {
|
|
574
|
+
return this.settings.formatMultiLineJsx.preserveTemplateLiteralIndent !== false;
|
|
575
|
+
}
|
|
564
576
|
getAttributeString(attr, originalText) {
|
|
565
577
|
if (!attr || !attr.name)
|
|
566
578
|
return '';
|
|
@@ -573,7 +585,20 @@ export class HybridFormatter {
|
|
|
573
585
|
else if (attr.value && attr.value.type === 'mdxJsxAttributeValueExpression') {
|
|
574
586
|
// Expression value
|
|
575
587
|
const exprValue = this.extractExpressionValue(attr.value);
|
|
576
|
-
|
|
588
|
+
// For template literals, prefer extracting from original text to preserve
|
|
589
|
+
// internal indentation (AST normalizes/strips leading whitespace)
|
|
590
|
+
if (this.shouldPreserveTemplateLiteral() &&
|
|
591
|
+
exprValue &&
|
|
592
|
+
exprValue.trimStart().startsWith('`')) {
|
|
593
|
+
const extracted = this.extractAttributeExpression(attr.name, originalText);
|
|
594
|
+
if (extracted) {
|
|
595
|
+
result = extracted;
|
|
596
|
+
}
|
|
597
|
+
else {
|
|
598
|
+
result += `={${exprValue}}`;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
else if (exprValue) {
|
|
577
602
|
result += `={${exprValue}}`;
|
|
578
603
|
}
|
|
579
604
|
else {
|
|
@@ -893,16 +918,69 @@ export class HybridFormatter {
|
|
|
893
918
|
}
|
|
894
919
|
});
|
|
895
920
|
}
|
|
921
|
+
/**
|
|
922
|
+
* Pre-process YAML text to fix values that would cause parsing failures
|
|
923
|
+
* or silent data corruption. Detects unquoted values containing special
|
|
924
|
+
* YAML characters and wraps them in double quotes.
|
|
925
|
+
*/
|
|
926
|
+
preprocessYamlForParsing(yamlText) {
|
|
927
|
+
const lines = yamlText.split('\n');
|
|
928
|
+
const result = [];
|
|
929
|
+
for (const line of lines) {
|
|
930
|
+
// Match a YAML mapping entry: optional indent, key, colon, space, value
|
|
931
|
+
// Keys must start with a word char, may contain word chars, dots, hyphens
|
|
932
|
+
const match = line.match(/^(\s*)([\w][\w.-]*):\s+(.+)$/);
|
|
933
|
+
if (match) {
|
|
934
|
+
const [, indent, key, value] = match;
|
|
935
|
+
const trimmedValue = value.trim();
|
|
936
|
+
// Skip if already quoted
|
|
937
|
+
if ((trimmedValue.startsWith('"') && trimmedValue.endsWith('"')) ||
|
|
938
|
+
(trimmedValue.startsWith("'") && trimmedValue.endsWith("'"))) {
|
|
939
|
+
result.push(line);
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
// Skip if the value is a flow sequence [...] or flow mapping {...}
|
|
943
|
+
if ((trimmedValue.startsWith('[') && trimmedValue.endsWith(']')) ||
|
|
944
|
+
(trimmedValue.startsWith('{') && trimmedValue.endsWith('}'))) {
|
|
945
|
+
result.push(line);
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
// Skip block scalar indicators (>, |, >-, |-, >+, |+)
|
|
949
|
+
if (/^[|>][-+]?$/.test(trimmedValue)) {
|
|
950
|
+
result.push(line);
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
953
|
+
const needsQuoting = trimmedValue.includes(': ') ||
|
|
954
|
+
trimmedValue.includes(' #') ||
|
|
955
|
+
/^[!&*%@`]/.test(trimmedValue);
|
|
956
|
+
if (needsQuoting) {
|
|
957
|
+
const escaped = trimmedValue.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
958
|
+
result.push(`${indent}${key}: "${escaped}"`);
|
|
959
|
+
continue;
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
result.push(line);
|
|
963
|
+
}
|
|
964
|
+
return result.join('\n');
|
|
965
|
+
}
|
|
896
966
|
collectYamlFormatOperations(operations) {
|
|
897
967
|
const yamlSettings = this.settings.formatYamlFrontmatter;
|
|
898
968
|
visit(this.ast, (node) => {
|
|
899
969
|
if (node.type === 'yaml' && node.position) {
|
|
900
970
|
const yamlNode = node;
|
|
901
971
|
try {
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
972
|
+
let yamlToParse = yamlNode.value;
|
|
973
|
+
// Pre-process YAML to fix unsafe values (e.g., unquoted colons)
|
|
974
|
+
if (yamlSettings.fixUnsafeValues !== false) {
|
|
975
|
+
yamlToParse = this.preprocessYamlForParsing(yamlToParse);
|
|
976
|
+
}
|
|
977
|
+
// Parse the YAML content using JSON_SCHEMA to prevent silent
|
|
978
|
+
// data corruption (e.g., dates parsed as Date objects, octals)
|
|
979
|
+
const parsed = yaml.load(yamlToParse, { schema: yaml.JSON_SCHEMA });
|
|
980
|
+
// Format it back with proper formatting using JSON_SCHEMA
|
|
981
|
+
// to preserve string representations (dates, etc.)
|
|
905
982
|
const formatted = yaml.dump(parsed, {
|
|
983
|
+
schema: yaml.JSON_SCHEMA,
|
|
906
984
|
indent: yamlSettings.indent || 2,
|
|
907
985
|
lineWidth: yamlSettings.lineWidth || 100,
|
|
908
986
|
quotingType: (yamlSettings.quotingType || '"'),
|
package/dist/settings.js
CHANGED
|
@@ -15,6 +15,8 @@ export const formatterSettings = {
|
|
|
15
15
|
indentSize: 2,
|
|
16
16
|
// Components to ignore (preserve their formatting completely)
|
|
17
17
|
ignoreComponents: [],
|
|
18
|
+
// Preserve indentation inside template literal JSX attributes (html={`...`}, css={`...`})
|
|
19
|
+
preserveTemplateLiteralIndent: true,
|
|
18
20
|
},
|
|
19
21
|
// Rule 3: Format all HTML blocks within MDX using Prettier
|
|
20
22
|
formatHtmlBlocksInMdx: {
|
|
@@ -60,6 +62,7 @@ export const formatterSettings = {
|
|
|
60
62
|
quotingType: '"', // Quote type for strings that need quoting: '"' or "'"
|
|
61
63
|
forceQuotes: false, // Force quotes on all string values
|
|
62
64
|
noCompatMode: true, // Use YAML 1.2 spec (not 1.1)
|
|
65
|
+
fixUnsafeValues: true, // Pre-process YAML to quote values containing special characters like colons
|
|
63
66
|
},
|
|
64
67
|
// Rule 8: Preserve Docusaurus admonitions
|
|
65
68
|
preserveAdmonitions: {
|
package/dist/types.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export interface FormatMultiLineJsxSetting {
|
|
|
76
76
|
indentSize: number;
|
|
77
77
|
indentType?: string;
|
|
78
78
|
ignoreComponents: string[];
|
|
79
|
+
preserveTemplateLiteralIndent: boolean;
|
|
79
80
|
}
|
|
80
81
|
export interface FormatHtmlBlocksInMdxSetting {
|
|
81
82
|
enabled: boolean;
|
|
@@ -111,6 +112,7 @@ export interface FormatYamlFrontmatterSetting {
|
|
|
111
112
|
quotingType: string;
|
|
112
113
|
forceQuotes: boolean;
|
|
113
114
|
noCompatMode: boolean;
|
|
115
|
+
fixUnsafeValues: boolean;
|
|
114
116
|
}
|
|
115
117
|
export interface PreserveAdmonitionsSetting {
|
|
116
118
|
enabled: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@takazudo/mdx-formatter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "AST-based markdown and MDX formatter with Japanese text support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"lint:fix": "eslint --fix .",
|
|
34
34
|
"check": "prettier --check . && eslint .",
|
|
35
35
|
"check:fix": "prettier --write . && eslint --fix .",
|
|
36
|
+
"b4push": "./scripts/run-b4push.sh",
|
|
36
37
|
"doc:start": "pnpm --dir doc start",
|
|
37
38
|
"prepare": "husky",
|
|
38
39
|
"prepublishOnly": "tsc && vitest run"
|