-
Notifications
You must be signed in to change notification settings - Fork 3
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
npm i -D [email protected] # or @2.0.22. Configure Prettier (any of these approaches)
Option A - Root .prettierrc.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
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
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
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:
- Plugin exports are evaluated at module load
as_estree = {}is empty at that pointinitPrinter()hasn't been called yet- Prettier tries to use the printer before parsing the first file
Additional Issue: Parser Scope
The plugin overrides the built-in typescript parser globally:
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:
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.push(comment.slice(magic.length, -magic.length));
return doc;
}
// Fall back to original printComment
return this.originalPrintComment?.(commentPath, options);
},
};
// ... rest of implementationAlternatively, use Prettier's newer plugin API that properly supports async printer initialization.
Workaround
Currently, the only workaround is to use .prettierignore to exclude AssemblyScript files:
# .prettierignore
# AssemblyScript files use decorators that prettier doesn't understand
examples/wasm-plugins/*/assembly
packages/assemblyscript-plugin-sdk/assembly
This is not ideal as it means AssemblyScript files are never formatted.
Test Configuration Used
signalk-server (monorepo)
├── .prettierrc.json # Root config (no plugin)
├── .prettierignore # Excludes assembly dirs
├── packages/
│ └── assemblyscript-plugin-sdk/
│ └── assembly/
│ └── *.ts # AssemblyScript files
└── examples/
└── wasm-plugins/
└── */assembly/
└── *.ts # AssemblyScript files
We tested:
- Global plugin configuration - FAILED
- Directory-scoped
.prettierrc.jsonwith plugin - FAILED - Both versions 3.0.1 and 2.0.2 - BOTH FAILED
Version Information
Tested combinations:
[email protected]+[email protected]- FAILED[email protected]+[email protected]- FAILED
Both versions declare peer dependency prettier: ">=3.0.0-alpha.4" but fail to work with any Prettier 3.x version.