mermaid-formatter 0.1.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/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +141 -0
- package/dist/formatter.d.ts +10 -0
- package/dist/formatter.js +220 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +99 -0
- package/dist/parser.d.ts +14 -0
- package/dist/parser.js +116 -0
- package/dist/rules.d.ts +45 -0
- package/dist/rules.js +100 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +6 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 chenyanchen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# mermaid-formatter
|
|
2
|
+
|
|
3
|
+
A formatter for [Mermaid](https://mermaid.js.org/) diagram syntax.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Format all Mermaid diagram types (sequenceDiagram, flowchart, classDiagram, etc.)
|
|
8
|
+
- Normalize indentation and whitespace
|
|
9
|
+
- Format Mermaid code blocks in Markdown
|
|
10
|
+
- CLI tool and programmatic API
|
|
11
|
+
- Zero dependencies (lightweight)
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install mermaid-formatter
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### CLI
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Format file to stdout
|
|
25
|
+
npx mmdfmt diagram.mmd
|
|
26
|
+
|
|
27
|
+
# Format file in-place
|
|
28
|
+
npx mmdfmt -w diagram.mmd
|
|
29
|
+
|
|
30
|
+
# Format from stdin
|
|
31
|
+
echo "sequenceDiagram
|
|
32
|
+
A->>B: hello" | npx mmdfmt
|
|
33
|
+
|
|
34
|
+
# Custom indent (default: 4 spaces)
|
|
35
|
+
npx mmdfmt --indent 2 diagram.mmd
|
|
36
|
+
|
|
37
|
+
# Use tabs instead of spaces
|
|
38
|
+
npx mmdfmt --tabs diagram.mmd
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Programmatic API
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { formatMermaid, formatMarkdownMermaidBlocks } from 'mermaid-formatter';
|
|
45
|
+
|
|
46
|
+
// Format Mermaid code
|
|
47
|
+
const input = `sequenceDiagram
|
|
48
|
+
participant A
|
|
49
|
+
A->>B: Hello`;
|
|
50
|
+
|
|
51
|
+
const formatted = formatMermaid(input);
|
|
52
|
+
// Output:
|
|
53
|
+
// sequenceDiagram
|
|
54
|
+
// participant A
|
|
55
|
+
// A->>B: Hello
|
|
56
|
+
|
|
57
|
+
// With options
|
|
58
|
+
const formatted2 = formatMermaid(input, { indentSize: 2, useTabs: false });
|
|
59
|
+
|
|
60
|
+
// Format Mermaid blocks in Markdown
|
|
61
|
+
const markdown = `
|
|
62
|
+
# My Document
|
|
63
|
+
|
|
64
|
+
\`\`\`mermaid
|
|
65
|
+
sequenceDiagram
|
|
66
|
+
A->>B: hello
|
|
67
|
+
\`\`\`
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const formattedMarkdown = formatMarkdownMermaidBlocks(markdown);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### API Reference
|
|
74
|
+
|
|
75
|
+
#### `formatMermaid(input: string, options?: FormatOptions): string`
|
|
76
|
+
|
|
77
|
+
Format Mermaid diagram source code.
|
|
78
|
+
|
|
79
|
+
**Options:**
|
|
80
|
+
|
|
81
|
+
- `indentSize` (number, default: 4) - Number of spaces for indentation
|
|
82
|
+
- `useTabs` (boolean, default: false) - Use tabs instead of spaces
|
|
83
|
+
|
|
84
|
+
#### `formatMarkdownMermaidBlocks(markdown: string, options?: FormatOptions): string`
|
|
85
|
+
|
|
86
|
+
Format all Mermaid code blocks in a Markdown document.
|
|
87
|
+
|
|
88
|
+
#### `parse(input: string): Diagram`
|
|
89
|
+
|
|
90
|
+
Parse Mermaid source into an AST.
|
|
91
|
+
|
|
92
|
+
#### `detectDiagramType(input: string): DiagramType`
|
|
93
|
+
|
|
94
|
+
Detect the diagram type from source code.
|
|
95
|
+
|
|
96
|
+
## Formatting Rules
|
|
97
|
+
|
|
98
|
+
- Diagram declaration at column 0
|
|
99
|
+
- Block keywords (`critical`, `alt`, `loop`, `par`, `opt`, `break`, `rect`, `subgraph`, `end`) at column 0
|
|
100
|
+
- Content inside blocks indented by configured amount
|
|
101
|
+
- Consecutive blank lines collapsed to single blank line
|
|
102
|
+
- Blank line inserted before block starts
|
|
103
|
+
- Whitespace normalized (multiple spaces → single, bracket padding removed)
|
|
104
|
+
|
|
105
|
+
## Supported Diagram Types
|
|
106
|
+
|
|
107
|
+
- sequenceDiagram
|
|
108
|
+
- flowchart / graph
|
|
109
|
+
- classDiagram
|
|
110
|
+
- stateDiagram / stateDiagram-v2
|
|
111
|
+
- erDiagram
|
|
112
|
+
- journey
|
|
113
|
+
- gantt
|
|
114
|
+
- pie
|
|
115
|
+
- quadrantChart
|
|
116
|
+
- requirementDiagram
|
|
117
|
+
- gitGraph
|
|
118
|
+
- mindmap
|
|
119
|
+
- timeline
|
|
120
|
+
- sankey-beta
|
|
121
|
+
- xychart-beta
|
|
122
|
+
- block-beta
|
|
123
|
+
- architecture-beta
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { formatMermaid } from './index.js';
|
|
4
|
+
function parseArgs(args) {
|
|
5
|
+
const result = {
|
|
6
|
+
write: false,
|
|
7
|
+
indent: 4,
|
|
8
|
+
tabs: false,
|
|
9
|
+
help: false,
|
|
10
|
+
version: false,
|
|
11
|
+
};
|
|
12
|
+
for (let i = 0; i < args.length; i++) {
|
|
13
|
+
const arg = args[i];
|
|
14
|
+
if (arg === '-h' || arg === '--help') {
|
|
15
|
+
result.help = true;
|
|
16
|
+
}
|
|
17
|
+
else if (arg === '-v' || arg === '--version') {
|
|
18
|
+
result.version = true;
|
|
19
|
+
}
|
|
20
|
+
else if (arg === '-w' || arg === '--write') {
|
|
21
|
+
result.write = true;
|
|
22
|
+
}
|
|
23
|
+
else if (arg === '--tabs') {
|
|
24
|
+
result.tabs = true;
|
|
25
|
+
}
|
|
26
|
+
else if (arg === '--indent') {
|
|
27
|
+
const next = args[++i];
|
|
28
|
+
const parsed = parseInt(next, 10);
|
|
29
|
+
result.indent = Number.isNaN(parsed) ? 4 : parsed;
|
|
30
|
+
}
|
|
31
|
+
else if (arg.startsWith('--indent=')) {
|
|
32
|
+
const parsed = parseInt(arg.slice(9), 10);
|
|
33
|
+
result.indent = Number.isNaN(parsed) ? 4 : parsed;
|
|
34
|
+
}
|
|
35
|
+
else if (!arg.startsWith('-')) {
|
|
36
|
+
result.file = arg;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
function printHelp() {
|
|
42
|
+
console.log(`
|
|
43
|
+
mmdfmt - Mermaid diagram formatter
|
|
44
|
+
|
|
45
|
+
USAGE:
|
|
46
|
+
mmdfmt [OPTIONS] [FILE]
|
|
47
|
+
|
|
48
|
+
ARGS:
|
|
49
|
+
<FILE> Input file (reads from stdin if not provided)
|
|
50
|
+
|
|
51
|
+
OPTIONS:
|
|
52
|
+
-w, --write Write result to source file instead of stdout
|
|
53
|
+
--indent <N> Number of spaces for indentation (default: 4)
|
|
54
|
+
--tabs Use tabs instead of spaces
|
|
55
|
+
-h, --help Print help information
|
|
56
|
+
-v, --version Print version information
|
|
57
|
+
|
|
58
|
+
EXAMPLES:
|
|
59
|
+
# Format file to stdout
|
|
60
|
+
mmdfmt diagram.mmd
|
|
61
|
+
|
|
62
|
+
# Format file in-place
|
|
63
|
+
mmdfmt -w diagram.mmd
|
|
64
|
+
|
|
65
|
+
# Format from stdin
|
|
66
|
+
echo "sequenceDiagram" | mmdfmt
|
|
67
|
+
|
|
68
|
+
# Custom indent
|
|
69
|
+
mmdfmt --indent 2 diagram.mmd
|
|
70
|
+
mmdfmt --tabs diagram.mmd
|
|
71
|
+
`);
|
|
72
|
+
}
|
|
73
|
+
function printVersion() {
|
|
74
|
+
// Read version from package.json
|
|
75
|
+
try {
|
|
76
|
+
const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
|
|
77
|
+
console.log(`mmdfmt ${pkg.version}`);
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
console.log('mmdfmt 0.1.0');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function readStdin() {
|
|
84
|
+
const chunks = [];
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
process.stdin.on('data', (chunk) => chunks.push(chunk));
|
|
87
|
+
process.stdin.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
|
|
88
|
+
process.stdin.on('error', reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
async function main() {
|
|
92
|
+
const args = parseArgs(process.argv.slice(2));
|
|
93
|
+
if (args.help) {
|
|
94
|
+
printHelp();
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
if (args.version) {
|
|
98
|
+
printVersion();
|
|
99
|
+
process.exit(0);
|
|
100
|
+
}
|
|
101
|
+
const options = {
|
|
102
|
+
indentSize: args.indent,
|
|
103
|
+
useTabs: args.tabs,
|
|
104
|
+
};
|
|
105
|
+
let input;
|
|
106
|
+
if (args.file) {
|
|
107
|
+
try {
|
|
108
|
+
input = readFileSync(args.file, 'utf-8');
|
|
109
|
+
}
|
|
110
|
+
catch (_err) {
|
|
111
|
+
console.error(`Error reading file: ${args.file}`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
// Check if stdin is a TTY (no piped input)
|
|
117
|
+
if (process.stdin.isTTY) {
|
|
118
|
+
printHelp();
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}
|
|
121
|
+
input = await readStdin();
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const formatted = formatMermaid(input, options);
|
|
125
|
+
if (args.write && args.file) {
|
|
126
|
+
writeFileSync(args.file, formatted, 'utf-8');
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
process.stdout.write(formatted);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch (err) {
|
|
133
|
+
console.error(`Error formatting: ${err instanceof Error ? err.message : err}`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
main().catch((err) => {
|
|
138
|
+
console.error(err);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
|
141
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatter for Mermaid diagram AST.
|
|
3
|
+
* Converts AST back to formatted source code.
|
|
4
|
+
*/
|
|
5
|
+
import type { Diagram, FormatOptions } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Format a parsed diagram AST back to string.
|
|
8
|
+
*/
|
|
9
|
+
export declare function format(diagram: Diagram, options?: FormatOptions): string;
|
|
10
|
+
//# sourceMappingURL=formatter.d.ts.map
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Formatter for Mermaid diagram AST.
|
|
3
|
+
* Converts AST back to formatted source code.
|
|
4
|
+
*/
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Configuration
|
|
7
|
+
// ============================================================================
|
|
8
|
+
const DEFAULT_OPTIONS = {
|
|
9
|
+
indentSize: 4,
|
|
10
|
+
useTabs: false,
|
|
11
|
+
};
|
|
12
|
+
const STATEMENT_FORMATTERS = {
|
|
13
|
+
'block-start': (stmt) => {
|
|
14
|
+
const s = stmt;
|
|
15
|
+
return s.label ? `${s.blockKind} ${s.label}` : s.blockKind;
|
|
16
|
+
},
|
|
17
|
+
'brace-block-start': (stmt) => {
|
|
18
|
+
const s = stmt;
|
|
19
|
+
return `${s.blockKind} ${s.name} {`;
|
|
20
|
+
},
|
|
21
|
+
'block-option': (stmt) => {
|
|
22
|
+
const label = stmt.label;
|
|
23
|
+
return label ? `option ${label}` : 'option';
|
|
24
|
+
},
|
|
25
|
+
'block-else': (stmt) => {
|
|
26
|
+
const label = stmt.label;
|
|
27
|
+
return label ? `else ${label}` : 'else';
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
// Statements that need content normalization
|
|
31
|
+
const NORMALIZABLE_TYPES = [
|
|
32
|
+
'generic-line',
|
|
33
|
+
'participant',
|
|
34
|
+
'note',
|
|
35
|
+
];
|
|
36
|
+
const CONTENT_NORMALIZERS = [
|
|
37
|
+
// Collapse multiple spaces to single space
|
|
38
|
+
(content) => content.replace(/ +/g, ' '),
|
|
39
|
+
// Normalize spaces after colon (A: B -> A: B)
|
|
40
|
+
(content) => content.replace(/:\s{2,}/g, ': '),
|
|
41
|
+
// Normalize bracket padding: [ text ] -> [text]
|
|
42
|
+
(content) => normalizeBracketPair(content, '[', ']'),
|
|
43
|
+
// Normalize brace padding: { text } -> {text}
|
|
44
|
+
(content) => normalizeBracketPair(content, '{', '}'),
|
|
45
|
+
// Normalize paren padding: ( text ) -> (text)
|
|
46
|
+
(content) => normalizeBracketPair(content, '(', ')'),
|
|
47
|
+
// Normalize pipe labels: | text | -> |text|
|
|
48
|
+
(content) => content.replace(/\|\s+([^|]*?)\s*\|/g, (_, inner) => `|${inner.trim()}|`),
|
|
49
|
+
];
|
|
50
|
+
function normalizeContent(content) {
|
|
51
|
+
return CONTENT_NORMALIZERS.reduce((acc, fn) => fn(acc), content);
|
|
52
|
+
}
|
|
53
|
+
// ============================================================================
|
|
54
|
+
// Indentation Rules
|
|
55
|
+
// ============================================================================
|
|
56
|
+
/** Statements that always appear at column 0 (relative to brace depth) */
|
|
57
|
+
const COLUMN_ZERO_TYPES = [
|
|
58
|
+
'diagram-decl',
|
|
59
|
+
'directive',
|
|
60
|
+
'block-start',
|
|
61
|
+
'block-option',
|
|
62
|
+
'block-else',
|
|
63
|
+
'block-end',
|
|
64
|
+
];
|
|
65
|
+
function getIndentDepth(stmt, seenDiagramDecl, braceBlockDepth) {
|
|
66
|
+
// Diagram declaration and directives: always at column 0
|
|
67
|
+
if (stmt.type === 'diagram-decl' || stmt.type === 'directive') {
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
// Block control statements: at column 0 (relative to brace depth)
|
|
71
|
+
if (COLUMN_ZERO_TYPES.includes(stmt.type)) {
|
|
72
|
+
return braceBlockDepth;
|
|
73
|
+
}
|
|
74
|
+
// Brace block start/end: at brace depth level
|
|
75
|
+
if (stmt.type === 'brace-block-start' || stmt.type === 'brace-block-end') {
|
|
76
|
+
return braceBlockDepth;
|
|
77
|
+
}
|
|
78
|
+
// Inside brace blocks: use brace depth directly
|
|
79
|
+
if (braceBlockDepth > 0) {
|
|
80
|
+
return braceBlockDepth;
|
|
81
|
+
}
|
|
82
|
+
// At top level (after diagram decl): indent by 1
|
|
83
|
+
if (seenDiagramDecl) {
|
|
84
|
+
return 1;
|
|
85
|
+
}
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// Blank Line Rules
|
|
90
|
+
// ============================================================================
|
|
91
|
+
/** Types that trigger blank line insertion before block starts */
|
|
92
|
+
const BLANK_BEFORE_BLOCK_TYPES = [
|
|
93
|
+
'diagram-decl',
|
|
94
|
+
'generic-line',
|
|
95
|
+
'participant',
|
|
96
|
+
'note',
|
|
97
|
+
'block-end',
|
|
98
|
+
'brace-block-end',
|
|
99
|
+
];
|
|
100
|
+
function shouldInsertBlankBefore(stmt, lastNonBlankType) {
|
|
101
|
+
if (!lastNonBlankType)
|
|
102
|
+
return false;
|
|
103
|
+
// Insert blank before block-start or brace-block-start
|
|
104
|
+
if (stmt.type === 'block-start' || stmt.type === 'brace-block-start') {
|
|
105
|
+
return BLANK_BEFORE_BLOCK_TYPES.includes(lastNonBlankType);
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// Main Format Function
|
|
111
|
+
// ============================================================================
|
|
112
|
+
/**
|
|
113
|
+
* Format a parsed diagram AST back to string.
|
|
114
|
+
*/
|
|
115
|
+
export function format(diagram, options = {}) {
|
|
116
|
+
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
117
|
+
const indentStr = opts.useTabs ? '\t' : ' '.repeat(opts.indentSize);
|
|
118
|
+
const lines = [];
|
|
119
|
+
let braceBlockDepth = 0;
|
|
120
|
+
let seenDiagramDecl = false;
|
|
121
|
+
let lastNonBlankType = null;
|
|
122
|
+
for (let i = 0; i < diagram.statements.length; i++) {
|
|
123
|
+
const stmt = diagram.statements[i];
|
|
124
|
+
// Handle blank lines: collapse consecutive blanks, skip trailing
|
|
125
|
+
if (stmt.type === 'blank-line') {
|
|
126
|
+
if (lastNonBlankType === 'blank-line' ||
|
|
127
|
+
i === diagram.statements.length - 1) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (lines.length === 0) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
lines.push('');
|
|
134
|
+
lastNonBlankType = 'blank-line';
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
// Insert blank line before block-start if needed
|
|
138
|
+
if (shouldInsertBlankBefore(stmt, lastNonBlankType)) {
|
|
139
|
+
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
|
140
|
+
lines.push('');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// Decrement brace depth before formatting brace-block-end
|
|
144
|
+
if (stmt.type === 'brace-block-end' && braceBlockDepth > 0) {
|
|
145
|
+
braceBlockDepth--;
|
|
146
|
+
}
|
|
147
|
+
// Calculate indentation depth
|
|
148
|
+
const depth = getIndentDepth(stmt, seenDiagramDecl, braceBlockDepth);
|
|
149
|
+
// Format the statement
|
|
150
|
+
const content = formatStatement(stmt);
|
|
151
|
+
const formatted = depth > 0 ? indentStr.repeat(depth) + content : content;
|
|
152
|
+
lines.push(formatted);
|
|
153
|
+
// Update state
|
|
154
|
+
if (stmt.type === 'diagram-decl') {
|
|
155
|
+
seenDiagramDecl = true;
|
|
156
|
+
}
|
|
157
|
+
if (stmt.type === 'brace-block-start') {
|
|
158
|
+
braceBlockDepth++;
|
|
159
|
+
}
|
|
160
|
+
lastNonBlankType = stmt.type;
|
|
161
|
+
}
|
|
162
|
+
// Ensure single trailing newline
|
|
163
|
+
return lines.join('\n') + '\n';
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Format a single statement's content.
|
|
167
|
+
*/
|
|
168
|
+
function formatStatement(stmt) {
|
|
169
|
+
// Use custom formatter if available
|
|
170
|
+
const formatter = STATEMENT_FORMATTERS[stmt.type];
|
|
171
|
+
if (formatter) {
|
|
172
|
+
return formatter(stmt);
|
|
173
|
+
}
|
|
174
|
+
// Normalize content for specific types
|
|
175
|
+
if (NORMALIZABLE_TYPES.includes(stmt.type)) {
|
|
176
|
+
return normalizeContent(stmt.content);
|
|
177
|
+
}
|
|
178
|
+
// Default: return content as-is
|
|
179
|
+
return stmt.content;
|
|
180
|
+
}
|
|
181
|
+
// ============================================================================
|
|
182
|
+
// Helper Functions
|
|
183
|
+
// ============================================================================
|
|
184
|
+
/**
|
|
185
|
+
* Normalize padding inside bracket pairs.
|
|
186
|
+
* Only normalizes when there's space after opening bracket.
|
|
187
|
+
*/
|
|
188
|
+
function normalizeBracketPair(content, open, close) {
|
|
189
|
+
const chars = [...content];
|
|
190
|
+
const result = [];
|
|
191
|
+
let i = 0;
|
|
192
|
+
while (i < chars.length) {
|
|
193
|
+
if (chars[i] === open && chars[i + 1] === ' ') {
|
|
194
|
+
// Find matching close bracket
|
|
195
|
+
let depth = 1;
|
|
196
|
+
let j = i + 1;
|
|
197
|
+
while (j < chars.length && depth > 0) {
|
|
198
|
+
if (chars[j] === open)
|
|
199
|
+
depth++;
|
|
200
|
+
else if (chars[j] === close)
|
|
201
|
+
depth--;
|
|
202
|
+
j++;
|
|
203
|
+
}
|
|
204
|
+
if (depth === 0) {
|
|
205
|
+
// Extract inner content (excluding brackets)
|
|
206
|
+
const inner = chars
|
|
207
|
+
.slice(i + 1, j - 1)
|
|
208
|
+
.join('')
|
|
209
|
+
.trim();
|
|
210
|
+
result.push(open, inner, close);
|
|
211
|
+
i = j;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
result.push(chars[i]);
|
|
216
|
+
i++;
|
|
217
|
+
}
|
|
218
|
+
return result.join('');
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=formatter.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid Formatter - Public API
|
|
3
|
+
*
|
|
4
|
+
* Format Mermaid diagram syntax for consistent code style.
|
|
5
|
+
*/
|
|
6
|
+
export type { FormatOptions, DiagramType, Statement, Diagram, StatementType, BlockKind, BraceBlockKind, } from './types.js';
|
|
7
|
+
export { parse, detectDiagramType } from './parser.js';
|
|
8
|
+
export { format } from './formatter.js';
|
|
9
|
+
export { isIndentSensitive, INDENT_SENSITIVE_DIAGRAMS } from './rules.js';
|
|
10
|
+
import type { FormatOptions } from './types.js';
|
|
11
|
+
/**
|
|
12
|
+
* Format Mermaid diagram source code.
|
|
13
|
+
*
|
|
14
|
+
* @param input - Mermaid diagram source code
|
|
15
|
+
* @param options - Formatting options
|
|
16
|
+
* @returns Formatted Mermaid code
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { formatMermaid } from 'mermaid-formatter';
|
|
21
|
+
*
|
|
22
|
+
* const formatted = formatMermaid(`
|
|
23
|
+
* sequenceDiagram
|
|
24
|
+
* participant A
|
|
25
|
+
* A->>B: Hello
|
|
26
|
+
* `);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatMermaid(input: string, options?: FormatOptions): string;
|
|
30
|
+
/**
|
|
31
|
+
* Format Mermaid code blocks in Markdown.
|
|
32
|
+
*
|
|
33
|
+
* Preserves indentation for nested code blocks (in lists, blockquotes, etc.)
|
|
34
|
+
*
|
|
35
|
+
* @param markdown - Markdown content
|
|
36
|
+
* @param options - Formatting options
|
|
37
|
+
* @returns Markdown with formatted Mermaid blocks
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { formatMarkdownMermaidBlocks } from 'mermaid-formatter';
|
|
42
|
+
*
|
|
43
|
+
* const formatted = formatMarkdownMermaidBlocks(`
|
|
44
|
+
* # My Document
|
|
45
|
+
*
|
|
46
|
+
* \`\`\`mermaid
|
|
47
|
+
* sequenceDiagram
|
|
48
|
+
* A->>B: Hello
|
|
49
|
+
* \`\`\`
|
|
50
|
+
* `);
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function formatMarkdownMermaidBlocks(markdown: string, options?: FormatOptions): string;
|
|
54
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mermaid Formatter - Public API
|
|
3
|
+
*
|
|
4
|
+
* Format Mermaid diagram syntax for consistent code style.
|
|
5
|
+
*/
|
|
6
|
+
// Function exports
|
|
7
|
+
export { parse, detectDiagramType } from './parser.js';
|
|
8
|
+
export { format } from './formatter.js';
|
|
9
|
+
export { isIndentSensitive, INDENT_SENSITIVE_DIAGRAMS } from './rules.js';
|
|
10
|
+
// Internal imports
|
|
11
|
+
import { parse, detectDiagramType } from './parser.js';
|
|
12
|
+
import { format } from './formatter.js';
|
|
13
|
+
import { isIndentSensitive } from './rules.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Main API
|
|
16
|
+
// ============================================================================
|
|
17
|
+
/**
|
|
18
|
+
* Format Mermaid diagram source code.
|
|
19
|
+
*
|
|
20
|
+
* @param input - Mermaid diagram source code
|
|
21
|
+
* @param options - Formatting options
|
|
22
|
+
* @returns Formatted Mermaid code
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* import { formatMermaid } from 'mermaid-formatter';
|
|
27
|
+
*
|
|
28
|
+
* const formatted = formatMermaid(`
|
|
29
|
+
* sequenceDiagram
|
|
30
|
+
* participant A
|
|
31
|
+
* A->>B: Hello
|
|
32
|
+
* `);
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export function formatMermaid(input, options) {
|
|
36
|
+
// Pipeline: detect -> check policy -> parse -> format
|
|
37
|
+
const diagramType = detectDiagramType(input);
|
|
38
|
+
// Policy: skip formatting for indent-sensitive diagrams
|
|
39
|
+
if (isIndentSensitive(diagramType)) {
|
|
40
|
+
return ensureTrailingNewline(input);
|
|
41
|
+
}
|
|
42
|
+
const diagram = parse(input);
|
|
43
|
+
return format(diagram, options);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Format Mermaid code blocks in Markdown.
|
|
47
|
+
*
|
|
48
|
+
* Preserves indentation for nested code blocks (in lists, blockquotes, etc.)
|
|
49
|
+
*
|
|
50
|
+
* @param markdown - Markdown content
|
|
51
|
+
* @param options - Formatting options
|
|
52
|
+
* @returns Markdown with formatted Mermaid blocks
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* import { formatMarkdownMermaidBlocks } from 'mermaid-formatter';
|
|
57
|
+
*
|
|
58
|
+
* const formatted = formatMarkdownMermaidBlocks(`
|
|
59
|
+
* # My Document
|
|
60
|
+
*
|
|
61
|
+
* \`\`\`mermaid
|
|
62
|
+
* sequenceDiagram
|
|
63
|
+
* A->>B: Hello
|
|
64
|
+
* \`\`\`
|
|
65
|
+
* `);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function formatMarkdownMermaidBlocks(markdown, options) {
|
|
69
|
+
// Pattern captures:
|
|
70
|
+
// 1. Leading indentation (spaces/tabs before ```)
|
|
71
|
+
// 2. Code content between fences
|
|
72
|
+
// Supports both LF and CRLF line endings
|
|
73
|
+
return markdown.replace(/^([ \t]*)```mermaid\r?\n([\s\S]*?)```/gm, (_, indent, code) => {
|
|
74
|
+
const formatted = formatMermaid(code, options);
|
|
75
|
+
const indentedCode = applyIndent(formatted, indent);
|
|
76
|
+
return `${indent}\`\`\`mermaid\n${indentedCode}${indent}\`\`\``;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Helper Functions
|
|
81
|
+
// ============================================================================
|
|
82
|
+
/**
|
|
83
|
+
* Ensure string ends with exactly one newline.
|
|
84
|
+
*/
|
|
85
|
+
function ensureTrailingNewline(input) {
|
|
86
|
+
return input.endsWith('\n') ? input : input + '\n';
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Apply indentation to each non-empty line.
|
|
90
|
+
*/
|
|
91
|
+
function applyIndent(content, indent) {
|
|
92
|
+
if (!indent)
|
|
93
|
+
return content;
|
|
94
|
+
return content
|
|
95
|
+
.split('\n')
|
|
96
|
+
.map((line) => (line ? indent + line : line))
|
|
97
|
+
.join('\n');
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=index.js.map
|
package/dist/parser.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight parser for Mermaid diagram syntax.
|
|
3
|
+
* Converts source code into an AST for formatting.
|
|
4
|
+
*/
|
|
5
|
+
import type { Diagram, DiagramType } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Parse Mermaid diagram source into AST.
|
|
8
|
+
*/
|
|
9
|
+
export declare function parse(input: string): Diagram;
|
|
10
|
+
/**
|
|
11
|
+
* Detect diagram type from source code.
|
|
12
|
+
*/
|
|
13
|
+
export declare function detectDiagramType(input: string): DiagramType;
|
|
14
|
+
//# sourceMappingURL=parser.d.ts.map
|
package/dist/parser.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight parser for Mermaid diagram syntax.
|
|
3
|
+
* Converts source code into an AST for formatting.
|
|
4
|
+
*/
|
|
5
|
+
import { matchDiagramType, matchBlockKeyword, matchBraceBlockStart, } from './rules.js';
|
|
6
|
+
/**
|
|
7
|
+
* Parse Mermaid diagram source into AST.
|
|
8
|
+
*/
|
|
9
|
+
export function parse(input) {
|
|
10
|
+
const lines = input.split('\n');
|
|
11
|
+
const statements = [];
|
|
12
|
+
let diagramType = 'unknown';
|
|
13
|
+
for (const line of lines) {
|
|
14
|
+
const trimmed = line.trim();
|
|
15
|
+
const statement = parseLine(trimmed, diagramType);
|
|
16
|
+
if (statement) {
|
|
17
|
+
// Track diagram type from first declaration
|
|
18
|
+
if (statement.type === 'diagram-decl' && diagramType === 'unknown') {
|
|
19
|
+
diagramType = statement.diagramType;
|
|
20
|
+
}
|
|
21
|
+
statements.push(statement);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { type: diagramType, statements };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parse a single line into a statement.
|
|
28
|
+
*/
|
|
29
|
+
function parseLine(trimmed, currentDiagramType) {
|
|
30
|
+
// Blank line
|
|
31
|
+
if (trimmed === '') {
|
|
32
|
+
return { type: 'blank-line', content: '' };
|
|
33
|
+
}
|
|
34
|
+
// Comment (but not directive)
|
|
35
|
+
if (trimmed.startsWith('%%') && !trimmed.startsWith('%%{')) {
|
|
36
|
+
return { type: 'comment', content: trimmed };
|
|
37
|
+
}
|
|
38
|
+
// Directive %%{ ... }%%
|
|
39
|
+
if (trimmed.startsWith('%%{')) {
|
|
40
|
+
return { type: 'directive', content: trimmed };
|
|
41
|
+
}
|
|
42
|
+
// Diagram declaration
|
|
43
|
+
const detectedType = matchDiagramType(trimmed);
|
|
44
|
+
if (detectedType && currentDiagramType === 'unknown') {
|
|
45
|
+
return {
|
|
46
|
+
type: 'diagram-decl',
|
|
47
|
+
diagramType: detectedType,
|
|
48
|
+
content: trimmed,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
// Block end: 'end'
|
|
52
|
+
if (trimmed === 'end') {
|
|
53
|
+
return { type: 'block-end', content: 'end' };
|
|
54
|
+
}
|
|
55
|
+
// Brace block end: '}'
|
|
56
|
+
if (trimmed === '}') {
|
|
57
|
+
return { type: 'brace-block-end', content: '}' };
|
|
58
|
+
}
|
|
59
|
+
// Block option (sequence diagram)
|
|
60
|
+
if (/^option\b/.test(trimmed)) {
|
|
61
|
+
const label = trimmed.slice(6).trim() || undefined;
|
|
62
|
+
return { type: 'block-option', label, content: trimmed };
|
|
63
|
+
}
|
|
64
|
+
// Block else (sequence diagram)
|
|
65
|
+
if (/^else\b/.test(trimmed)) {
|
|
66
|
+
const label = trimmed.slice(4).trim() || undefined;
|
|
67
|
+
return { type: 'block-else', label, content: trimmed };
|
|
68
|
+
}
|
|
69
|
+
// Block start with 'end' keyword
|
|
70
|
+
const blockKind = matchBlockKeyword(trimmed);
|
|
71
|
+
if (blockKind) {
|
|
72
|
+
const label = trimmed.slice(blockKind.length).trim() || undefined;
|
|
73
|
+
return {
|
|
74
|
+
type: 'block-start',
|
|
75
|
+
blockKind,
|
|
76
|
+
label,
|
|
77
|
+
content: trimmed,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Brace block start (state/class/namespace with {)
|
|
81
|
+
const braceBlock = matchBraceBlockStart(trimmed);
|
|
82
|
+
if (braceBlock) {
|
|
83
|
+
return {
|
|
84
|
+
type: 'brace-block-start',
|
|
85
|
+
blockKind: braceBlock.kind,
|
|
86
|
+
name: braceBlock.name,
|
|
87
|
+
content: trimmed,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
// Participant declaration (sequence diagram)
|
|
91
|
+
if (/^participant\b/.test(trimmed) || /^actor\b/.test(trimmed)) {
|
|
92
|
+
return { type: 'participant', content: trimmed };
|
|
93
|
+
}
|
|
94
|
+
// Note
|
|
95
|
+
if (/^note\b/i.test(trimmed)) {
|
|
96
|
+
return { type: 'note', content: trimmed };
|
|
97
|
+
}
|
|
98
|
+
// Generic line (arrows, relationships, nodes, etc.)
|
|
99
|
+
return { type: 'generic-line', content: trimmed };
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Detect diagram type from source code.
|
|
103
|
+
*/
|
|
104
|
+
export function detectDiagramType(input) {
|
|
105
|
+
const lines = input.split('\n');
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const trimmed = line.trim();
|
|
108
|
+
if (trimmed === '' || trimmed.startsWith('%%'))
|
|
109
|
+
continue;
|
|
110
|
+
const type = matchDiagramType(trimmed);
|
|
111
|
+
if (type)
|
|
112
|
+
return type;
|
|
113
|
+
}
|
|
114
|
+
return 'unknown';
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=parser.js.map
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized grammar rules and patterns for Mermaid diagrams.
|
|
3
|
+
* All syntax definitions in one place for easy maintenance.
|
|
4
|
+
*/
|
|
5
|
+
import type { DiagramType, BlockKind, BraceBlockKind } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Patterns for detecting diagram types.
|
|
8
|
+
* Order matters: more specific patterns should come first.
|
|
9
|
+
*/
|
|
10
|
+
export declare const DIAGRAM_PATTERNS: [RegExp, DiagramType][];
|
|
11
|
+
/**
|
|
12
|
+
* Block keywords that close with 'end' keyword.
|
|
13
|
+
* Used in sequence diagrams, flowcharts, etc.
|
|
14
|
+
*/
|
|
15
|
+
export declare const BLOCK_KEYWORDS: BlockKind[];
|
|
16
|
+
/**
|
|
17
|
+
* Block keywords that close with '}'.
|
|
18
|
+
* Used in class diagrams, state diagrams, etc.
|
|
19
|
+
*/
|
|
20
|
+
export declare const BRACE_BLOCK_KEYWORDS: BraceBlockKind[];
|
|
21
|
+
/**
|
|
22
|
+
* Diagram types where indentation represents hierarchy.
|
|
23
|
+
* These should NOT be reformatted as it would change semantics.
|
|
24
|
+
*/
|
|
25
|
+
export declare const INDENT_SENSITIVE_DIAGRAMS: DiagramType[];
|
|
26
|
+
/**
|
|
27
|
+
* Check if a diagram type is indent-sensitive.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isIndentSensitive(diagramType: DiagramType): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Match diagram type from a line of text.
|
|
32
|
+
*/
|
|
33
|
+
export declare function matchDiagramType(line: string): DiagramType | null;
|
|
34
|
+
/**
|
|
35
|
+
* Match block start keyword (critical, alt, loop, etc.)
|
|
36
|
+
*/
|
|
37
|
+
export declare function matchBlockKeyword(line: string): BlockKind | null;
|
|
38
|
+
/**
|
|
39
|
+
* Match brace block start (state Name {, class Name {, namespace Name {)
|
|
40
|
+
*/
|
|
41
|
+
export declare function matchBraceBlockStart(line: string): {
|
|
42
|
+
kind: BraceBlockKind;
|
|
43
|
+
name: string;
|
|
44
|
+
} | null;
|
|
45
|
+
//# sourceMappingURL=rules.d.ts.map
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized grammar rules and patterns for Mermaid diagrams.
|
|
3
|
+
* All syntax definitions in one place for easy maintenance.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Patterns for detecting diagram types.
|
|
7
|
+
* Order matters: more specific patterns should come first.
|
|
8
|
+
*/
|
|
9
|
+
export const DIAGRAM_PATTERNS = [
|
|
10
|
+
[/^sequenceDiagram\b/, 'sequenceDiagram'],
|
|
11
|
+
[/^flowchart(\s+(TD|TB|BT|LR|RL))?\b/, 'flowchart'],
|
|
12
|
+
[/^graph(\s+(TD|TB|BT|LR|RL))?\b/, 'graph'],
|
|
13
|
+
[/^classDiagram\b/, 'classDiagram'],
|
|
14
|
+
[/^stateDiagram-v2\b/, 'stateDiagram-v2'],
|
|
15
|
+
[/^stateDiagram\b/, 'stateDiagram'],
|
|
16
|
+
[/^erDiagram\b/, 'erDiagram'],
|
|
17
|
+
[/^journey\b/, 'journey'],
|
|
18
|
+
[/^gantt\b/, 'gantt'],
|
|
19
|
+
[/^pie(\s+showData)?\b/, 'pie'],
|
|
20
|
+
[/^quadrantChart\b/, 'quadrantChart'],
|
|
21
|
+
[/^requirementDiagram\b/, 'requirementDiagram'],
|
|
22
|
+
[/^gitGraph\b/, 'gitGraph'],
|
|
23
|
+
[/^mindmap\b/, 'mindmap'],
|
|
24
|
+
[/^timeline\b/, 'timeline'],
|
|
25
|
+
[/^sankey-beta\b/, 'sankey-beta'],
|
|
26
|
+
[/^xychart-beta\b/, 'xychart-beta'],
|
|
27
|
+
[/^block-beta\b/, 'block-beta'],
|
|
28
|
+
[/^architecture-beta\b/, 'architecture-beta'],
|
|
29
|
+
];
|
|
30
|
+
/**
|
|
31
|
+
* Block keywords that close with 'end' keyword.
|
|
32
|
+
* Used in sequence diagrams, flowcharts, etc.
|
|
33
|
+
*/
|
|
34
|
+
export const BLOCK_KEYWORDS = [
|
|
35
|
+
'critical',
|
|
36
|
+
'alt',
|
|
37
|
+
'loop',
|
|
38
|
+
'par',
|
|
39
|
+
'opt',
|
|
40
|
+
'break',
|
|
41
|
+
'rect',
|
|
42
|
+
'subgraph',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Block keywords that close with '}'.
|
|
46
|
+
* Used in class diagrams, state diagrams, etc.
|
|
47
|
+
*/
|
|
48
|
+
export const BRACE_BLOCK_KEYWORDS = [
|
|
49
|
+
'state',
|
|
50
|
+
'class',
|
|
51
|
+
'namespace',
|
|
52
|
+
];
|
|
53
|
+
/**
|
|
54
|
+
* Diagram types where indentation represents hierarchy.
|
|
55
|
+
* These should NOT be reformatted as it would change semantics.
|
|
56
|
+
*/
|
|
57
|
+
export const INDENT_SENSITIVE_DIAGRAMS = ['mindmap', 'timeline'];
|
|
58
|
+
/**
|
|
59
|
+
* Check if a diagram type is indent-sensitive.
|
|
60
|
+
*/
|
|
61
|
+
export function isIndentSensitive(diagramType) {
|
|
62
|
+
return INDENT_SENSITIVE_DIAGRAMS.includes(diagramType);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Match diagram type from a line of text.
|
|
66
|
+
*/
|
|
67
|
+
export function matchDiagramType(line) {
|
|
68
|
+
for (const [pattern, type] of DIAGRAM_PATTERNS) {
|
|
69
|
+
if (pattern.test(line)) {
|
|
70
|
+
return type;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Match block start keyword (critical, alt, loop, etc.)
|
|
77
|
+
*/
|
|
78
|
+
export function matchBlockKeyword(line) {
|
|
79
|
+
for (const keyword of BLOCK_KEYWORDS) {
|
|
80
|
+
const pattern = new RegExp(`^${keyword}\\b`);
|
|
81
|
+
if (pattern.test(line)) {
|
|
82
|
+
return keyword;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Match brace block start (state Name {, class Name {, namespace Name {)
|
|
89
|
+
*/
|
|
90
|
+
export function matchBraceBlockStart(line) {
|
|
91
|
+
for (const keyword of BRACE_BLOCK_KEYWORDS) {
|
|
92
|
+
const pattern = new RegExp(`^${keyword}\\s+(.+?)\\s*\\{\\s*$`);
|
|
93
|
+
const match = line.match(pattern);
|
|
94
|
+
if (match) {
|
|
95
|
+
return { kind: keyword, name: match[1].trim() };
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=rules.js.map
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Mermaid formatter AST and options.
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Formatting configuration options.
|
|
6
|
+
*/
|
|
7
|
+
export interface FormatOptions {
|
|
8
|
+
/** Number of spaces for indentation (default: 4) */
|
|
9
|
+
indentSize?: number;
|
|
10
|
+
/** Use tabs instead of spaces (default: false) */
|
|
11
|
+
useTabs?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Supported Mermaid diagram types.
|
|
15
|
+
*/
|
|
16
|
+
export type DiagramType = 'sequenceDiagram' | 'flowchart' | 'graph' | 'classDiagram' | 'stateDiagram' | 'stateDiagram-v2' | 'erDiagram' | 'journey' | 'gantt' | 'pie' | 'quadrantChart' | 'requirementDiagram' | 'gitGraph' | 'mindmap' | 'timeline' | 'sankey-beta' | 'xychart-beta' | 'block-beta' | 'architecture-beta' | 'unknown';
|
|
17
|
+
/**
|
|
18
|
+
* Block types that close with 'end' keyword.
|
|
19
|
+
*/
|
|
20
|
+
export type BlockKind = 'critical' | 'alt' | 'loop' | 'par' | 'opt' | 'break' | 'rect' | 'subgraph';
|
|
21
|
+
/**
|
|
22
|
+
* Block types that close with '}'.
|
|
23
|
+
*/
|
|
24
|
+
export type BraceBlockKind = 'state' | 'class' | 'namespace';
|
|
25
|
+
/** Base interface for all statement nodes */
|
|
26
|
+
interface StatementBase {
|
|
27
|
+
type: string;
|
|
28
|
+
}
|
|
29
|
+
/** Diagram type declaration (e.g., "sequenceDiagram", "flowchart TD") */
|
|
30
|
+
export interface DiagramDeclStatement extends StatementBase {
|
|
31
|
+
type: 'diagram-decl';
|
|
32
|
+
diagramType: DiagramType;
|
|
33
|
+
content: string;
|
|
34
|
+
}
|
|
35
|
+
/** Directive (e.g., "%%{init: {...}}%%") */
|
|
36
|
+
export interface DirectiveStatement extends StatementBase {
|
|
37
|
+
type: 'directive';
|
|
38
|
+
content: string;
|
|
39
|
+
}
|
|
40
|
+
/** Participant declaration (sequence diagram) */
|
|
41
|
+
export interface ParticipantStatement extends StatementBase {
|
|
42
|
+
type: 'participant';
|
|
43
|
+
content: string;
|
|
44
|
+
}
|
|
45
|
+
/** Block start with 'end' keyword (critical, alt, loop, etc.) */
|
|
46
|
+
export interface BlockStartStatement extends StatementBase {
|
|
47
|
+
type: 'block-start';
|
|
48
|
+
blockKind: BlockKind;
|
|
49
|
+
label?: string;
|
|
50
|
+
content: string;
|
|
51
|
+
}
|
|
52
|
+
/** Brace block start (state {, class {, namespace {) */
|
|
53
|
+
export interface BraceBlockStartStatement extends StatementBase {
|
|
54
|
+
type: 'brace-block-start';
|
|
55
|
+
blockKind: BraceBlockKind;
|
|
56
|
+
name: string;
|
|
57
|
+
content: string;
|
|
58
|
+
}
|
|
59
|
+
/** Block option (sequence diagram) */
|
|
60
|
+
export interface BlockOptionStatement extends StatementBase {
|
|
61
|
+
type: 'block-option';
|
|
62
|
+
label?: string;
|
|
63
|
+
content: string;
|
|
64
|
+
}
|
|
65
|
+
/** Block else (sequence diagram) */
|
|
66
|
+
export interface BlockElseStatement extends StatementBase {
|
|
67
|
+
type: 'block-else';
|
|
68
|
+
label?: string;
|
|
69
|
+
content: string;
|
|
70
|
+
}
|
|
71
|
+
/** Block end ('end' keyword) */
|
|
72
|
+
export interface BlockEndStatement extends StatementBase {
|
|
73
|
+
type: 'block-end';
|
|
74
|
+
content: string;
|
|
75
|
+
}
|
|
76
|
+
/** Brace block end ('}') */
|
|
77
|
+
export interface BraceBlockEndStatement extends StatementBase {
|
|
78
|
+
type: 'brace-block-end';
|
|
79
|
+
content: string;
|
|
80
|
+
}
|
|
81
|
+
/** Note statement */
|
|
82
|
+
export interface NoteStatement extends StatementBase {
|
|
83
|
+
type: 'note';
|
|
84
|
+
content: string;
|
|
85
|
+
}
|
|
86
|
+
/** Comment (e.g., "%% comment") */
|
|
87
|
+
export interface CommentStatement extends StatementBase {
|
|
88
|
+
type: 'comment';
|
|
89
|
+
content: string;
|
|
90
|
+
}
|
|
91
|
+
/** Generic line (arrows, relationships, nodes, etc.) */
|
|
92
|
+
export interface GenericLineStatement extends StatementBase {
|
|
93
|
+
type: 'generic-line';
|
|
94
|
+
content: string;
|
|
95
|
+
}
|
|
96
|
+
/** Blank line */
|
|
97
|
+
export interface BlankLineStatement extends StatementBase {
|
|
98
|
+
type: 'blank-line';
|
|
99
|
+
content: '';
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Union of all statement types.
|
|
103
|
+
*/
|
|
104
|
+
export type Statement = DiagramDeclStatement | DirectiveStatement | ParticipantStatement | BlockStartStatement | BraceBlockStartStatement | BlockOptionStatement | BlockElseStatement | BlockEndStatement | BraceBlockEndStatement | NoteStatement | CommentStatement | GenericLineStatement | BlankLineStatement;
|
|
105
|
+
/**
|
|
106
|
+
* Statement type discriminator.
|
|
107
|
+
*/
|
|
108
|
+
export type StatementType = Statement['type'];
|
|
109
|
+
/**
|
|
110
|
+
* Parsed diagram AST.
|
|
111
|
+
*/
|
|
112
|
+
export interface Diagram {
|
|
113
|
+
type: DiagramType;
|
|
114
|
+
statements: Statement[];
|
|
115
|
+
}
|
|
116
|
+
export { INDENT_SENSITIVE_DIAGRAMS } from './rules.js';
|
|
117
|
+
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mermaid-formatter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A formatter for Mermaid diagram syntax",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"bin": {
|
|
17
|
+
"mmdfmt": "dist/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"!dist/**/*.map"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"test:watch": "vitest",
|
|
27
|
+
"lint": "eslint .",
|
|
28
|
+
"lint:fix": "eslint . --fix",
|
|
29
|
+
"format": "prettier --write .",
|
|
30
|
+
"format:check": "prettier --check .",
|
|
31
|
+
"prepublishOnly": "npm run lint && npm run test && npm run build",
|
|
32
|
+
"clean": "rm -rf dist"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"mermaid",
|
|
36
|
+
"formatter",
|
|
37
|
+
"diagram",
|
|
38
|
+
"markdown",
|
|
39
|
+
"sequence-diagram",
|
|
40
|
+
"flowchart",
|
|
41
|
+
"ast"
|
|
42
|
+
],
|
|
43
|
+
"author": "chenyanchen",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/chenyanchen/mermaid-formatter.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/chenyanchen/mermaid-formatter/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/chenyanchen/mermaid-formatter#readme",
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=18"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.0.0",
|
|
58
|
+
"eslint": "^9.0.0",
|
|
59
|
+
"prettier": "^3.0.0",
|
|
60
|
+
"typescript": "^5.0.0",
|
|
61
|
+
"typescript-eslint": "^8.0.0",
|
|
62
|
+
"vitest": "^2.0.0"
|
|
63
|
+
}
|
|
64
|
+
}
|