@chilts/weave 0.6.0 → 0.7.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/CHANGELOG.md ADDED
@@ -0,0 +1,178 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@chilts/weave` are documented here.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ While the version is below `1.0.0`, breaking changes may land in minor releases.
8
+
9
+ ## [Unreleased]
10
+
11
+ ## [0.7.0] - 2026-10-20
12
+
13
+ ### Added
14
+
15
+ - **Escaped dots in class and id shorthand.** `.gap-1\.5` is the single class
16
+ `gap-1.5`, so Tailwind's fractional spacing classes work as shorthand.
17
+ - **Multi-line parameter lists.** The parentheses after `template`, `mixin`,
18
+ `extends`, `for` and a mixin call may now wrap over several lines, staying
19
+ open until they balance, so a long destructured signature can be written
20
+ the way TypeScript is normally wrapped. Continuation lines must be indented
21
+ under the opening line (a closing line such as `}: Props)` may sit level with
22
+ it). A string inside must close on its own line, and `//` comments are
23
+ dropped from the output. The compiled code keeps the wrapped layout, adding
24
+ `attrs`/`blocks` on a line of their own when the `)` was on its own line.
25
+ - **Emacs mode understands context.** `weave-mode` now follows multi-line
26
+ attribute and parameter lists and raw (`tag!`) blocks when highlighting and
27
+ indenting, highlights attribute names, aligns `else` with its `if`, and
28
+ cycles outwards on repeated TAB. An apostrophe in text no longer opens a
29
+ string, `//` in a URL is no longer a comment, and `.`/`#` are only coloured
30
+ as a class or id where they really are one.
31
+
32
+ ### Fixed
33
+
34
+ - A dotted class such as `.gap-1.5` was silently split into `gap-1` plus a
35
+ stray class `5`. A shorthand class that is a bare number is now a
36
+ `LexerError` that names the split and suggests escaping the dot.
37
+ - `include ./partials` imported a binding named after the file (`partials`)
38
+ instead of the mixins the template calls, so a file defining several
39
+ mixins, or one not named after its file, compiled to code that failed in
40
+ `tsc`. The compiler now reads the included file and imports only the mixins
41
+ called (`import { badge, chip } from './partials.js'`). Calling a mixin that
42
+ no include or local definition provides is a `CodegenError`. For API users,
43
+ `compile()`, `compileWithSourceMap()` and `generate()` take an optional
44
+ `resolveInclude`. Without it, a lone include provides every called mixin
45
+ that isn't defined locally, and several includes are an error.
46
+ - Shorthand classes combined with a non-literal `class=` value leaked the
47
+ value's string form: `div.pt-4(class=undefined)` rendered
48
+ `class="pt-4 undefined"`, as did the usual conditional-class pattern
49
+ `class=(on ? "x" : undefined)` whenever it was off. Non-literal values are
50
+ now merged at render time by a new `mergeClass()` runtime helper, which
51
+ drops `undefined`, `null`, `false` and `""`. An attribute whose value is
52
+ `null` is now omitted instead of rendering as `"null"`.
53
+ - A template literal with a `${...}` interpolation inside a mixin call's
54
+ arguments, or a `template`/`mixin` parameter list, failed with
55
+ "Unterminated parentheses". A string containing a brace inside the
56
+ interpolation is also handled.
57
+ - Error locations and source maps now point at the start of a token rather
58
+ than just past its end — for example, a mixin call is located at its `+`
59
+ instead of its argument list.
60
+ - In watch mode, changing an included file now recompiles the templates that
61
+ include it, so a newly added mixin is picked up without touching each page.
62
+ The start-up reconcile likewise recompiles a template whose include is
63
+ newer than its output.
64
+ - Rebuilding with `--no-sourcemap` now removes a `.ts.map` left over from an
65
+ earlier build instead of leaving a map that no longer matches its `.ts`.
66
+
67
+ ## [0.6.0] - 2026-10-19
68
+
69
+ ### Added
70
+
71
+ - **Multi-line attribute lists.** End a line with the opening `(` to spread an
72
+ element's attributes over several lines — one attribute per line, indented
73
+ under the element, closed by a `)` on a line of its own. Works for mixin call
74
+ attributes too. Anything after the closing `)` is read as trailing content.
75
+ Attribute values follow exactly the same rules as the inline form.
76
+
77
+ ### Changed
78
+
79
+ - **Breaking: every attribute must have a value.** The Pug-style valueless
80
+ boolean shorthand has been removed — `input(disabled)` no longer compiles.
81
+ Boolean attributes are now written with an explicit boolean value:
82
+ `disabled=true`, `disabled=false`, or an expression `disabled=isActive`
83
+ (the runtime renders a bare attribute for `true` and omits it for `false`
84
+ or `undefined`). Do not use a quoted `disabled="false"` — in HTML any
85
+ attribute that is present counts as true.
86
+ - A bare (unquoted) attribute value must be a single token. Multi-token
87
+ expressions such as a ternary or string concatenation must be wrapped in
88
+ parentheses (`class=(wide ? "a" : "b")`) or backticks
89
+ (``class=`${wide ? "a" : "b"}` ``), because a space separates one
90
+ attribute from the next.
91
+
92
+ ### Fixed
93
+
94
+ - The lexer no longer hangs and aborts the process (SIGABRT) on a multi-token
95
+ bare attribute value; it now raises a clear `LexerError` with line and
96
+ column, pointing you to wrap the expression.
97
+ - `main(class=a b)` is rejected with a message offering both readings
98
+ (`class="a b"` as one value, or `class=a b="..."` as two attributes)
99
+ instead of silently parsing as `class="a"` plus a boolean `b`.
100
+ - An attribute list that ran off the end of its line was accepted as if it had
101
+ been closed, so the following lines were lexed as child elements — attribute
102
+ names became tags and their values rendered as visible text, and the stray
103
+ `)` could leak into the generated TypeScript. It is now a `LexerError`
104
+ anchored on the opening `(`.
105
+ - Watch mode reconciles the output tree on start so edits made while the
106
+ watcher was down are not missed.
107
+ - Watch mode on Linux stopped seeing a template after the first time an editor
108
+ saved it atomically (write a temp file, rename it over the original), so
109
+ later edits were never recompiled. Each directory now has its own watcher,
110
+ which survives atomic saves, and subdirectories created while watching are
111
+ picked up — including any templates already written into them.
112
+ - Deleting a `.weave` template now removes its compiled `.ts` from the output
113
+ directory (in one-shot builds and in watch mode). The output directory is
114
+ treated as owned by Weave: any `.ts` with no matching `.weave` is removed.
115
+ - A mixin, or a block's default content, with more than one root element
116
+ referenced `Fragment` without importing it, so the compiled file failed to
117
+ type-check. `Fragment` is now imported whenever it is used.
118
+
119
+ ### Removed
120
+
121
+ - Source maps are no longer included in the published npm bundle.
122
+
123
+ ## [0.5.0] - 2026-06-11
124
+
125
+ ### Fixed
126
+
127
+ - Multi-level `extends` and `doctype` now generate type-safe output.
128
+
129
+ ## [0.4.0] - 2026-04-05
130
+
131
+ ### Changed
132
+
133
+ - Compiled templates return strings directly instead of arrays, simplifying
134
+ the generated output and how templates compose.
135
+
136
+ ## [0.3.0] - 2026-04-05
137
+
138
+ ### Added
139
+
140
+ - Raw block syntax (`tag!`) for passing literal, unescaped content through to
141
+ the output (inline CSS, JavaScript, etc.).
142
+ - Emacs major mode with syntax highlighting, indentation, and comment support.
143
+
144
+ ### Documentation
145
+
146
+ - Added guidance on special characters and the single-pass escaping model to
147
+ the README.
148
+
149
+ ## [0.2.0] - 2026-04-05
150
+
151
+ ### Added
152
+
153
+ - A bare `|` (pipe) on its own line emits a single space, for adding
154
+ whitespace between sibling elements.
155
+
156
+ ## [0.1.0] - 2026-04-05
157
+
158
+ Initial release — the Weave compiler and runtime.
159
+
160
+ ### Added
161
+
162
+ - Template syntax compiling `.weave` files to typed TypeScript functions:
163
+ templates, mixins, `extends`/`block` layouts, conditionals, iteration,
164
+ includes, interpolation, buffered expressions, and silent/pass-through
165
+ comments.
166
+ - Runtime helpers: `h`, `doctype`, `Fragment`, `raw`, and single-pass escaping.
167
+ - CLI `compile` command and a `--watch` mode for automatic recompilation.
168
+ - Error messages with source code frames.
169
+ - Source map generation.
170
+
171
+ [Unreleased]: https://codeberg.org/chilts/weave/compare/v0.7.0...HEAD
172
+ [0.7.0]: https://codeberg.org/chilts/weave/compare/v0.6.0...v0.7.0
173
+ [0.6.0]: https://codeberg.org/chilts/weave/compare/v0.5.0...v0.6.0
174
+ [0.5.0]: https://codeberg.org/chilts/weave/compare/v0.4.0...v0.5.0
175
+ [0.4.0]: https://codeberg.org/chilts/weave/compare/v0.3.0...v0.4.0
176
+ [0.3.0]: https://codeberg.org/chilts/weave/compare/v0.2.0...v0.3.0
177
+ [0.2.0]: https://codeberg.org/chilts/weave/compare/v0.1.0...v0.2.0
178
+ [0.1.0]: https://codeberg.org/chilts/weave/src/tag/v0.1.0
package/ReadMe.md CHANGED
@@ -12,7 +12,7 @@ Pug is great. Weave takes the same indentation-based syntax and makes it TypeScr
12
12
 
