@jacobknightley/fabric-format 0.0.1

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.
Files changed (50) hide show
  1. package/README.md +196 -0
  2. package/dist/cell-formatter.d.ts +75 -0
  3. package/dist/cell-formatter.js +144 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +435 -0
  6. package/dist/formatters/index.d.ts +19 -0
  7. package/dist/formatters/index.js +76 -0
  8. package/dist/formatters/python/config.d.ts +33 -0
  9. package/dist/formatters/python/config.js +29 -0
  10. package/dist/formatters/python/index.d.ts +7 -0
  11. package/dist/formatters/python/index.js +13 -0
  12. package/dist/formatters/python/python-formatter.d.ts +51 -0
  13. package/dist/formatters/python/python-formatter.js +180 -0
  14. package/dist/formatters/sparksql/constants.d.ts +16 -0
  15. package/dist/formatters/sparksql/constants.js +16 -0
  16. package/dist/formatters/sparksql/fmt-detector.d.ts +65 -0
  17. package/dist/formatters/sparksql/fmt-detector.js +84 -0
  18. package/dist/formatters/sparksql/formatter.d.ts +24 -0
  19. package/dist/formatters/sparksql/formatter.js +1276 -0
  20. package/dist/formatters/sparksql/formatting-context.d.ts +154 -0
  21. package/dist/formatters/sparksql/formatting-context.js +363 -0
  22. package/dist/formatters/sparksql/generated/SqlBaseLexer.d.ts +529 -0
  23. package/dist/formatters/sparksql/generated/SqlBaseLexer.js +2609 -0
  24. package/dist/formatters/sparksql/generated/SqlBaseParser.d.ts +8195 -0
  25. package/dist/formatters/sparksql/generated/SqlBaseParser.js +48793 -0
  26. package/dist/formatters/sparksql/generated/SqlBaseParserListener.d.ts +910 -0
  27. package/dist/formatters/sparksql/generated/SqlBaseParserListener.js +2730 -0
  28. package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.d.ts +456 -0
  29. package/dist/formatters/sparksql/generated/SqlBaseParserVisitor.js +1822 -0
  30. package/dist/formatters/sparksql/generated/builtinFunctions.d.ts +8 -0
  31. package/dist/formatters/sparksql/generated/builtinFunctions.js +510 -0
  32. package/dist/formatters/sparksql/index.d.ts +11 -0
  33. package/dist/formatters/sparksql/index.js +22 -0
  34. package/dist/formatters/sparksql/output-builder.d.ts +89 -0
  35. package/dist/formatters/sparksql/output-builder.js +191 -0
  36. package/dist/formatters/sparksql/parse-tree-analyzer.d.ts +264 -0
  37. package/dist/formatters/sparksql/parse-tree-analyzer.js +1956 -0
  38. package/dist/formatters/sparksql/sql-formatter.d.ts +25 -0
  39. package/dist/formatters/sparksql/sql-formatter.js +56 -0
  40. package/dist/formatters/sparksql/token-utils.d.ts +68 -0
  41. package/dist/formatters/sparksql/token-utils.js +155 -0
  42. package/dist/formatters/sparksql/types.d.ts +264 -0
  43. package/dist/formatters/sparksql/types.js +7 -0
  44. package/dist/formatters/types.d.ts +57 -0
  45. package/dist/formatters/types.js +7 -0
  46. package/dist/index.d.ts +18 -0
  47. package/dist/index.js +41 -0
  48. package/dist/notebook-formatter.d.ts +107 -0
  49. package/dist/notebook-formatter.js +424 -0
  50. package/package.json +63 -0
