Prettier Plugin PowerShell
A Prettier 3 plugin that formats PowerShell source files (.ps1, .psm1, .psd1) with predictable, idiomatic output. The formatter is extensively tested (high coverage with strict CI thresholds) and ready for CI/CD pipelines, editor integrations, and automated release flows.

Table of contents
Highlights
- 🌟 Idiomatic PowerShell – balances spacing, casing, and pipeline layout while preserving comments and here-strings.
- 🔧 Fine-grained controls – tune indentation style/width, trailing delimiters, brace style, alias rewriting, and keyword casing.
- ⚡ Prettier-first – drop-in plugin for Prettier v3+, compatible with the CLI, editors, and format-on-save workflows.
- 📈 Production ready – enforced by CI (lint, typecheck, tests) with Codecov-powered reporting and ≥95 % coverage gates.
- 🛠️ TypeScript source – strongly typed AST helpers and printer utilities for easy extension.
Quick start
Install
npm install --save-dev prettier prettier-plugin-powershell
Requires Node.js 18.12 or newer and Prettier v3 or newer.
Prettier configuration
Add the plugin to your Prettier config (e.g. .prettierrc.json):
{
"plugins": ["prettier-plugin-powershell"],
"parser": "powershell"
}
You can co-locate plugin options with standard Prettier settings:
{
"plugins": ["prettier-plugin-powershell"],
"tabWidth": 2,
"powershellTrailingComma": "all",
"powershellRewriteAliases": true
}
Command line
Format scripts recursively:
npx prettier "**/*.ps1" --write
Programmatic usage
import prettier from 'prettier';
import plugin from 'prettier-plugin-powershell';
const formatted = await prettier.format(source, {
filepath: 'script.ps1',
parser: 'powershell',
plugins: [plugin]
});
Configuration reference
| Option | Type | Default | Description |
|---|---|---|---|
powershellIndentStyle |
"spaces" \ | "tabs" |
"spaces" |
Render indentation with spaces or tabs. |
powershellIndentSize |
number |
4 |
Overrides Prettier’s tabWidth specifically for PowerShell files (clamped between 1 and 8). |
powershellTrailingComma |
"none" \ | "multiline" \ | "all" |
"none" |
When to emit trailing semicolons between hashtable entries (PowerShell arrays do not support trailing commas). |
powershellSortHashtableKeys |
boolean |
false |
Sort hashtable keys alphabetically before printing. |
powershellBlankLinesBetweenFunctions |
number |
1 |
Minimum blank lines preserved between function declarations (clamped between 0 and 3). |
powershellBlankLineAfterParam |
boolean |
true |
Insert a blank line after param (...) blocks within functions/script blocks. |
powershellBraceStyle |
"1tbs" \ | "allman" |
"1tbs" |
Choose inline braces or newline-aligned Allman style. |
powershellLineWidth |
number |
120 |
Maximum print width for wrapping pipelines, hashtables, and arrays (clamped between 40 and 200). |
powershellPreferSingleQuote |
boolean |
false |
Prefer single-quoted strings when interpolation is not required. |
powershellKeywordCase |
"preserve" \ | "lower" \ | "upper" \ | "pascal" |
"lower" |
Normalise PowerShell keyword casing (defaults to lowercase to match PSScriptAnalyzer/Invoke-Formatter). |
powershellRewriteAliases |
boolean |
false |
Expand cmdlet aliases such as ls, %, ?, gci. |
powershellRewriteWriteHost |
boolean |
false |
Rewrite Write-Host invocations to Write-Output. |
powershellPreset |
"none" \ | "invoke-formatter" |
"none" |
Apply a bundle of defaults (e.g. invoke-formatter mirrors the settings PowerShell’s built-in formatter uses). |
Invoke-Formatter parity preset
Set "powershellPreset": "invoke-formatter" to mirror the behavior of Invoke-Formatter/PSScriptAnalyzer’s CodeFormatting profile. The preset only fills in values that you haven’t provided yourself–any explicit option in your Prettier config still wins.
{
"plugins": ["prettier-plugin-powershell"],
"powershellPreset": "invoke-formatter",
// overrides remain opt-in
"powershellRewriteAliases": true
}
Per-directory overrides (keyword casing, presets, etc.)
Prettier supports overrides, so you can scope keyword casing/presets to specific folders without extra tooling:
{
"plugins": ["prettier-plugin-powershell"],
"powershellPreset": "invoke-formatter",
"overrides": [
{
"files": "legacy/**/*.ps1",
"options": {
"powershellKeywordCase": "preserve"
}
}
]
}
Combined with the preset, this makes it easy to keep your primary scripts aligned with PowerShell’s formatter while letting legacy or third-party snippets retain their original casing.
Example formatting
Input:
function Get-Widget{
param(
[string]$Name,
[int] $Count
)
$items=Get-Item |Where-Object { $_.Name -eq $Name}| Select-Object Name,Length
$hash=@{ b=2; a =1 }
}
Output with default settings:
function Get-Widget {
param(
[string] $Name,
[int] $Count
)
$items = Get-Item
| Where-Object {
$_.Name -eq $Name
}
| Select-Object Name, Length
$hash = @{ b = 2; a = 1 }
}
Automation & coverage
- CI – GitHub Actions (see
ci.yml) installs dependencies, lint checks, type-checks, and runs the Vitest suite with coverage on every push and pull request. - Codecov – Coverage artefacts (
coverage/lcov.info) are uploaded via the Codecov action. The badge above reflects the latest metrics onmain. - npm publishing – Every push to
maintriggerspublish.yml, which bumps the version (patch by default,feat→ minor,BREAKING→ major), runs the quality bar, commits the build artifacts, tags the release, publishes to npm, and opens a GitHub release. The legacy manual workflow now just points back to this automated pipeline; you can still run it manually from the Actions tab when needed.
Property-based testing
-
Fast-check harness – Property-based tests across multiple modules use
fast-checkto validate behavior with randomly generated inputs:tests/parser.property.test.ts– Exercises the parser and formatter with randomly generated PowerShell snippets, validating location metadata, token ordering, formatting stability, and re-parseability.tests/parser.edge-cases.property.test.ts– Stress-tests the parser with edge cases: deep nesting, unbalanced delimiters, comment placement, string variations, whitespace handling, pipelines, operators, and location consistency.tests/tokenizer.property.test.ts– Validates tokenizer correctness: token ordering, location ranges, determinism, and proper handling of keywords, variables, strings, comments, and edge cases.tests/tokenizer-helpers.property.test.ts– Tests thenormalizeHereStringhelper function with various line counts, empty lines, mixed line endings, and edge cases.tests/options.property.test.ts– Ensures option resolution never throws, produces valid output, respects user preferences, applies sensible defaults, and correctly clamps numeric values.tests/ast.property.test.ts– Tests AST utility functions (createLocation,isNodeType,cloneNode) for correctness with edge cases like negative values, NaN, Infinity, and type safety.tests/printer.property.test.ts– Validates printer output: formatting never throws, produces valid PowerShell, remains idempotent, preserves semantics, respects configuration options, and handles edge cases like empty scripts and comments.tests/integration.property.test.ts– Tests full round-trip preservation (tokenize → parse → format → re-parse), option combinations, cross-module consistency, error resilience, plugin interface contracts, and file extension handling.tests/weird-files.property.test.ts– Exercises BOM + shebang combinations, Unicode-heavy content, comment directives, and exotic whitespace to ensure the parser and printer remain stable on atypical files.tests/printer-options.property.test.ts– Verifies option-sensitive printing behavior (blank line heuristics, string quote normalization, alias rewriting, and Write-Host rewriting) across randomized inputs.tests/github-samples.property.test.ts– Opt-in: when enabled, pulls real-world PowerShell scripts from the GitHub API, then formats and re-parses them to guard against regressions on long/complex inputs. By default it runs against local fallback fixtures; setPOWERSHELL_ENABLE_GITHUB_SAMPLES=1(and optionallyGITHUB_TOKEN) to exercise live GitHub samples.
-
Custom arbitraries – Reusable builders in
tests/property/arbitraries.tsgenerate assignments, pipelines, functions, try/catch blocks, and other constructs to shake out edge cases. - Idempotence checks – Most property tests and fixtures assert that formatting is idempotent; a small number of known edge-case fixtures are marked with
expectIdempotent: falseso they still validate parseability without requiring strict first/second-pass equality. - Tuning – Adjust the number of runs with the
POWERSHELL_PROPERTY_RUNSenvironment variable (default100for most tests,150for parser tests). For a deeper local sweep:POWERSHELL_PROPERTY_RUNS=500 npm test. -
PowerShell syntax sampling – By default, every formatted script is re-validated with PowerShell’s built-in parser so regressions surface immediately. Use
POWERSHELL_MAX_SYNTAX_CHECKSto cap the number of checks (set to a positive integer) or0to skip entirely, and toggle the feature wholesale withPOWERSHELL_VERIFY_SYNTAX(0to disable).- Use
POWERSHELL_SYNTAX_TRACE=1to emit per-invocation logs when diagnosing hangs or parser failures.
- Use
-
Property progress & timeboxing – Flip on run-by-run logging with
POWERSHELL_PROPERTY_PROGRESS=1(default interval50, tweak viaPOWERSHELL_PROPERTY_PROGRESS_INTERVAL). Extend or shrink Vitest’s overall timeout withPOWERSHELL_TEST_TIMEOUT_MSwhen running extended fuzz sweeps, and control worker concurrency withMAX_THREADS(default4, or1on CI). - Deep fuzzing –
npm run test:fuzznow shells through PowerShell so thePOWERSHELL_PROPERTY_RUNS=2000environment toggle works cross-platform.
To fuzz against GitHub-hosted PowerShell, export POWERSHELL_ENABLE_GITHUB_SAMPLES=1 (optionally GITHUB_TOKEN to raise rate limits) and run npm test. You can further tune POWERSHELL_GITHUB_SAMPLE_COUNT, POWERSHELL_GITHUB_QUERY, POWERSHELL_GITHUB_MIN_LENGTH, POWERSHELL_GITHUB_MAX_LENGTH, and POWERSHELL_GITHUB_MAX_CANDIDATES to control source selection. Enable POWERSHELL_CACHE_GITHUB_SAMPLES=1 to save downloaded samples to tests/fixtures/github-cache/ for reuse across runs, avoiding redundant API calls.
Project scripts
| Script | Description |
|---|---|
npm run build |
Bundle the plugin to dist/ via tsup. |
npm run build:watch |
Rebuild continuously while developing. |
npm run clean |
Remove the dist/ directory. |
npm run lint / npm run lint:fix |
Run ESLint (optionally with auto-fix). |
npm run format |
Apply Prettier to TypeScript source and tests. |
npm run test / npm run test:watch |
Execute the Vitest suite. |
npm run test:ci |
Run the Vitest suite with a summary reporter (used in CI workflows). |
npm run test:debug |
Start Vitest under the Node inspector for interactive debugging. |
npm run test:coverage |
Generate v8 coverage reports (consumed by Codecov). |
npm run test:fuzz |
Run the Vitest suite with POWERSHELL_PROPERTY_RUNS=2000 via PowerShell for deep fuzzing. |
npm run benchmark |
Run the built-in benchmark against synthetic PowerShell functions. |
npm run profile / npm run profile:enhanced |
Capture parser/formatter performance profiles for detailed analysis. |
npm run typecheck |
Ensure the TypeScript project compiles without emitting files. |
Contributing
- Fork and clone the repository.
- Install dependencies with
npm install. - Use
npm run build:watchduring active development. - Before opening a pull request, run:
npm run lintnpm run typechecknpm run test:coverage
- Contributions remain under the UnLicense license.
Bug reports and feature requests are welcome via GitHub issues.
Credits
- Mascot artwork courtesy of the ColorScripts team (light and dark variants included in
assets/). - Built with Prettier, TypeScript, and Vitest.
License
Distributed under the UnLicense License.