13
13
  - **Type-safe templates** — template parameters are real TypeScript types
14
14
  - **Type-safe mixins** — positional args with TS type annotations
15
- - **Tailwind-friendly** — class names with colons (`.md:flex`, `.hover:bg-blue-500`) just work
15
+ - **Tailwind-friendly** — class names with colons (`.md:flex`, `.hover:bg-blue-500`) just work, and dots escape (`.gap-1\.5`)
16
16
  - **Single-pass escaping** — values are escaped once at render time, never pre-escaped
17
17
  - **No runtime template engine** — compiled output is plain TypeScript
18
18
 
@@ -97,6 +97,14 @@ p.intro This is a paragraph with a class
97
97
  a(href="/about" class="nav-link") About
98
98
  ```
99
99
 
100
+ A `.` always starts a new class, so a class name that contains a dot — like Tailwind's `gap-1.5` — must escape it with a backslash:
101
+
102
+ ```pug
103
+ .flex.gap-1\.5.px-2\.5
104
+ ```
105
+
106
+ An unescaped `.gap-1.5` would split into `gap-1` and a stray `5`, so a class that is a bare number is rejected with an error pointing at the fix.
107
+
100
108
  ### Attributes
101
109
 
102
110
  Attributes live inside parentheses and are separated by spaces. **Every attribute must have a value** — there is no valueless shorthand:
@@ -115,7 +123,7 @@ button(class=`btn-${variant}`)= label
115
123
 
116
124
  #### Boolean attributes
117
125
 
118
- Write the boolean explicitly. The runtime renders `true` as a bare attribute and omits the attribute entirely when the value is `false` or `undefined`:
126
+ Write the boolean explicitly. The runtime renders `true` as a bare attribute and omits the attribute entirely when the value is `false`, `undefined` or `null`:
119
127
 
120
128
  ```
