{"id":34519347,"url":"https://github.com/pickier/pickier","last_synced_at":"2026-05-01T14:01:11.285Z","repository":{"id":309011451,"uuid":"1015478535","full_name":"pickier/pickier","owner":"pickier","description":"Fast linting and formatting. Minimal defaults. Extensible. Built for speed.","archived":false,"fork":false,"pushed_at":"2026-04-30T06:29:23.000Z","size":2471,"stargazers_count":8,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T08:16:37.873Z","etag":null,"topics":["css","formatter","html","javascript","json","jsonc","linter","md","performant","stx","typescript","yaml","yml"],"latest_commit_sha":null,"homepage":"https://pickier.netlify.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pickier.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["stacksjs","chrisbbreuer"],"open_collective":"stacksjs"}},"created_at":"2025-07-07T15:02:43.000Z","updated_at":"2026-04-29T11:05:09.000Z","dependencies_parsed_at":"2025-08-09T09:13:08.183Z","dependency_job_id":"4a2d3a10-234b-49a1-9c66-a9b87b2740f4","html_url":"https://github.com/pickier/pickier","commit_stats":null,"previous_names":["stacksjs/pickier"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/pickier/pickier","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pickier%2Fpickier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pickier%2Fpickier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pickier%2Fpickier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pickier%2Fpickier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pickier","download_url":"https://codeload.github.com/pickier/pickier/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pickier%2Fpickier/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32499691,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","formatter","html","javascript","json","jsonc","linter","md","performant","stx","typescript","yaml","yml"],"created_at":"2025-12-24T04:28:12.821Z","updated_at":"2026-05-01T14:01:11.264Z","avatar_url":"https://github.com/pickier.png","language":"TypeScript","funding_links":["https://github.com/sponsors/stacksjs","https://github.com/sponsors/chrisbbreuer","https://opencollective.com/stacksjs"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\".github/art/cover.jpg\" alt=\"Social Card of this repo\"\u003e\u003c/p\u003e\n\n[![npm version](https://img.shields.io/npm/v/pickier?style=flat-square)](https://npmjs.com/package/pickier)\n[![GitHub Actions](https://img.shields.io/github/actions/workflow/status/pickier/pickier/ci.yml?style=flat-square\u0026branch=main)](https://github.com/pickier/pickier/actions?query=workflow%3Aci)\n\n# Pickier\n\n\u003e Fast linting and formatting. Minimal defaults. Extensible. Built for speed.\n\n## Features\n\n- Fast CLI with instant feedback\n- Lint and format in one tool\n- Zero-config defaults; simple, typed `pickier.config.ts` when you need it\n- Import organization: splits type/value imports, sorts modules/specifiers, removes unused named imports\n- JSON and config sorting for common files (e.g. `package.json`, `tsconfig.json`)\n- Tailwind CSS class ordering — enforces canonical class order across HTML/JSX/TSX/Vue/Svelte/STX files with auto-fix\n- Markdown linting for documentation quality (headings, lists, links, code blocks, tables, etc.) with auto-fix support\n- Shell script linting \u0026 formatting — 21 rules for `.sh`, `.bash`, and `.zsh` files covering error prevention, style, and best practices with auto-fix support and indentation normalization\n- Optional spell checking — 234K-word dictionary with 150+ common misspelling corrections, powered by [ts-spell-check](https://github.com/stacksjs/ts-spell-check)\n- Flexible formatting: `indent`, `indentStyle` (tabs or spaces), `quotes`, `semi`, `trimTrailingWhitespace`, `maxConsecutiveBlankLines`, `finalNewline`\n- Smart whitespace cleanup\n- Package.json validation for correct npm publishing (exports ordering, file format, module system, etc.)\n- ESLint-style plugin system for lint rules (load plugins, enable/disable rules, WIP labeling)\n- CI-friendly reporters (stylish, compact, JSON) and strict `--max-warnings` control\n- Programmatic API for custom tooling and editor integrations\n\n## Install\n\n```bash\n# as a dev dependency\nbun add -D pickier\n\n# or\nnpm i -D pickier\n\n# or\npnpm add -D pickier\n\n# or\nyarn add -D pickier\n```\n\nYou can also run it directly via npx without installing:\n\n```bash\nnpx pickier --help\n\n# or\nbunx pickier --help\n```\n\n## Quick Start\n\n```bash\n# Lint your project\npickier .\n\n# Auto-fix lint issues\npickier . --fix\n\n# Format files\npickier . --format\n\n# Check formatting without writing (CI-friendly)\npickier . --format --check\n\n# Preview fixes without writing\npickier . --fix --dry-run\n```\n\nBy default, `pickier` lints. Use `--fix` to auto-fix problems or `--format` to format files.\n\n## CLI\n\n### `pickier [...globs]`\n\nLints by default. Add `--fix` to auto-fix or `--format` to format files.\n\n```bash\n# These are equivalent\npickier .\npickier lint .\n\n# These are equivalent\npickier . --fix\npickier lint . --fix\n\n# These are equivalent\npickier . --format\npickier format . --write\n```\n\n### `pickier lint [...globs]`\n\nLint files.\n\n| Flag | Description | Default |\n|------|-------------|---------|\n| `--fix` | Auto-fix problems | `false` |\n| `--dry-run` | Simulate fixes without writing | `false` |\n| `--max-warnings \u003cn\u003e` | Fail if warnings exceed _n_ | `-1` |\n| `--reporter \u003cname\u003e` | `stylish`, `json`, or `compact` | `stylish` |\n| `--ext \u003cexts\u003e` | Comma-separated extensions (overrides config) | — |\n| `--ignore-path \u003cfile\u003e` | Optional ignore file (e.g. `.gitignore`) | — |\n| `--config \u003cpath\u003e` | Path to pickier config file | — |\n| `--cache` | Enable cache (reserved) | `false` |\n| `--verbose` | Verbose output | `false` |\n\n### `pickier format [...globs]`\n\nFormat files.\n\n| Flag | Description | Default |\n|------|-------------|---------|\n| `--write` | Write changes to files | `false` |\n| `--check` | Check without writing, non-zero exit on differences (CI-friendly) | `false` |\n| `--ext \u003cexts\u003e` | Comma-separated extensions (overrides config) | — |\n| `--ignore-path \u003cfile\u003e` | Optional ignore file (e.g. `.gitignore`) | — |\n| `--config \u003cpath\u003e` | Path to pickier config file | — |\n| `--verbose` | Verbose output | `false` |\n\n### `pickier run [...globs]`\n\nUnified command that routes to lint or format based on `--mode`.\n\n| Flag | Description | Default |\n|------|-------------|---------|\n| `--mode \u003cmode\u003e` | `auto`, `lint`, or `format` | `auto` |\n\nAccepts all flags from both `lint` and `format`. Useful for scripts that need explicit mode control.\n\n## Configuration\n\nPickier works out-of-the-box. To customize, create `pickier.config.ts` in your project root. All fields are optional.\n\n```ts\n// pickier.config.ts\nimport type { PickierConfig } from 'pickier'\n\nconst config: PickierConfig = {\n  verbose: false,\n  ignores: ['**/node_modules/**', '**/dist/**', '**/build/**'],\n\n  lint: {\n    // which extensions to lint ('.ts' or 'ts' both supported)\n    extensions: ['ts', 'js'],\n    // stylish | json | compact\n    reporter: 'stylish',\n    // reserved (not used yet)\n    cache: false,\n    // -1 disables, otherwise fail when warnings \u003e maxWarnings\n    maxWarnings: -1,\n  },\n\n  format: {\n    // which extensions to format\n    extensions: ['ts', 'js', 'json', 'md', 'yaml', 'yml', 'sh', 'bash', 'zsh'],\n    trimTrailingWhitespace: true,\n    maxConsecutiveBlankLines: 1,\n    // one | two | none\n    finalNewline: 'one',\n    // indentation width (code files)\n    indent: 2,\n    // 'spaces' | 'tabs'\n    indentStyle: 'spaces',\n    // preferred string quotes: 'single' | 'double'\n    quotes: 'single',\n    // when true, safely remove stylistic semicolons\n    // (never touches for(;;) headers; removes duplicate/empty semicolons)\n    semi: false,\n  },\n\n  rules: {\n    // 'off' | 'warn' | 'error'\n    noDebugger: 'error',\n    noConsole: 'warn',\n  },\n\n  // Tailwind CSS class ordering (auto-enables pickier/sort-tailwind-classes at 'warn')\n  tailwind: {\n    enabled: true,\n    // configPath: './tailwind.config.ts', // optional\n  },\n\n  // Plugin rules for markdown, shell, style, sorting, etc.\n  pluginRules: {\n    // Markdown linting (53+ rules available)\n    'markdown/heading-increment': 'error',\n    'markdown/no-trailing-spaces': 'error',\n    'markdown/fenced-code-language': 'error',\n    'markdown/no-duplicate-heading': 'warn',\n\n    // Shell script linting (21 rules available)\n    'shell/command-substitution': 'error',\n    'shell/no-eval': 'error',\n    'shell/quote-variables': 'warn',\n    'shell/prefer-double-brackets': 'warn',\n  },\n}\n\nexport default config\n```\n\n### Plugin System\n\nPickier supports an ESLint-style plugin system for lint rules organized into focused categories.\n\n#### Available Plugins\n\n| Plugin | Description | Rules |\n|--------|-------------|-------|\n| `pickier/` | Sorting, import organization, Tailwind class ordering, and core checks | 18+ |\n| `general/` | Error detection and possible problems | 35+ |\n| `quality/` | Best practices and code quality | 40+ |\n| `style/` | Code style enforcement (spacing, punctuation, line breaks, etc.) | 50+ |\n| `ts/` | TypeScript-specific rules (type safety, formatting) | 13+ |\n| `regexp/` | Regular expression safety | 3+ |\n| `markdown/` | Markdown documentation linting | 53+ |\n| `shell/` | Shell script linting \u0026 formatting (`.sh`, `.bash`, `.zsh`) | 21 |\n| `spell/` | Spell checking (optional, requires `ts-spell-check`) | 3 |\n| `publint/` | Package.json validation for npm publishing ([publint](https://publint.dev) rules) | 20 |\n| `lockfile/` | Lock file validation | 5+ |\n\nConfigure rules via `pluginRules`:\n\n```ts\npluginRules: {\n  'pluginName/ruleId': 'off' | 'warn' | 'error' | ['warn', options],\n}\n```\n\n#### Custom Plugin Example\n\n```ts\n// sample-plugin.ts\nimport type { PickierPlugin, RuleContext } from 'pickier'\n\nexport const samplePlugin: PickierPlugin = {\n  name: 'sample',\n  rules: {\n    'no-todo': {\n      meta: { docs: 'disallow TODO comments', recommended: true },\n      check(content: string, ctx: RuleContext) {\n        const issues = []\n        const lines = content.split(/\\r?\\n/)\n        for (let i = 0; i \u003c lines.length; i++) {\n          const col = lines[i].indexOf('TODO')\n          if (col !== -1) {\n            issues.push({\n              filePath: ctx.filePath,\n              line: i + 1,\n              column: col + 1,\n              ruleId: 'sample/no-todo',\n              message: 'Unexpected TODO comment.',\n              severity: 'warning',\n            })\n          }\n        }\n        return issues\n      },\n    },\n    'experimental-check': {\n      meta: { wip: true },\n      check() {\n        // not implemented yet — WIP rules surface errors with a :wip-error suffix\n        throw new Error('WIP rule')\n      },\n    },\n  },\n}\n```\n\n#### Register the Plugin in Your Config\n\n```ts\n// pickier.config.ts\nimport type { PickierConfig } from 'pickier'\nimport { samplePlugin } from './sample-plugin'\n\nconst config: PickierConfig = {\n  plugins: [samplePlugin],\n  pluginRules: {\n    'sample/no-todo': 'warn',\n    'sample/experimental-check': 'error',\n  },\n}\n\nexport default config\n```\n\n### Formatting Details\n\n#### Semicolons\n\nControlled by `format.semi` (default `false`). When `true`, Pickier safely removes only stylistic semicolons:\n\n- Preserves `for (init; test; update)` headers\n- Removes duplicate trailing semicolons (e.g. `foo();;` -\u003e `foo();`)\n- Removes lines that are just empty statements (`;`)\n- Keeps normal end-of-line semicolons otherwise (non-destructive)\n\n#### Imports (TypeScript/JavaScript)\n\nGroups and rewrites the top import block:\n\n- Splits type-only specifiers into `import type { ... } from 'x'`\n- Keeps default and namespace imports\n- Removes unused named specifiers (only when they have no alias)\n- Merges multiple imports from the same module\n\nSorting order:\n\n- By kind: type imports, side-effect imports, value imports\n- For modules: external before relative\n- For specifiers: A-Z by identifier\n\nSpacing:\n\n- Ensures a single blank line between the import block and the rest of the file\n- Respects `format.finalNewline` at EOF\n\n#### Built-in Rules\n\n- `noDebugger` — removes `debugger` statements when `--fix` is used\n- `noConsole` — controls severity (turn off for libraries that allow console logs)\n\n## Benchmarks\n\nMeasured on an Apple M3 Pro with Bun 1.3.10. Each tool uses equivalent settings (single quotes, no semicolons, 2-space indent). Pickier and Prettier use their in-memory APIs; oxfmt and Biome have no JS formatting API, so they are called via stdin pipe. Full benchmark source is in `bechmarks/benchmarks/format-comparison.bench.ts`.\n\n### In-memory / Programmatic API\n\nPickier `formatCode()` and Prettier `format()` run in-process. oxfmt and Biome are piped via stdin (no JS formatting API).\n\n| File | Pickier | Biome (stdin) | oxfmt (stdin) | Prettier |\n|------|--------:|--------------:|--------------:|---------:|\n| Small (52 lines, 1 KB) | **41 µs** | 40 ms | 51 ms | 1.59 ms |\n| Medium (419 lines, 10 KB) | **417 µs** | 42 ms | 50 ms | 10.2 ms |\n| Large (1,279 lines, 31 KB) | **1.25 ms** | 46 ms | 50 ms | 28.1 ms |\n\n### Linting — Pickier vs ESLint vs oxlint vs Biome\n\nFrom the `bench:lint` suite. `(api)` = programmatic in-process; `(cli)` = native Zig binary, the fair CLI-vs-CLI comparison. ESLint runs via `node` since its `ajv` has a Bun compat issue.\n\n| File | Pickier (api) | Pickier (cli) | ESLint (node) | oxlint | Biome |\n|------|-------------:|--------------:|--------------:|-------:|------:|\n| Small (52 lines) | **249 µs**|**19 ms** | 57 ms | 47 ms | 38 ms |\n| Medium (419 lines) | **1.73 ms**|**21 ms** | 57 ms | 47 ms | 41 ms |\n| Large (1,279 lines) | **4.43 ms**|**28 ms** | 57 ms | 49 ms | 45 ms |\n| All files (batch) | **40 µs**|**62 ms** | 172 ms | 144 ms | 129 ms |\n\n### Combined — Lint + Format Workflow\n\nFrom the `bench:combined` suite. `(api)` = programmatic in-process; `(cli)` = native Zig binary doing both lint + format. ESLint runs via `node`.\n\n| File | Pickier (api) | Pickier (cli) | ESLint + Prettier | oxlint + oxfmt | Biome |\n|------|-------------:|--------------:|------------------:|---------------:|------:|\n| Small (52 lines) | **303 µs**|**35 ms** | 63 ms | 94 ms | 41 ms |\n| Medium (419 lines) | **2.19 ms**|**38 ms** | 74 ms | 94 ms | 54 ms |\n| Large (1,279 lines) | **5.98 ms**|**49 ms** | 93 ms | 102 ms | 91 ms |\n| All files (batch) | **8.24 ms**|**125 ms** | 238 ms | 286 ms | 184 ms |\n\n### CLI Batch (all files, sequential)\n\n| Tool | Time |\n|------|-----:|\n| Pickier (Zig) | **50 ms** |\n| Biome | 167 ms |\n| oxfmt | 186 ms |\n| Prettier | 353 ms |\n\n### Throughput (large file x 20)\n\n| Tool | Time |\n|------|-----:|\n| Pickier | **21 ms** |\n| Prettier | 439 ms |\n| Biome (stdin) | 857 ms |\n| oxfmt (stdin) | 892 ms |\n\n\u003e Pickier's in-memory API is **22-39x faster than Prettier**and orders of magnitude faster than tools that must spawn a process. On CLI batch, Pickier's compiled binary is**3.2x faster than Biome**and**6.9x faster than Prettier**. At throughput scale (20x large file), Pickier is**21x faster**than Prettier and**40x faster** than Biome/oxfmt.\n\n```bash\n# reproduce locally\nbun bechmarks/benchmarks/format-comparison.bench.ts\n```\n\n## Programmatic Usage\n\nCall Pickier from code (Bun/Node) for custom tooling, editors, or pipelines.\n\n```ts\nimport type { RunOptions } from 'pickier'\nimport { config, defaultConfig, run, runLint, runFormat, lintText } from 'pickier'\n\n// Unified run\nconst exitCode = await run(['.'], {\n  mode: 'lint',\n  fix: true,\n})\n\n// Lint specific directories\nconst lintCode = await runLint(['src', 'tests'], {\n  fix: true,\n  dryRun: false,\n  reporter: 'json',\n  maxWarnings: 0,\n})\n\n// Format specific globs\nconst fmtCode = await runFormat(['src/**/*.ts'], {\n  write: true,\n})\n\n// Lint a single string\nconst result = await lintText('const x = 1;;', {\n  filePath: 'virtual.ts',\n})\n\n// Access loaded config (from pickier.config.ts or defaults)\nconsole.log('loaded config:', config)\n```\n\n## Development\n\nThis repository contains Pickier's source under `packages/pickier`.\n\n```bash\n# install deps\nbun i\n\n# run tests (with coverage)\nbun test --coverage\n\n# build JS and type declarations\nbun run -C packages/pickier build\n\n# compile native binary for your platform\nbun run -C packages/pickier compile\n\n# compile all platform binaries\nbun run -C packages/pickier compile:all\n```\n\nTry the CLI locally without publishing:\n\n```bash\n# run the TS entry directly\nbun packages/pickier/bin/cli.ts --help\n\n# lint the current directory\nbun packages/pickier/bin/cli.ts .\n\n# or the compiled native binary (after compile)\n./packages/pickier/bin/pickier-\u003cyour-platform\u003e .\n```\n\n## Testing\n\n```bash\nbun test\n```\n\n## Changelog\n\nPlease see our [releases](https://github.com/pickier/pickier/releases) page for more information on what has changed recently.\n\n## Contributing\n\nPlease review the [Contributing Guide](https://github.com/stacksjs/contributing) for details.\n\n## Community\n\nFor help, discussion about best practices, or any other conversation that would benefit from being searchable:\n\n[Discussions on GitHub](https://github.com/pickier/pickier/discussions)\n\nFor casual chit-chat with others using this package:\n\n[Join the Stacks Discord Server](https://discord.gg/stacksjs)\n\n## Postcardware\n\n“Software that is free, but hopes for a postcard.” We love receiving postcards from around the world showing where`pickier` is being used! We showcase them on our website too.\n\nOur address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎\n\n## Sponsors\n\nWe would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.\n\n- [JetBrains](https://www.jetbrains.com/)\n- [The Solana Foundation](https://solana.com/)\n\n## Credits\n\n- [Chris Breuer](https://github.com/chrisbbreuer)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.\n\nMade with 💙\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpickier%2Fpickier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpickier%2Fpickier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpickier%2Fpickier/lists"}