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.
- π 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.
npm install --save-dev prettier prettier-plugin-powershellRequires Node.js 18.12 or newer and Prettier v3 or newer.
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
}Format scripts recursively:
npx prettier "**/*.ps1" --writeimport prettier from 'prettier';
import plugin from 'prettier-plugin-powershell';
const formatted = await prettier.format(source, {
filepath: 'script.ps1',
parser: 'powershell',
plugins: [plugin]
});| Option | Type | Default | Description |
|---|---|---|---|
powershellIndentStyle |
`"spaces" \ | "tabs"` | "spaces" |
powershellIndentSize |
number |
4 |
Overrides Prettier's tabWidth specifically for PowerShell files (clamped between 1 and 8). |
powershellTrailingComma |
`"none" \ | "multiline" \ | "all"` |
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" |
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" \ |
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" |
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.
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.
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 }
}- 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.
-
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.
| 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. |
- 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.
- Mascot artwork courtesy of the ColorScripts team (light and dark variants included in
assets/). - Built with Prettier, TypeScript, and Vitest.
Distributed under the UnLicense License.

{ "plugins": ["prettier-plugin-powershell"], "powershellPreset": "invoke-formatter", // overrides remain opt-in "powershellRewriteAliases": true }