121
129
  input(type="checkbox" checked=true) → <input type="checkbox" checked>
@@ -125,6 +133,16 @@ input(type="checkbox" checked=isActive) → omitted when isActive is false
125
133
 
126
134
  Avoid a quoted `checked="false"` — in HTML any attribute that is present counts as true, so that renders as checked.
127
135
 
136
+ #### Combining shorthand classes with `class=`
137
+
138
+ Shorthand classes and a `class=` value are merged into one attribute. The shorthand classes are always kept; a value of `undefined`, `null`, `false` or `""` just adds nothing:
139
+
140
+ ```
141
+ div.pt-4(class="mt-2") → <div class="pt-4 mt-2">
142
+ div.pt-4(class=undefined) → <div class="pt-4">
143
+ div.btn(class=(active ? "active" : undefined)) → <div class="btn"> when active is false
144
+ ```
145
+
128
146
  #### Multi-token values must be wrapped
129
147
 
130
148
  Because a space separates one attribute from the next, a bare (unquoted) value has to be a single token. Wrap anything longer in parentheses or backticks:
@@ -244,6 +262,32 @@ template home({ title, welcomeMessage }: HomeProps)
244
262
 
245
263
  Blocks can have default content in the layout. If a page doesn't override a block, the default renders.
246
264
 
