espolar 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 +140 -0
- package/dist/index.d.ts +99 -0
- package/dist/index.js +1483 -0
- package/package.json +45 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Piovium Labs
|
|
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,140 @@
|
|
|
1
|
+
# espolar
|
|
2
|
+
|
|
3
|
+
ESTree-compatible AST printer designed for JavaScript/TypeScript language tooling.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Source preservation** — untouched nodes (configurable) are printed verbatim from the original source, preserving whitespace, comments, formatting, and ASI safety.
|
|
8
|
+
- **Volar.js mappings** — generates `Mapping<Data>[]` mapping source ranges to generated ranges, with automatic merging of adjacent mappings.
|
|
9
|
+
- **Full TypeScript support** — handles all `@typescript-eslint/types` AST node types (TSESTree), compatible with acorn-typescript parsed ASTs.
|
|
10
|
+
- **Extensible** — every aspect is customizable: untouched detection, mapping data, comments injection, printer overrides per node type.
|
|
11
|
+
- **Zero runtime dependencies** — keeps the library minimal, only 10 kB gzipped.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm add espolar
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Requires Node.js >= 26.
|
|
20
|
+
|
|
21
|
+
## Usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { print } from "espolar";
|
|
25
|
+
import type { PrintOptions } from "espolar";
|
|
26
|
+
|
|
27
|
+
const ast = /* parse to AST.Program with loc/range */;
|
|
28
|
+
|
|
29
|
+
const result = print(ast, {
|
|
30
|
+
source: originalCode,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
console.log(result.code); // generated source string
|
|
34
|
+
console.log(result.mappings); // Volar.js Mapping<{}>[]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Custom printer
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { print } from "espolar";
|
|
41
|
+
|
|
42
|
+
const result = print(ast, {
|
|
43
|
+
source,
|
|
44
|
+
printers: {
|
|
45
|
+
Literal(node, ctx) {
|
|
46
|
+
ctx.write(`"overridden"`);
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Custom mapping data
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { print } from "espolar";
|
|
56
|
+
|
|
57
|
+
const result = print(ast, {
|
|
58
|
+
source,
|
|
59
|
+
getMappingData: (node) => (node ? [node.type] : []),
|
|
60
|
+
combineMappingData: (left, right) => {
|
|
61
|
+
return [...left, ...right];
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## API
|
|
67
|
+
|
|
68
|
+
### `print(node, options)`
|
|
69
|
+
|
|
70
|
+
Main entry point. Returns `PrintResult<Data>`.
|
|
71
|
+
|
|
72
|
+
#### `PrintOptions<Data>`
|
|
73
|
+
|
|
74
|
+
| Option | Type | Description |
|
|
75
|
+
| --------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
76
|
+
| `source` | `string` | The original source code (required) |
|
|
77
|
+
| `isUntouched` | `(node) => boolean \| SourceRange` | Determine if a node should be preserved from source. Default: checks `range`/`start`/`end` |
|
|
78
|
+
| `getMappingData` | `(node?) => Data` | Extract data for each mapping entry. Default: `() => ({})` |
|
|
79
|
+
| `combineMappingData` | `(left, right) => Data` | Merge data when adjacent mappings are combined. Default: returns `right` |
|
|
80
|
+
| `printers` | `Printers<Data>` | Override printers for specific `AST_NODE_TYPES` |
|
|
81
|
+
| `getLeadingComments` | `(node) => Comment[] \| undefined` | Return comments to print before a touched node |
|
|
82
|
+
| `getTrailingComments` | `(node) => Comment[] \| undefined` | Return comments to print after a touched node |
|
|
83
|
+
|
|
84
|
+
#### `PrintResult<Data>`
|
|
85
|
+
|
|
86
|
+
| Field | Type | Description |
|
|
87
|
+
| ---------- | ----------------- | ----------------------------------------- |
|
|
88
|
+
| `code` | `string` | The generated source code |
|
|
89
|
+
| `mappings` | `Mapping<Data>[]` | Volar.js source mappings (range-to-range) |
|
|
90
|
+
|
|
91
|
+
### `PrinterContext<Data>`
|
|
92
|
+
|
|
93
|
+
The context object passed to each printer function.
|
|
94
|
+
|
|
95
|
+
| Method/Property | Description |
|
|
96
|
+
| ------------------------------------------------------- | ----------------------------------------------- |
|
|
97
|
+
| `readonly source: string` | The original source string |
|
|
98
|
+
| `readonly generatedOffset: number` | Current output position |
|
|
99
|
+
| `write(text: string)` | Emit text to the output |
|
|
100
|
+
| `writeNode(node)` | Dispatch to a printer (preserving if untouched) |
|
|
101
|
+
| `writeNodeList(nodes, separator)` | Print a list with explicit separator |
|
|
102
|
+
| `writeNodeListWithSourceGaps(nodes, fallbackSeparator)` | Print a list, copying source gaps between nodes |
|
|
103
|
+
| `writePreservedNode(node)` | Force-preserve a node from source |
|
|
104
|
+
| `writeSource(start, end, data)` | Copy source range and add mapping |
|
|
105
|
+
| `getLeadingComments(node)` | Query leading comments |
|
|
106
|
+
| `getTrailingComments(node)` | Query trailing comments |
|
|
107
|
+
|
|
108
|
+
### Exported helpers
|
|
109
|
+
|
|
110
|
+
| Export | Description |
|
|
111
|
+
| --------------------------- | ------------------------------------ |
|
|
112
|
+
| `defaultIsUntouched` | Default untouched detection function |
|
|
113
|
+
| `defaultGetMappingData` | Default mapping data extractor |
|
|
114
|
+
| `defaultCombineMappingData` | Default mapping data combiner |
|
|
115
|
+
|
|
116
|
+
## Architecture
|
|
117
|
+
|
|
118
|
+
The printer works in a single pass:
|
|
119
|
+
|
|
120
|
+
1. If a node is **untouched** (has source range and `isUntouched` returns truthy), its original source text is copied verbatim with a 1:1 mapping.
|
|
121
|
+
2. Otherwise, the node is dispatched to its registered **printer function**, which recursively calls `writeNode` on children.
|
|
122
|
+
3. Adjacent untouched siblings have their mappings **merged** into a single combined range.
|
|
123
|
+
|
|
124
|
+
This design preserves original formatting for unmodified code while only reconstructing the parts that changed.
|
|
125
|
+
|
|
126
|
+
## Development
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pnpm install # Install dependencies
|
|
130
|
+
pnpm test # Run tests (vitest)
|
|
131
|
+
pnpm coverage # Run tests with V8 coverage
|
|
132
|
+
pnpm typecheck # TypeScript type checking
|
|
133
|
+
pnpm build # Build via tsdown
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Tests use `acorn` + `@sveltejs/acorn-typescript` to parse TypeScript source into AST inputs.
|
|
137
|
+
|
|
138
|
+
## License
|
|
139
|
+
|
|
140
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { Mapping } from "@volar/source-map";
|
|
2
|
+
import { AST_NODE_TYPES, TSESTree as AST } from "@typescript-eslint/types";
|
|
3
|
+
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
declare module "@typescript-eslint/types" {
|
|
6
|
+
namespace TSESTree {
|
|
7
|
+
interface NodeOrTokenData {
|
|
8
|
+
start?: number;
|
|
9
|
+
end?: number;
|
|
10
|
+
}
|
|
11
|
+
interface BabelTSFunctionSignatureBase {
|
|
12
|
+
typeAnnotation?: TSTypeAnnotation;
|
|
13
|
+
parameters?: Parameter[];
|
|
14
|
+
}
|
|
15
|
+
interface TSFunctionType extends BabelTSFunctionSignatureBase {}
|
|
16
|
+
interface TSConstructorType extends BabelTSFunctionSignatureBase {}
|
|
17
|
+
interface TSCallSignatureDeclaration extends BabelTSFunctionSignatureBase {}
|
|
18
|
+
interface TSConstructSignatureDeclaration extends BabelTSFunctionSignatureBase {}
|
|
19
|
+
interface TSIndexSignature extends BabelTSFunctionSignatureBase {}
|
|
20
|
+
interface TSMethodSignatureComputedName extends BabelTSFunctionSignatureBase {}
|
|
21
|
+
interface TSMethodSignatureNonComputedName extends BabelTSFunctionSignatureBase {}
|
|
22
|
+
interface BabelTSAbstractBase {
|
|
23
|
+
abstract?: boolean;
|
|
24
|
+
accessor?: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface MethodDefinitionComputedName extends BabelTSAbstractBase {}
|
|
27
|
+
interface MethodDefinitionNonComputedName extends BabelTSAbstractBase {}
|
|
28
|
+
interface PropertyDefinitionComputedName extends BabelTSAbstractBase {}
|
|
29
|
+
interface PropertyDefinitionNonComputedName extends BabelTSAbstractBase {}
|
|
30
|
+
interface BabelClassBase {
|
|
31
|
+
superTypeParameters?: TSTypeParameterInstantiation;
|
|
32
|
+
}
|
|
33
|
+
interface ClassDeclarationWithName extends BabelClassBase {}
|
|
34
|
+
interface ClassDeclarationWithOptionalName extends BabelClassBase {}
|
|
35
|
+
interface ClassExpression extends BabelClassBase {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
interface NodeLike {
|
|
39
|
+
type: string;
|
|
40
|
+
loc?: AST.SourceLocation | null;
|
|
41
|
+
start?: number;
|
|
42
|
+
end?: number;
|
|
43
|
+
range?: [number, number];
|
|
44
|
+
}
|
|
45
|
+
interface Comment {
|
|
46
|
+
type: "Line" | "Block";
|
|
47
|
+
value: string;
|
|
48
|
+
loc?: AST.SourceLocation;
|
|
49
|
+
start?: number;
|
|
50
|
+
end?: number;
|
|
51
|
+
range?: [number, number];
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/mappings.d.ts
|
|
55
|
+
interface SourceRange {
|
|
56
|
+
start: number;
|
|
57
|
+
end: number;
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/api.d.ts
|
|
61
|
+
interface PrintResult<Data> {
|
|
62
|
+
code: string;
|
|
63
|
+
mappings: Mapping<Data>[];
|
|
64
|
+
}
|
|
65
|
+
interface PrintOptions<Data> {
|
|
66
|
+
source: string;
|
|
67
|
+
isUntouched?: (node: AST.Node) => boolean | SourceRange;
|
|
68
|
+
getMappingData?: (node?: AST.Node | null) => Data;
|
|
69
|
+
combineMappingData?: (left: Data, right: Data) => Data;
|
|
70
|
+
printers?: Printers<Data>;
|
|
71
|
+
getLeadingComments?: (node: AST.Node) => Comment[] | undefined;
|
|
72
|
+
getTrailingComments?: (node: AST.Node) => Comment[] | undefined;
|
|
73
|
+
}
|
|
74
|
+
interface PrinterContext<Data> {
|
|
75
|
+
readonly source: string;
|
|
76
|
+
readonly generatedOffset: number;
|
|
77
|
+
write(text: string): void;
|
|
78
|
+
writeNode(node: AST.Node | null | undefined): void;
|
|
79
|
+
writeNodeList(nodes: readonly (AST.Node | null | undefined)[], separator: string): void;
|
|
80
|
+
writeNodeListWithSourceGaps(nodes: readonly (AST.Node | null | undefined)[], fallbackSeparator: string): void;
|
|
81
|
+
writePreservedNode(node: AST.Node): void;
|
|
82
|
+
writeSource(start: number, end: number, data: Data): void;
|
|
83
|
+
getLeadingComments: (node: AST.Node) => Comment[] | undefined;
|
|
84
|
+
getTrailingComments: (node: AST.Node) => Comment[] | undefined;
|
|
85
|
+
}
|
|
86
|
+
type NodePrinter<Key extends AST_NODE_TYPES, Data> = (node: Extract<AST.Node, {
|
|
87
|
+
type: Key;
|
|
88
|
+
}>, context: PrinterContext<Data>) => void;
|
|
89
|
+
type Printers<Data> = { [K in AST_NODE_TYPES]?: NodePrinter<K, Data> };
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/printer.d.ts
|
|
92
|
+
declare function print<Data>(node: AST.Node, options: PrintOptions<Data>): PrintResult<Data>;
|
|
93
|
+
declare function print<Data>(node: import("estree").Node, options: PrintOptions<Data>): PrintResult<Data>;
|
|
94
|
+
declare function print<Data>(node: NodeLike, options: PrintOptions<Data>): PrintResult<Data>;
|
|
95
|
+
declare function defaultIsUntouched(node: AST.Node): boolean | SourceRange;
|
|
96
|
+
declare function defaultGetMappingData(node?: AST.Node | null): unknown;
|
|
97
|
+
declare function defaultCombineMappingData(left: unknown, right: unknown): unknown;
|
|
98
|
+
//#endregion
|
|
99
|
+
export { type AST, type Comment, type NodeLike, type NodePrinter, type PrintOptions, type PrintResult, type PrinterContext, defaultCombineMappingData, defaultGetMappingData, defaultIsUntouched, print };
|