package/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # fabric-format
2
+
3
+ A zero-config formatter for **Microsoft Fabric notebooks**.
4
+
5
+ ## Packages
6
+
7
+ | Package | Description |
8
+ | -------------------------------------------- | ------------------------------------------ |
9
+ | [@jacobknightley/fabric-format](./packages/core) | Core formatting library (npm package) |
10
+ | [fabric-format-chromium](./packages/chromium) | Chrome/Edge extension for Fabric notebooks |
11
+
12
+ ## Philosophy
13
+
14
+ **Opinionated by design.** This formatter has one style, enforced everywhere, with no configuration options—and no plans to add any.
15
+
16
+ Built this for teams who want consistent notebook formatting without endless debates over style guides. The decisions are made. Your code looks the same every time.
17
+
18
+ The focus is on clean, consistent output—not tailored experiences or nuanced edge cases.
19
+
20
+ ## CLI
21
+
22
+ ### Installation
23
+
24
+ ```bash
25
+ npm install -g @jacobknightley/fabric-format
26
+ ```
27
+
28
+ ### Usage
29
+
30
+ ```bash
31
+ # format
32
+ fabfmt format notebook.py # Format a single file
33
+ fabfmt format ./src # Format all files in directory
34
+ fabfmt format query.sql --print # Print formatted output
35
+ fabfmt format --type sparksql -i "select * from t" # Format inline string
36
+ echo "select * from t" | fabfmt format --type sparksql # Format from stdin
37
+
38
+ # check (exit 1 if changes needed)
39
+ fabfmt check notebook.py # Check a single file
40
+ fabfmt check ./src # Check all files in directory
41
+ fabfmt check --type sparksql -i "select * from t" # Check inline string
42
+ echo "select * from t" | fabfmt check --type sparksql # Check from stdin
43
+ ```
44
+
45
+ ## Browser Extension
46
+
47
+ Format Fabric notebooks directly in your browser with a single click.
48
+
49
+ ### Installation
50
+
51
+ 1. Download `fabric-format-chromium.zip` from the [latest release](https://github.com/jacobknightley/fabric-format/releases)
52
+ 2. Extract the zip file
53
+ 3. Open your browser's extension page:
54
+ - **Chrome:** `chrome://extensions`
55
+ - **Edge:** `edge://extensions`
56
+ 4. Enable **Developer mode** (toggle in top-right)
57
+ 5. Click **Load unpacked** and select the extracted folder
58
+
59
+ > **Note:** Plan to eventually support directly in Chrome and Edge extension stores.
60
+
61
+ ### Usage
62
+
63
+ 1. Open a notebook in Microsoft Fabric
64
+ 2. Click the **Format** button in the notebook toolbar
65
+
66
+ ![Format button in Fabric notebook toolbar](assets/extension-format-button.png)
67
+
68
+ 3. All cells in the notebook are formatted instantly
69
+
70
+ ## Supported File Types
71
+
72
+ - `.py` — Python notebooks
73
+ - `.scala` — Scala notebooks
74
+ - `.r` — R notebooks
75
+ - `.sql` — SQL notebooks
76
+
77
+ ## Supported Languages
78
+
79
+ - Spark SQL
80
+ - Python
81
+
82
+ > **Note:** All other language cells are preserved as-is.
83
+
84
+ ### Spark SQL
85
+
86
+ ---
87
+
88
+ Custom formatter built on [Apache Spark's official ANTLR grammar](https://github.com/apache/spark/tree/master/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser). If Spark supports the syntax, fabric-format formats it correctly.
89
+
90
+ #### Style Overview
91
+
92
+ | Element | Formatting |
93
+ | ---------------------- | -------------------------- |
94
+ | Keywords | `UPPERCASE` |
95
+ | Built-in functions | `UPPERCASE()` |
96
+ | User-defined functions | `preserveCase()` |
97
+ | Identifiers | `preserveCase` |
98
+ | Indentation | 4 spaces |
99
+ | Expression line width | 140 characters (then wrap) |
100
+ | Commas | Leading (comma-first) |
101
+
102
+ See [SQL_STYLE_GUIDE.md](./SQL_STYLE_GUIDE.md) for complete rules and examples.
103
+
104
+ #### Format Directives
105
+
106
+ ##### `fmt: off`
107
+
108
+ Skip formatting entirely—preserves original whitespace and casing. Applicable only to the statement directly after it.
109
+
110
+ ```sql
111
+ -- fmt: off
112
+ select Col_A,Col_B B,Col_C from t;
113
+ select Col_A,Col_B B,Col_C from t;
114
+ ```
115
+
116
+ ⬇️ Output
117
+
118
+ ```sql
119
+ -- fmt: off
120
+ select Col_A,Col_B B,Col_C from t;
121
+
122
+ SELECT
123
+ Col_A
124
+ ,Col_B AS B
125
+ ,Col_C
126
+ FROM t;
127
+ ```
128
+
129
+ ##### `fmt: inline`
130
+
131
+ Suppress line wrapping for long expressions that are wrapped by default at 140 characters.
132
+
133
+ ```sql
134
+ SELECT
135
+ conv(right(md5(upper(concat(coalesce(VeryLongTable.VeryLongColumnName, AnotherLongAlias.AnotherLongColumn), SomeOtherReallyLongColumnName))), 16), 16, -10) AS A-- fmt: inline
136
+ ,conv(right(md5(upper(concat(coalesce(VeryLongTable.VeryLongColumnName, AnotherLongAlias.AnotherLongColumn), SomeOtherReallyLongColumnName))), 16), 16, -10) AS B
137
+ FROM t
138
+ ```
139
+
140
+ ⬇️ Output
141
+
142
+ ```sql
143
+ SELECT
144
+ CONV(RIGHT(MD5(UPPER(CONCAT(COALESCE(VeryLongTable.VeryLongColumnName, AnotherLongAlias.AnotherLongColumn), SomeOtherReallyLongColumnName))), 16), 16, -10) AS A -- fmt: inline
145
+ ,CONV(
146
+ RIGHT(
147
+ MD5(UPPER(CONCAT(
148
+ COALESCE(VeryLongTable.VeryLongColumnName, AnotherLongAlias.AnotherLongColumn)
149
+ ,SomeOtherReallyLongColumnName
150
+ )))
151
+ ,16
152
+ )
153
+ ,16
154
+ ,-10
155
+ ) AS B
156
+ FROM t
157
+ ```
158
+
159
+ ### Python
160
+
161
+ ---
162
+
163
+ Formatted via [Ruff](https://docs.astral.sh/ruff/) with sensible defaults:
164
+
165
+ - 140 character line width
166
+ - 4-space indentation
167
+ - Double quotes
168
+ - PEP 8 compliant
169
+
170
+ Magic commands (`%%sql`, `%run`, etc.) are preserved.
171
+
172
+ #### Format Directives
173
+
174
+ ##### `fmt: off` / `fmt: on`
175
+
176
+ Disable formatting for a block of code:
177
+
178
+ ```python
179
+ # fmt: off
180
+ matrix = [
181
+ 1, 0, 0,
182
+ 0, 1, 0,
183
+ 0, 0, 1,
184
+ ]
185
+ # fmt: on
186
+ ```
187
+
188
+ ##### `fmt: skip`
189
+
190
+ Skip formatting for a single statement:
191
+
192
+ ```python
193
+ result = some_function(a, b, c,d, e) # fmt: skip
194
+ ```
195
+
196
+ See [Ruff's documentation](https://docs.astral.sh/ruff/formatter/#format-suppression) for more details.
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Cell Formatter
3
+ *
4
+ * Low-level API for formatting raw cell content by type.
5
+ * Use this when you already know the cell type (e.g., from a Chrome extension
6
+ * that reads the cell metadata directly).
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { formatCell, formatCellSync, initializePythonFormatter } from '@jacobknightley/fabric-format';
11
+ *
12
+ * // Format Spark SQL (synchronous)
13
+ * const result = formatCellSync('select * from table', 'sparksql');
14
+ * console.log(result.formatted); // 'SELECT * FROM table'
15
+ *
16
+ * // Format Python (async - needs initialization)
17
+ * await initializePythonFormatter();
18
+ * const result = formatCell('x=1', 'python');
19
+ * console.log(result.formatted); // 'x = 1'
20
+ * ```
21
+ */
22
+ import { type WasmInitOptions } from './formatters/python/index.js';
23
+ /** Result from formatCell */
24
+ export interface FormatCellResult {
25
+ /** The formatted content */
26
+ formatted: string;
27
+ /** Whether the content was changed */
28
+ changed: boolean;
29
+ /** Error message if formatting failed */
30
+ error?: string;
31
+ }
32
+ /** Supported cell types for formatCell */
33
+ export type CellType = 'sparksql' | 'python' | 'pyspark';
34
+ /**
35
+ * Initialize the Python formatter (must be called before formatting Python cells).
36
+ * This is async because the Ruff WASM module needs to be loaded.
37
+ *
38
+ * @param options - Optional WASM initialization options for browser environments.
39
+ * - wasmUrl: URL to the .wasm file (use this in Chrome extensions with chrome.runtime.getURL)
40
+ * - wasmBinary: WASM binary as ArrayBuffer or Uint8Array (for sync initialization)
41
+ */
42
+ export declare function initializePythonFormatter(options?: WasmInitOptions): Promise<void>;
43
+ /**
44
+ * Check if Python formatter is ready.
45
+ */
46
+ export declare function isPythonFormatterReady(): boolean;
47
+ /**
48
+ * Format a single cell's content based on its type.
49
+ *
50
+ * This is the low-level API for formatting raw code content.
51
+ * Use this when you already know the cell type (e.g., from a Chrome extension
52
+ * that reads the cell metadata directly).
53
+ *
54
+ * @param content Raw cell content (no comment wrappers like `# MAGIC`)
55
+ * @param cellType The cell type from metadata (e.g., 'sparksql', 'python', 'pyspark')
56
+ * @returns Formatted content and status
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * // Format Spark SQL
61
+ * const result = formatCell('select * from table', 'sparksql');
62
+ * console.log(result.formatted); // 'SELECT * FROM table'
63
+ *
64
+ * // Format Python (must initialize first)
65
+ * await initializePythonFormatter();
66
+ * const result = formatCell('x=1', 'python');
67
+ * console.log(result.formatted); // 'x = 1'
68
+ * ```
69
+ */
70
+ export declare function formatCell(content: string, cellType: CellType): FormatCellResult;
71
+ /**
72
+ * Synchronous version of formatCell for Spark SQL only.
73
+ * Use this when you only need SQL formatting and don't want async.
74
+ */
75
+ export declare function formatCellSync(content: string, cellType: 'sparksql'): FormatCellResult;
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Cell Formatter
3
+ *
4
+ * Low-level API for formatting raw cell content by type.
5
+ * Use this when you already know the cell type (e.g., from a Chrome extension
6
+ * that reads the cell metadata directly).
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { formatCell, formatCellSync, initializePythonFormatter } from '@jacobknightley/fabric-format';
11
+ *
12
+ * // Format Spark SQL (synchronous)
13
+ * const result = formatCellSync('select * from table', 'sparksql');
14
+ * console.log(result.formatted); // 'SELECT * FROM table'
15
+ *
16
+ * // Format Python (async - needs initialization)
17
+ * await initializePythonFormatter();
18
+ * const result = formatCell('x=1', 'python');
19
+ * console.log(result.formatted); // 'x = 1'
20
+ * ```
21
+ */
22
+ import { getFormatterRegistry } from './formatters/index.js';
23
+ import { formatSql as formatSqlDirect } from './formatters/sparksql/index.js';
24
+ import { getPythonFormatter, resetPythonFormatter } from './formatters/python/index.js';
25
+ // ============================================================================
26
+ // Python Formatter State
27
+ // ============================================================================
28
+ /** State for Python formatter initialization */
29
+ let pythonFormatterReady = false;
30
+ let pythonFormatterInitPromise = null;
31
+ /**
32
+ * Initialize the Python formatter (must be called before formatting Python cells).
33
+ * This is async because the Ruff WASM module needs to be loaded.
34
+ *
35
+ * @param options - Optional WASM initialization options for browser environments.
36
+ * - wasmUrl: URL to the .wasm file (use this in Chrome extensions with chrome.runtime.getURL)
37
+ * - wasmBinary: WASM binary as ArrayBuffer or Uint8Array (for sync initialization)
38
+ */
39
+ export async function initializePythonFormatter(options) {
40
+ if (pythonFormatterReady)
41
+ return;
42
+ if (pythonFormatterInitPromise)
43
+ return pythonFormatterInitPromise;
44
+ pythonFormatterInitPromise = (async () => {
45
+ // If options provided, reset the formatter to use new options
46
+ if (options) {
47
+ resetPythonFormatter();
48
+ }
49
+ // Get or create the formatter with options
50
+ const pythonFormatter = getPythonFormatter(options);
51
+ // Re-register in the registry so formatCell uses the correct instance
52
+ const registry = getFormatterRegistry();
53
+ registry.register(pythonFormatter);
54
+ if (!pythonFormatter.isReady()) {
55
+ await pythonFormatter.initialize();
56
+ }
57
+ pythonFormatterReady = true;
58
+ })();
59
+ return pythonFormatterInitPromise;
60
+ }
61
+ /**
62
+ * Check if Python formatter is ready.
63
+ */
64
+ export function isPythonFormatterReady() {
65
+ return pythonFormatterReady;
66
+ }
67
+ // ============================================================================
68
+ // Cell Formatting API
69
+ // ============================================================================
70
+ /**
71
+ * Format a single cell's content based on its type.
72
+ *
73
+ * This is the low-level API for formatting raw code content.
74
+ * Use this when you already know the cell type (e.g., from a Chrome extension
75
+ * that reads the cell metadata directly).
76
+ *
77
+ * @param content Raw cell content (no comment wrappers like `# MAGIC`)
78
+ * @param cellType The cell type from metadata (e.g., 'sparksql', 'python', 'pyspark')
79
+ * @returns Formatted content and status
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * // Format Spark SQL
84
+ * const result = formatCell('select * from table', 'sparksql');
85
+ * console.log(result.formatted); // 'SELECT * FROM table'
86
+ *
87
+ * // Format Python (must initialize first)
88
+ * await initializePythonFormatter();
89
+ * const result = formatCell('x=1', 'python');
90
+ * console.log(result.formatted); // 'x = 1'
91
+ * ```
92
+ */
93
+ export function formatCell(content, cellType) {
94
+ const type = cellType.toLowerCase();
95
+ switch (type) {
96
+ case 'sparksql':
97
+ try {
98
+ const formatted = formatSqlDirect(content);
99
+ return {
100
+ formatted,
101
+ changed: formatted !== content,
102
+ };
103
+ }
104
+ catch (error) {
105
+ return {
106
+ formatted: content,
107
+ changed: false,
108
+ error: `Spark SQL format error: ${error}`,
109
+ };
110
+ }
111
+ case 'python':
112
+ case 'pyspark':
113
+ const registry = getFormatterRegistry();
114
+ const pythonFormatter = registry.get('python');
115
+ if (!pythonFormatter?.isReady()) {
116
+ return {
117
+ formatted: content,
118
+ changed: false,
119
+ error: 'Python formatter not initialized. Call initializePythonFormatter() first.',
120
+ };
121
+ }
122
+ const result = pythonFormatter.format(content, {
123
+ stripTrailingNewline: true,
124
+ });
125
+ return {
126
+ formatted: result.formatted,
127
+ changed: result.changed,
128
+ error: result.error,
129
+ };
130
+ default:
131
+ // Unsupported cell type - return unchanged
132
+ return {
133
+ formatted: content,
134
+ changed: false,
135
+ };
136
+ }
137
+ }
138
+ /**
139
+ * Synchronous version of formatCell for Spark SQL only.
140
+ * Use this when you only need SQL formatting and don't want async.
141
+ */
142
+ export function formatCellSync(content, cellType) {
143
+ return formatCell(content, cellType);
144
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};