265
+ ### Long parameter lists
266
+
267
+ The parentheses after `template`, `mixin`, `extends`, `for` and a mixin call hold
268
+ TypeScript, which Weave copies through untouched. They can wrap over several lines
269
+ however TypeScript usually wraps: the group simply stays open until its parentheses
270
+ balance.
271
+
272
+ ```
273
+ template dashboard({
274
+ title,
275
+ user,
276
+ }: DashboardProps)
277
+ h1= title
278
+
279
+ +card(
280
+ post.title,
281
+ post.summary,
282
+ )(class="mt-4")
283
+ ```
284
+
285
+ Unlike a long attribute list there is no required shape, but continuation lines must
286
+ be indented under the line that opens the group. The closing line may sit level with
287
+ it if it starts with a closing bracket, as `}: DashboardProps)` does. So a forgotten
288
+ `)` is reported where the indentation drops back. A string must close on the line it
289
+ starts on, and `//` comments are allowed but left out of the compiled output.
290
+
247
291
  ### Includes
248
292
 
249
293
  Include another `.weave` file to use its mixins:
@@ -258,6 +302,12 @@ template actionsPage({ title }: ActionsPageProps)
258
302
  +button("Save", "primary")
259
303
  ```
260
304
 
305
+ An included file can define any number of mixins. Weave reads it and imports
306
+ only the ones the template calls, so `include ./partials` followed by
307
+ `+badge(...)` and `+chip(...)` compiles to
308
+ `import { badge, chip } from './partials.js'`. Calling a mixin that is neither
309
+ defined in the file nor provided by an include is a compile error.
310
+
261
311
  ### Text content
262
312
 
263
313
  Use `|` (pipe) to add literal text as a child of an element:
@@ -341,6 +391,7 @@ See [CHANGELOG.md](CHANGELOG.md) for the full details of each release.
341
391
 
342
392
  | Version | Date | Highlights |
343
393
  |---------|------------|------------|
394
+ | 0.7.0 | 2026-10-20 | Multi-line parameter lists; escaped dots in class shorthand; `include` imports the mixins it provides; context-aware Emacs mode |
344
395
  | 0.6.0 | 2026-10-19 | Multi-line attribute lists; every attribute requires a value (breaking); watch-mode fixes |
345
396
  | 0.5.0 | 2026-06-11 | Type-safe multi-level `extends` and `doctype` |
346
397
  | 0.4.0 | 2026-04-05 | Templates return strings directly |
@@ -1,3 +1,4 @@
1
+ import type { IncludeResolver } from '../codegen/index.js';
1
2
  interface CompileArgs {
2
3
  mode: 'compile';
3
4
  inputPath: string;
@@ -24,6 +25,21 @@ export declare function pruneOrphans(inputDir: string, outputDir: string): void;
24
25
  export declare function formatElapsed(ms: number): string;
25
26
  export declare function formatCodeFrame(source: string, line: number, col: number): string;
26
27
  export declare function formatError(filePath: string, err: unknown, source?: string): string;
28
+ /** Resolves `include` paths against the including file and reads their mixins. */
29
+ export declare function includeResolver(inputFile: string): IncludeResolver;
30
+ /**
31
+ * Absolute paths of the .weave files a template includes. A template that does
32
+ * not lex or parse includes nothing as far as the watcher is concerned — its
33
+ * own compile reports the error.
34
+ */
35
+ export declare function includedFiles(inputFile: string, source: string): string[];
36
+ /**
37
+ * Templates under inputDir (relative paths) that include relPath. An includer's
38
+ * import line is built from the mixins its includes define, so it must be
39
+ * recompiled when one of them changes. Includes do not re-export, so only
40
+ * direct includers are affected.
41
+ */
42
+ export declare function findIncluders(inputDir: string, relPath: string): string[];
27
43
  export declare function compileFile(inputFile: string, outputFile: string, sourcemap?: boolean): void;
28
44
  export declare function compileDir(inputDir: string, outputDir: string, sourcemap?: boolean): boolean;
29
45
  export declare function watchDir(inputDir: string, outputDir: string, sourcemap?: boolean): () => void;
package/dist/cli/index.js CHANGED
@@ -1,8 +1,8 @@
1
- import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync, unlinkSync, rmdirSync, watch, } from 'node:fs';
1
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync, existsSync, unlinkSync, rmdirSync, rmSync, watch, } from 'node:fs';
2
2
  import path from 'node:path';
3
- import { compile, compileWithSourceMap } from '../codegen/index.js';
4
- import { LexerError } from '../lexer/index.js';
5
- import { ParserError } from '../parser/index.js';
3
+ import { compile, compileWithSourceMap, definedMixins } from '../codegen/index.js';
4
+ import { lex, LexerError } from '../lexer/index.js';
5
+ import { parse, ParserError } from '../parser/index.js';
6
6
  import { CodegenError } from '../codegen/index.js';
7
7
  export class UsageError extends Error {
8
8
  constructor(message) {
@@ -173,6 +173,69 @@ export function formatError(filePath, err, source) {
173
173
  }
174
174
  return result;
175
175
  }
176
+ /** Resolves `include` paths against the including file and reads their mixins. */
177
+ export function includeResolver(inputFile) {
178
+ return includePath => {
179
+ const file = path.resolve(path.dirname(inputFile), includePath + '.weave');
180
+ let source;
181
+ try {
182
+ source = readFileSync(file, 'utf-8');
183
+ }
184
+ catch (err) {
185
+ if (err instanceof Error && 'code' in err && err.code === 'ENOENT') {
186
+ throw new Error(`${path.relative(process.cwd(), file)}: no such file`);
187
+ }
188
+ throw err;
189
+ }
190
+ return definedMixins(parse(lex(source)));
191
+ };
192
+ }
193
+ /**
194
+ * Absolute paths of the .weave files a template includes. A template that does
195
+ * not lex or parse includes nothing as far as the watcher is concerned — its
196
+ * own compile reports the error.
197
+ */
198
+ export function includedFiles(inputFile, source) {
199
+ if (!/^include\s/m.test(source))
200
+ return [];
201
+ let doc;
202
+ try {
203
+ doc = parse(lex(source));
204
+ }
205
+ catch {
206
+ return [];
207
+ }
208
+ return doc.children.flatMap(c => c.type === 'Include' ? [path.resolve(path.dirname(inputFile), c.path + '.weave')] : []);
209
+ }
210
+ /**
211
+ * Templates under inputDir (relative paths) that include relPath. An includer's
212
+ * import line is built from the mixins its includes define, so it must be
213
+ * recompiled when one of them changes. Includes do not re-export, so only
214
+ * direct includers are affected.
215
+ */
216
+ export function findIncluders(inputDir, relPath) {
217
+ const target = path.resolve(inputDir, relPath);
218
+ let files;
219
+ try {
220
+ files = discoverFiles(inputDir);
221
+ }
222
+ catch {
223
+ return [];
224
+ }
225
+ return files.filter(file => {
226
+ if (file === relPath)
227
+ return false;
228
+ const inFile = path.join(inputDir, file);
229
+ let source;
230
+ try {
231
+ source = readFileSync(inFile, 'utf-8');
232
+ }
233
+ catch {
234
+ return false;
235
+ }
236
+ return includedFiles(inFile, source).includes(target);
237
+ });
238
+ }
176
239
  function writeCompiled(inputFile, outputFile, source, sourcemap) {
177
240
  const start = performance.now();
178
241
  if (sourcemap) {
@@ -181,15 +244,19 @@ function writeCompiled(inputFile, outputFile, source, sourcemap) {
181
244
  const { code, sourceMap } = compileWithSourceMap(source, {
182
245
  file: basename,
183
246
  sourceFile: sourceRelPath,
247
+ resolveInclude: includeResolver(inputFile),
184
248
  });
185
249
  mkdirSync(path.dirname(outputFile), { recursive: true });
186
250
  writeFileSync(outputFile, code + `//# sourceMappingURL=${basename}.map\n`);
