Commit 5c9d6cf
initialize printer synchronously at module load (#16)
The plugin exports an empty `as_estree` printer object that gets
populated asynchronously during `parse()`. Prettier attempts to use the
printer before initialization completes, causing `TypeError:
printer.print is not a function` on Prettier 3.x.
## Changes
- **Import estree plugin explicitly** at module load to access the base
printer
- **Initialize `as_estree` synchronously** by spreading
`pluginEstree.printers.estree` at module level
- **Remove async initialization path** - deleted `initPrinter()`
function and made `parse()` synchronous
- **Add test** verifying printer methods are available at module load
time
## Before/After
```javascript
// Before: printer empty at export, populated during first parse
let as_estree = {};
async function initPrinter(jsPlugin) {
let estree = jsPlugin.printers.estree;
estree = typeof estree == "function" ? await estree() : estree;
Object.assign(as_estree, { ...estree, printComment(...) {...} });
}
async function parse(text, options) {
await initPrinter(options.plugins.find(...)); // Race condition
return pluginTypescript.parsers.typescript.parse(text, options);
}
// After: printer fully initialized at module load
import pluginEstree from "prettier/plugins/estree";
const baseEstreePrinter = pluginEstree.printers.estree;
const as_estree = {
...baseEstreePrinter,
printComment(...) {...}
};
function parse(text, options) {
return pluginTypescript.parsers.typescript.parse(text, options);
}
```
The printer now has all required methods (`print`, `printComment`, etc.)
immediately available when the plugin is loaded.
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
----
*This section details on the original issue you should resolve*
<issue_title>BUG: `TypeError: printer.print is not a function` with
Prettier 3.x</issue_title>
<issue_description>## Summary
The `assemblyscript-prettier` plugin fails with `TypeError:
printer.print is not a function` when used with Prettier 3.x. This
affects both version 3.0.1 (latest) and version 2.0.2.
## Environment
- **Node.js:** v20.x / v22.x
- **Prettier:** 3.3.3+
- **assemblyscript-prettier:** Tested with 3.0.1 and 2.0.2
- **OS:** Linux (Debian 13 / Ubuntu)
## Steps to Reproduce
### 1. Install the plugin
```bash
npm i -D assemblyscript-prettier@3.0.1 # or @2.0.2
```
### 2. Configure Prettier (any of these approaches)
**Option A - Root `.prettierrc.json`:**
```json
{
"semi": false,
"singleQuote": true,
"trailingComma": "none",
"plugins": ["assemblyscript-prettier"]
}
```
**Option B - Directory-scoped `.prettierrc.json`:**
Place the config in the AssemblyScript directory (e.g.,
`assembly/.prettierrc.json`)
### 3. Run Prettier
```bash
npx prettier --write .
```
## Expected Behavior
AssemblyScript files with decorators like `@inline`, `@lazy`,
`@external` should be formatted correctly.
## Actual Behavior
```
[error] assembly/index.ts: TypeError: printer.print is not a function
[error] at callPluginPrintFunction (file:///node_modules/prettier/index.mjs:16631:20)
[error] at printAstToDoc (file:///node_modules/prettier/index.mjs:16581:22)
[error] at async coreFormat (file:///node_modules/prettier/index.mjs:16959:14)
[error] at async formatWithCursor (file:///node_modules/prettier/index.mjs:17172:14)
[error] at async formatFiles (file:///node_modules/prettier/internal/legacy-cli.mjs:5831:18)
```
The error occurs on **all** `.ts` files processed by the plugin, not
just AssemblyScript files.
## Root Cause Analysis
Looking at the plugin source code in `src/plugin.js`:
### Version 3.0.1 Issue
```javascript
let as_estree = {};
async function initPrinter(jsPlugin) {
let estree = jsPlugin.printers.estree;
estree = typeof estree == "function" ? await estree() : estree;
Object.assign(as_estree, {
...estree,
// ...
});
}
async function parse(text, options) {
await initPrinter(options.plugins.find((plugin) => plugin.printers && plugin.printers.estree));
// ...
}
export default {
parsers: {
typescript: {
...pluginTypescript.parsers.typescript,
parse,
astFormat: "as-estree",
preprocess: preProcess,
},
},
printers: { "as-estree": as_estree }, // Empty object at registration time!
};
```
### Version 2.0.2 Issue
```javascript
let as_estree = {};
function initPrinter(jsPlugin) {
Object.assign(as_estree, {
...jsPlugin.printers.estree,
// ...
});
}
function parse(text, options) {
initPrinter(options.plugins.find((plugin) => plugin.printers && plugin.printers.estree));
// ...
}
export default {
parsers: {
typescript: { /* ... */ },
},
printers: { "as-estree": as_estree }, // Still empty at registration time!
};
```
**The problem:** The `as_estree` printer is registered as an empty
object (`{}`) at module load time. The `initPrinter` function that
populates it is called during `parse()`, but Prettier attempts to use
the printer **before** the first parse completes.
When Prettier calls `printer.print()`, the `as_estree` object is still
empty because:
1. Plugin exports are evaluated at module load
2. `as_estree = {}` is empty at that point
3. `initPrinter()` hasn't been called yet
4. Prettier tries to use the printer before parsing the first file
## Additional Issue: Parser Scope
The plugin overrides the built-in `typescript` parser globally:
```javascript
export default {
parsers: {
typescript: { /* ... */ }, // Replaces Prettier's typescript parser!
},
// ...
};
```
This means when the plugin is active, **all** `.ts` files are processed
with the AssemblyScript parser, not just files in `assembly/`
directories. This is problematic for monorepos or projects with both
regular TypeScript and AssemblyScript.
## Suggested Fix
The printer should be initialized synchronously at module load time, not
lazily during parse:
```javascript
import pluginTypescript from "prettier/plugins/typescript";
import { magic, preProcess } from "./replace.js";
import { builders } from "prettier/doc";
// Get the estree printer synchronously at module load
const tsPlugin = pluginTypescript;
const as_estree = {
// Copy all methods from the TypeScript estree printer
...(() => {
const estree = tsPlugin.printers?.estree;
return typeof estree === "function" ? null : estree; // Handle lazy loading differently
})(),
printComment(commentPath, options) {
const comment = commentPath.getValue().value;
if (comment.startsWith(magic) && comment.endsWith(magic)) {
const doc = [];
if (commentPath.stack[commentPath.stack.length - 2] === 0) {
doc.push(builders.hardline);
}
doc.pus...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes #15
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for you](https://github.com/wasm-ecosystem/assemblyscript-prettier/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: HerrCai0907 <77575210+HerrCai0907@users.noreply.github.com>
Co-authored-by: Cai Congcong <congcong.cai@bmw.com>1 parent a5e9b1e commit 5c9d6cf
5 files changed
Lines changed: 1078 additions & 3675 deletions
File tree
- .github/workflows
- src
- tests
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
14 | | - | |
15 | | - | |
| 14 | + | |
16 | 15 | | |
17 | | - | |
| 16 | + | |
18 | 17 | | |
19 | 18 | | |
20 | 19 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
20 | | - | |
21 | | - | |
22 | | - | |
23 | 19 | | |
0 commit comments