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 assemblyscript-prettier@3.0.1 # or @2.0.2
2. 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
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 point
initPrinter() 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 implementation
Alternatively, 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.json with plugin - FAILED
- Both versions 3.0.1 and 2.0.2 - BOTH FAILED
Version Information
Tested combinations:
assemblyscript-prettier@3.0.1 + prettier@3.3.3 - FAILED
assemblyscript-prettier@2.0.2 + prettier@3.3.3 - FAILED
Both versions declare peer dependency prettier: ">=3.0.0-alpha.4" but fail to work with any Prettier 3.x version.
Summary
The
assemblyscript-prettierplugin fails withTypeError: printer.print is not a functionwhen used with Prettier 3.x. This affects both version 3.0.1 (latest) and version 2.0.2.Environment
Steps to Reproduce
1. Install the plugin
npm i -D assemblyscript-prettier@3.0.1 # 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,@externalshould be formatted correctly.Actual Behavior
The error occurs on all
.tsfiles 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
Version 2.0.2 Issue
The problem: The
as_estreeprinter is registered as an empty object ({}) at module load time. TheinitPrinterfunction that populates it is called duringparse(), but Prettier attempts to use the printer before the first parse completes.When Prettier calls
printer.print(), theas_estreeobject is still empty because:as_estree = {}is empty at that pointinitPrinter()hasn't been called yetAdditional Issue: Parser Scope
The plugin overrides the built-in
typescriptparser globally:This means when the plugin is active, all
.tsfiles are processed with the AssemblyScript parser, not just files inassembly/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:
Alternatively, use Prettier's newer plugin API that properly supports async printer initialization.
Workaround
Currently, the only workaround is to use
.prettierignoreto exclude AssemblyScript files:This is not ideal as it means AssemblyScript files are never formatted.
Test Configuration Used
We tested:
.prettierrc.jsonwith plugin - FAILEDVersion Information
Tested combinations:
assemblyscript-prettier@3.0.1+prettier@3.3.3- FAILEDassemblyscript-prettier@2.0.2+prettier@3.3.3- FAILEDBoth versions declare peer dependency
prettier: ">=3.0.0-alpha.4"but fail to work with any Prettier 3.x version.