187
251
  writeFileSync(outputFile + '.map', JSON.stringify(sourceMap));
188
252
  }
189
253
  else {
190
- const result = compile(source);
254
+ const result = compile(source, { resolveInclude: includeResolver(inputFile) });
191
255
  mkdirSync(path.dirname(outputFile), { recursive: true });
192
256
  writeFileSync(outputFile, result);
257
+ // A map from an earlier sourcemap build no longer matches this .ts,
258
+ // and pruning keeps it because the .weave still exists.
259
+ rmSync(outputFile + '.map', { force: true });
193
260
  }
194
261
  return performance.now() - start;
195
262
  }
@@ -259,6 +326,13 @@ export function watchDir(inputDir, outputDir, sourcemap = true) {
259
326
  pending.set(relPath, setTimeout(() => {
260
327
  pending.delete(relPath);
261
328
  compileWatchedFile(inputDir, outputDir, relPath, sourcemap);
329
+ // Compiled directly rather than scheduled, so an include cycle
330
+ // cannot keep re-triggering itself. One with its own pending
331
+ // timer will be compiled by that.
332
+ for (const includer of findIncluders(inputDir, relPath)) {
333
+ if (!pending.has(includer))
334
+ compileWatchedFile(inputDir, outputDir, includer, sourcemap);
335
+ }
262
336
  }, 50));
263
337
  };
264
338
  const isDir = (relPath) => {
@@ -355,9 +429,9 @@ export function watchDir(inputDir, outputDir, sourcemap = true) {
355
429
  };
356
430
  }
357
431
  // Walk the input dir and recompile any .weave whose .ts output is missing or
358
- // older than the source. Files with a pending debounce timer are skipped so we
359
- // never race an edit the watcher is already handling — this only catches
360
- // changes whose fs.watch event was dropped.
432
+ // older than the source or any file it includes. Files with a pending debounce
433
+ // timer are skipped so we never race an edit the watcher is already handling —
434
+ // this only catches changes whose fs.watch event was dropped.
361
435
  export function reconcile(inputDir, outputDir, sourcemap, pending) {
362
436
  let files;
363
437
  try {
@@ -386,9 +460,6 @@ export function reconcile(inputDir, outputDir, sourcemap, pending) {
386
460
  catch {
387
461
  outMtime = undefined;
388
462
  }
389
- // Up to date — the watcher already handled this file.
390
- if (outMtime !== undefined && outMtime >= sourceMtime)
391
- continue;
392
463
  let source;
393
464
  try {
394
465
  source = readFileSync(inFile, 'utf-8');
@@ -396,6 +467,20 @@ export function reconcile(inputDir, outputDir, sourcemap, pending) {
396
467
  catch {
397
468
  continue;
398
469
  }
470
+ // The output also depends on the mixins each include defines, so an
471
+ // include edited since the last compile makes it stale too.
472
+ let newestMtime = sourceMtime;
473
+ for (const included of includedFiles(inFile, source)) {
474
+ try {
475
+ newestMtime = Math.max(newestMtime, statSync(included).mtimeMs);
476
+ }
477
+ catch {
478
+ // A missing include is reported when the template compiles.
479
+ }
480
+ }
481
+ // Up to date — the watcher already handled this file.
482
+ if (outMtime !== undefined && outMtime >= newestMtime)
483
+ continue;
399
484
  try {
400
485
  const elapsed = writeCompiled(inFile, outFile, source, sourcemap);
401
486
  console.log(`${outFile} (reconciled, ${formatElapsed(elapsed)})`);
@@ -5,22 +5,36 @@ export interface GenerateResult {
5
5
  code: string;
6
6
  mappings: Mapping[];
7
7
  }
8
+ /**
9
+ * Lists the mixins defined by the `.weave` file an `include` path names.
10
+ * The path is exactly as written after `include`; throw if it cannot be read.
11
+ */
12
+ export type IncludeResolver = (includePath: string) => string[];
13
+ export interface GenerateOptions {
14
+ /**
15
+ * Without a resolver, a lone `include` is assumed to provide every called
16
+ * mixin not defined in this file, and more than one `include` is an error.
17
+ */
18
+ resolveInclude?: IncludeResolver;
19
+ }
8
20
  export declare class CodegenError extends Error {
9
21
  readonly line: number;
10
22
  readonly col: number;
11
23
  constructor(message: string, line: number, col: number);
12
24
  }
25
+ /** Names of the mixins a document defines at top level. */
26
+ export declare function definedMixins(doc: DocumentNode): string[];
13
27
  /** Generate a TypeScript expression from a single AST node. */
14
28
  export declare function generateExpr(node: AstNode): string;
15
29
  /** Generate TypeScript source from a DocumentNode. */
16
- export declare function generate(doc: DocumentNode): GenerateResult;
30
+ export declare function generate(doc: DocumentNode, options?: GenerateOptions): GenerateResult;
17
31
  /** Full pipeline: source string → TypeScript source. */
18
- export declare function compile(source: string): string;
32
+ export declare function compile(source: string, options?: GenerateOptions): string;
19
33
  /** Full pipeline with source map generation. */
20
34
  export declare function compileWithSourceMap(source: string, opts: {
21
35
  file: string;
22
36
  sourceFile: string;
23
- }): {
37
+ } & GenerateOptions): {
24
38
  code: string;
25
39
  sourceMap: SourceMap;
26
40
  };