prettier/prettier
github.com/prettier/prettier ↗ · 3999 files · 7.7 MB · scan capped
Opinionated Code Formatter JavaScript · TypeScript · Flow · JSX · JSON CSS · SCSS · Less HTML · Vue · Angular GraphQL · Markdown · YAML Your favorite language? Intro Prettier is an opinionated code formatter.
Overview
Prettier 3.10.0-dev is an opinionated, plugin-based code formatter: it parses source into an AST, attaches comments, converts that AST to a Wadler-style Doc IR, then pretty-prints to a string. The public Node API in src/index.js is fully async (format, check, formatWithCursor, resolveConfig, getFileInfo, getSupportInfo); the CLI enters through bin/prettier.cjs into src/cli/index.js, with an opt-in rewrite re-exported from @prettier/cli. Built-in languages (JS/TS/Flow, JSON, CSS/Less/SCSS, HTML/Vue/Angular/LWC/MJML, GraphQL, Markdown/MDX, YAML, Handlebars) are themselves plugins, lazily loaded, and last-registered plugins win when resolving parsers and printers.
System map
The runtime has three public surfaces: the Node ESM/CJS API (src/index.js / src/index.cjs), the CLI (bin/prettier.cjs → src/cli), and a browser standalone bundle (src/standalone.js, no config/plugin autoload). Formatting is owned by src/main/core.js; languages live under src/language-*; plugins are assembled by src/main/plugins and the generated builtin registries. Config search/load lives in src/config; the Doc printer in src/document. Development requires Node >=22 and Yarn 4; production tests still exercise dist/ down to Node 14. The website is a Docusaurus + Vite playground published via Netlify; npm packages are built by scripts/build/build.js into dist/.
- package.json names the project prettier 3.10.0-dev, sets type module, engines.node >=22, bin to ./bin/prettier.cjs, main to ./src/index.cjs, and exports '.' (types src/index.d.ts, require src/index.cjs, default src/index.js), './standalone' to src/standalone.js, and './plugins/*' to src/plugins/*.js.
- The published browser/unpkg entry is standalone.js; package.json also defines #universal import maps that swap src/universal/*.browser.js in browsers versus src/universal/*.js on Node.
- bin/prettier.cjs is the real CLI bootstrap: it optionally enables Node compile cache, rejects Node versions below package.json engines.node, strips --experimental-cli / PRETTIER_EXPERIMENTAL_CLI, then dynamically imports either src/experimental-cli/index.js or src/cli/index.js and calls cli.run().
- src/index.js is the Node API: withPlugins loads builtin plus user plugins, then format/check/formatWithCursor/getSupportInfo/inferParser wrap src/main/core.js; it also exports resolveConfig, getFileInfo, doc, util, version, __debug, and __internal (shared with the CLI).
- src/standalone.js reimplements withPlugins without filesystem plugin loading: plugins may be an array or an object (prettierPlugins); there is no resolveConfig/getFileInfo.
- Builtin plugins are generated (do NOT edit) by scripts/generate-builtin-plugins.js into development-plugins.js (language-* modules) and production-plugins.js (src/plugins/*.js bundles), then lazy-loaded via toLazyLoadPlugins. loadBuiltinPlugins() picks production vs development based on NODE_ENV.
- Language implementations live in src/language-{js,json,css,html,graphql,handlebars,markdown,yaml}; each exports languages, parsers, printers, and options. JS printers are a single estree printer; HTML covers angular/html/lwc/mjml/vue parsers with one html printer.
- The build is node ./scripts/build/build.js; scripts/build/packages/index.js emits four packages (prettier, plugin-hermes, plugin-oxc, plugin-yuku) into dist/. prepublishOnly exits 1 so the source tree cannot be published.
- The website is Docusaurus 3 plus a separate Vite/Vue playground (website/vite.config.mjs, website/playground/). Netlify builds with yarn build:website and publishes website/build/. Playground formatting runs in a Worker at /worker.mjs.
- CI is GitHub Actions: Dev runs yarn test (and c8 coverage) on Node 22/26; Prod first yarn build --clean then tests dist/ across Node 14–26, OS matrix, standalone, and Chrome/Firefox ESM+UMD via Puppeteer; Lint runs deps/tsc/eslint/prettier/cspell/changelog/format-test plus actionlint.
- Config resolution is src/config/resolve-config.js: searchPrettierConfig + optional EditorConfig (editorconfig-without-wasm), mergeOverrides via micromatch, and relative plugin paths resolved against the config file directory.
- The experimental CLI is a thin re-export of @prettier/cli/bin (and worker from @prettier/cli/dist/prettier_serial.js), selected by --experimental-cli or PRETTIER_EXPERIMENTAL_CLI rather than a first-party implementation in src/cli.
Feature catalog
Users format via CLI, Node API, or browser standalone. The CLI writes, checks, lists, caches, and inspects files; the API formats text, maps cursor offsets, resolves config, and infers parsers. Built-in languages cover the web stack with multiple JS parsers (babel/acorn/espree/meriyah/oxc/yuku/typescript/flow/hermes). Plugins, EditorConfig, ignore files, pragmas, range format, and a playground are first-class. Prettier refuses new formatting options by policy.
- prettier.format(text, options) is withPlugins(core.formatWithCursor) with cursorOffset -1; prettier.check is format === text; prettier.formatWithCursor returns {formatted, cursorOffset, comments}.
- Public Node APIs also include resolveConfig, resolveConfigFile, clearConfigCache, getFileInfo, getSupportInfo, plus prettier.doc and prettier.util. All public APIs are async; src/index.d.ts is the shipped types.
- CLI surface from src/cli/cli-options.evaluate.js includes --write/-w, --check/-c, --list-different/-l, --cache, --cache-location, --cache-strategy content|metadata, --config/--no-config, --config-precedence cli-override|file-override|prefer-file, --editorconfig, --ignore-path (default .gitignore and .prettierignore), --ignore-unknown/-u, --with-node-modules, --file-info, --find-config-path, --support-info, --help, --version.
- Debug CLI flags --debug-print-doc, --debug-print-ast, --debug-print-comments, --debug-check, --debug-benchmark, --debug-repeat (and PRETTIER_PERF_REPEAT) short-circuit the format path in src/cli/format.js.
- Core format options in src/main/core-options.evaluate.js include parser, plugins (--plugin, array), printWidth (80), tabWidth (2), useTabs, endOfLine (lf|crlf|cr|auto), filepath/--stdin-filepath, rangeStart/rangeEnd, cursorOffset, insertPragma, requirePragma, checkIgnorePragma.
- JS builtin parsers registered in development-plugins.js include babel, babel-flow, babel-ts, acorn, espree, meriyah, oxc, oxc-ts, typescript, flow, hermes, yuku, yuku-ts plus internal __js_expression / __vue_* / __ng_* parsers.
- Non-JS builtins: css/less/scss (postcss), graphql, glimmer/handlebars, html/angular/lwc/mjml/vue, json/json-stringify/json5/jsonc, markdown/mdx/remark, yaml. Parser names are also listed as core option choices.
- Official in-repo extra packages @prettier/plugin-oxc, @prettier/plugin-hermes, and @prettier/plugin-yuku wrap the corresponding JS parsers, set a distinct astFormat (estree-oxc/estree-hermes/estree-yuku), and reuse the estree printer.
- Plugin loading is documented for CLI --plugin, API options.plugins, and config plugins; plugin modules export languages, parsers, printers, options, defaultOptions. Strings go through import() / importFromDirectory.
- Config files searched in order: package.json prettier key, package.yaml, .prettierrc, .prettierrc.json/yml/yaml/json5, .prettierrc.js/ts and prettier.config.js/ts, mjs/mts, cjs/cts, .prettierrc.toml. JS/TS configs are executed via import(pathToFileURL).
- CLI --cache uses FormatResultsCache (file-entry-cache) at node_modules/.cache/prettier/.prettier-cache by default, keyed by prettier version + Node version + stable-stringified options, strategy content (checksum) or metadata (mtime). Cache is forbidden with stdin.
- The playground (website/playground/PrettierFormat.js) posts {type:'format', version, code, options, settings} to a versioned Worker; docs/technical-details.md notes a special parser value doc-explorer for inspecting the Doc IR. Prettier will not accept PRs that add formatting options.
Data flow
Text enters via CLI files/stdin or the format() API. Options are normalized, a parser is inferred or selected, plugins are loaded, and the parser produces an AST. Comments are attached, embedded languages may be re-parsed, printers emit a Doc, and printDocToString measures/breaks groups to a string. CLI then writes, diffs, or caches that string. Config, ignore, and EditorConfig are filesystem side channels that never run in standalone.
- formatWithCursor in src/main/core.js: normalizeFormatOptions → normalizeInputAndOptions (BOM strip, EOL guess/normalize, index clamp) → optional pragma skip → formatRange or coreFormat → re-add BOM. Empty/whitespace-only input short-circuits to formatted: ''.
- normalizeFormatOptions infers parser from filepath via inferParser if options.parser is missing, then initParser/initPrinter, copies locStart/locEnd/getVisitorKeys/astFormat onto options, and fills defaults from getSupportInfo. Throws UndefinedParserError if neither parser nor filepath is usable.
- parse() in src/main/parse.js resolves the parser, optional parser.preprocess, then parser.parse(text, options, options) (third arg kept for v3 compat, slated for removal in v4). Parse errors with loc get a @babel/code-frame attached.
- printAstToDoc: prepareToPrint attaches comments (attachComments) and optional printer.preprocess; printEmbeddedLanguages walks the tree; callPluginPrintFunction dispatches printer.print, prettier-ignore via printIgnored, and comment printing.
- The Doc IR (string | Doc[] | commands such as group/fill/ifBreak/indent/line/cursor) is printed by src/document/printer/printer.js using MODE_FLAT vs MODE_BREAK and a fits() probe against remainingWidth. Public builders are re-exported as prettier.doc.
- Cursor preservation: getCursorLocation finds cursorNode/nodeBeforeCursor/nodeAfterCursor; print wraps those nodes with the cursor symbol; coreFormat diffs old vs new region with diffArrays to compute the new cursorOffset.
- Range format (formatRange) slices AST-calculated rangeStart/rangeEnd, formats with extra alignment, then splices the trimmed result back, converting EOL if needed.
- CLI file input: expandPatterns (literal file, recursive dir **/*, or fast-glob; followSymbolicLinks:false; skip .git/.svn/.hg/.jj/.sl and node_modules unless --with-node-modules) → ignore() from .gitignore/.prettierignore → getOptionsForFile → formatWithCursor → optional fs.writeFile.
- getOptionsForFile: resolveConfig (unless --no-config) with editorconfig flag, then applyConfigPrecedence (cli-override default), temporarily pushContextPlugins if the config lists plugins. Invalid config process.exit(2).
- Config loaders execute user JS/TS/MJS/CJS via import(pathToFileURL); JSON via parse-json; JSON5 via @bybrave/json5; TOML via smol-toml; YAML via the yaml plugin's __parsePrettierYamlConfig. A string prettier field is treated as an external package and loaded with requireFromFile then importFromFile.
- Standalone/browser path never searches the filesystem: plugins must be passed in; docs/browser.md states no config, ignore, CLI, or automatic plugin loading. withPlugins only Object.values(plugins) if not an array.
- Embedded-language formatting (printer.embed) is skipped unless embeddedLanguageFormatting === 'auto' (default). Embed errors are swallowed unless PRETTIER_DEBUG is set. Plugins using the old embed arity throw with a v3 migration URL.
Behavior traces
Three user-visible flows were walked in source. CLI startup validates Node, builds a Context from argv/plugins, then dispatches to version/help/support/config-path/file-info, stdin, or formatFiles. Formatting a file expands globs, resolves per-file options, optionally hits cache, then runs the parse→doc→string pipeline. Persistence is either in-place write via mockable.writeFormattedFile, stdout, or a check/list-different exit code.
- CLI startup order: bin/prettier.cjs run() → version check against engines.node → shouldEnableExperimentalCli → import src/cli/index.js → cli.run(process.argv.slice(2)).
- src/cli/index.js run(): parseArgvWithoutPlugins for log-level → new Context → context.init() (parse --plugin, getContextOptions, parseArgv, filePatterns=argv._) → main(context) with mutually exclusive flag checks.
- main() dispatch: --version prints prettier.version; --help uses createUsage/createDetailedUsage; --support-info → printSupportInfo; --find-config-path → logResolvedConfigPathOrDie; --file-info → logFileInfoOrDie; else stdin if no patterns and (stdin not TTY or --stdin-filepath); else formatFiles; else usage + exitCode 1.
- formatFiles: createIsIgnoredFromContextOrDie → optional FormatResultsCache → for await expandPatterns → skip ignored in write/check/list-different/debug-check → getOptionsForFile → fs.readFile utf8 → cache hit returns input unchanged else format() → maybe mockable.writeFormattedFile → set/remove cache → check summary + exitCode 1 if unformatted and not --write.
- format() inner function: debugPrintDoc → __debug.printToDoc then formatDoc; debugPrintComments → formatWithCursor.comments JSON; debugPrintAst → JSON.stringify(ast); debugCheck double-formats and compares massaged ASTs; else performance loop or prettier.formatWithCursor.
- API format path: prettier.format → formatWithCursor(cursorOffset:-1) → withPlugins loads loadBuiltinPlugins()+loadPlugins(plugins) → core.formatWithCursor → coreFormat: parseText → printAstToDoc → printDocToStringWithoutNormalizeOptions.
- Parser/printer resolve loops plugins.findLast so later plugins override builtins (comment: temporarily solution, see #13729). Missing parser/printer throws ConfigError; in PRETTIER_TARGET=universal the message says plugins must be added to the standalone bundle.
- JS print dispatch in printWithoutParentheses tries printAngular, printJsx, printFlow, printTypescript, then printEstree in order, returning the first defined Doc.
- JS babel parser is createParser(createParse(...)) over @babel/parser with errorRecovery, a long plugin list (doExpressions, decorators, pipelineOperator combinations, etc.), then postprocess. Tests for those plugins live in tests/format/js/babel-plugins/ and are generated by scripts/update-babel-plugin-tests.js.
- oxc parser uses oxc-parser parseSync with preserveParens:true, maps labels to Prettier loc errors via index-to-position, and shares postprocess/createParser with other JS parsers.
- Playground flow: PrettierFormat setup() watches code/options/version → worker.format → WorkerApi posts to /worker.mjs?version=… → worker replies {uid, message, error}. Website README requires yarn build:website so UMD bundles exist in static/lib/.
- Config persistence is not a write of Prettier state: --write overwrites source files; --cache writes .prettier-cache JSON via file-entry-cache.reconcile(); clearConfigCache clears prettier-config, editorconfig, and plugin import caches.
Testing signals
Jest 30 with jest-light-runner drives four suites: snapshot format tests, CLI/API integration tests, unit tests, and dts tests. Format tests are directory fixtures plus runFormatTest, which implicitly adds extra JS parsers and, under FULL_TEST=1, AST-compare, second-format, EOL, and BOM checks. Production tests install dist/prettier and also run standalone and browser (Puppeteer) matrices. Unit tests are skipped in production; integration tests are skipped in standalone/browser.
- jest.config.js testMatch: tests/format/**/format.test.js, tests/integration/__tests__/**/*.js, tests/unit/**/*.js, tests/dts/unit/**/*.js. Runner is jest-light-runner (child-process unless ENABLE_CODE_COVERAGE). Snapshot serializers: jest-snapshot-serializer-raw and -ansi.
- runFormatTest(import.meta, parsers, options) is the format-test DSL. getParsers implicitly adds acorn/espree/meriyah/oxc/yuku when babel is listed under tests/format/js|jsx; babel-ts/oxc-ts/yuku-ts when typescript is listed; hermes when flow is listed; __babel_estree when babel is listed.
- test-format.js snapshots eolVisualizedOutput via createSnapshot; expectFail directories use toThrowErrorMatchingSnapshot. Additional FULL_TEST checks are described in CONTRIBUTING.md (compare AST, second format, CRLF/CR, BOM) and implemented under tests/config/format-test/.
- tests/format is partitioned by language (js, jsx, typescript, flow, css, scss, less, html, vue, angular, markdown, mdx, yaml, graphql, handlebars, json, mjml, lwc, misc) with per-directory __snapshots__/format.test.js.snap. Flow’s tests/format/flow/flow-repo/ is synced via scripts/sync-flow-tests.cjs, not edited by hand.
- Integration tests cover CLI behaviors (check, write, cache, patterns, ignore, stdin, plugins, config-resolution, experimental-cli, node-version-check, help-options) with snapshot files under tests/integration/__tests__/__snapshots__/.
- Unit tests (tests/unit/) cover doc-printer, doc-builders, ast-path, visitor-keys, config-loader, resolve-parser, builtin-plugins, editorconfig-to-prettier, massage-ast; they are ignored when NODE_ENV=production. Bundle tests are ignored in development.
- Production Jest installs the built package via tests/config/install-prettier.js into a temp node_modules/prettier (unless SKIP_PRODUCTION_INSTALL); TEST_STANDALONE and TEST_RUNTIME=browser remap the entry and skip tests/integration/. Browser tests skip several plugin/front-matter format tests.
- package.json test scripts: test=jest; test:dev-package=INSTALL_PACKAGE=1; test:production=NODE_ENV=production; test:production-standalone=TEST_STANDALONE=1; test:production:browser with TEST_RUNTIME_BROWSER chrome|firefox and TEST_BUNDLE_FORMAT umd. Coverage via c8 include src/** and bin/**.
- Lint CI also runs yarn lint:format-test (scripts/format-test-lint.js), prettier check on docs with PRETTIER_DEBUG=true so embedded format errors throw, yarn lint:deps, tsc, knip (separate lint:knip script), and codecov upload of coverage/lcov.info.
- Release-script tests live in scripts/release/tests/; dts tests in tests/dts/unit/run.js (skipped on Node <=14). Dummy plugins under tests/config/prettier-plugins/ exercise embed/async printer/missing comments.
- Prod CLI job runs dist/prettier on Node v0.10.48 solely to assert the engines check message 'Prettier requires at least version 14 of Node.js' — the production engine floor advertised by that job is 14, while package.json engines.node is >=22 for this source tree.
Risk map
The largest security surface is intentional code execution: config files and plugins are imported from user-controlled paths, and --write overwrites arbitrary matched files. Plugin last-wins lets a later plugin replace builtin parsers/printers. Cache writes with restrictAccessToCwd:false. Generated plugin registries and the dist/ publish boundary are easy to edit in the wrong place. Option policy and the removed custom-parser API are social/API traps, not memory-safety bugs.
- loadPlugin/importPlugin will import() a URL, an absolute path, a cwd-relative file, or a node_modules specifier via importFromDirectory (import-meta-resolve from a fake noop.js parent). This is how --plugin and config plugins run third-party code in-process.
- JS/TS Prettier configs are executed, not parsed: loaders['.js'|'.mjs'|'.cjs'|'.ts'|'.mts'|'.cts'] = importModuleDefault via import(pathToFileURL(file)). External string configs try requireFromFile first then importFromFile.
- --write calls mockable.writeFormattedFile(filename, output) which is fs.writeFile; expandPatterns can match any non-ignored file under cwd. docs/cli.md warns this overwrites files. process.exitCode is set per-file rather than aborting the whole run on a single IO error.
- FormatResultsCache constructs file-entry-cache with restrictAccessToCwd: false. --cache-location can point anywhere that is not a directory; default falls back to os.tmpdir() if find-cache-directory fails.
- Parser/printer lookup uses plugins.findLast, so a user or config plugin can silently replace babel/estree/html printers. Integration tests exist for plugin-override-builtin-printers and plugin-override-buitin-plugins (filename typo).
- scripts/release/release.js uses child_process.exec(command) for git fetch --tags and yarn before importing run.js. The comment says it cannot use dependencies because they may not be installed yet. This is maintainer-only, not the formatter runtime.
- development-plugins.js and production-plugins.js are generated ('do NOT edit'); editing them by hand will be overwritten by yarn generate-builtin-plugins. Production plugin graph (src/plugins/*.js) is a different split than language-* modules.
- src/index.cjs in non-production throws if prettier.util or prettier.doc is accessed (ESM-only in dev CJS). Production CJS requires those files directly. Mixing NODE_ENV and CJS consumers is a support trap.
- Custom Parser API was removed in v3.0.0 (docs/api.md); plugins must use the Plugin API. printer.embed with >2 parameters throws, telling authors the v3 plugin API changed.
- No-new-options policy is enforced socially: CONTRIBUTING.md and docs/option-philosophy.md say option-request PRs are closed without discussion. Agents that add a formatting flag will be rejected.
- handleError maps UndefinedParserError to exit 2 (or ignore with --ignore-unknown); ConfigError/validation calls process.exit(1) immediately (fails the whole CLI); parse errors set exitCode 2 and continue. Check mode uses exit 1 for unformatted files.
- Website playground Worker loads /worker.mjs?version= and production Vite aliases npm deps to esm.sh CDN URLs (website/vite.config.mjs buildCdnAlias). That is a supply-chain/runtime dependency of prettier.io, not of the formatter library. (inferred)
Extension points
New languages are plugins: export languages/parsers/printers/options and load via --plugin, options.plugins, or config. Builtins are the same API, registered in the generated lazy-load tables. JS syntax work lands in src/language-js/parse and the printAngular/printJsx/printFlow/printTypescript/printEstree chain. Doc commands are the IR extension point. CLI flags are two tables (cli-options.evaluate.js and core-options.evaluate.js). Config file names are the CONFIG_FILES array.
- A plugin is a module (default or named exports) with languages, parsers (parse/astFormat/locStart/locEnd/hasPragma/preprocess), printers (print/embed/massageAstNode/hasPrettierIgnore/getVisitorKeys), options, defaultOptions.
- toLazyLoadPlugin turns {name, load, parsers[], printers[]} into objects whose parsers/printers entries are async thunks that import() the real module on first use — the intended way builtins stay tree-shakeable.
- Adding a production-bundled parser means adding src/plugins/<name>.js (see src/plugins/babel.js re-exporting language-js/parse/babel plus json parsers) and regenerating production-plugins.js. Dev mode loads language-*/index.js instead.
- JS printer extension: add a node-type handler in src/language-js/print/{estree,jsx,typescript,flow,angular}.js; printWithoutParentheses tries those five in order. Comment attachment hooks are canAttachComment/handleComments/willPrintOwnComments on the estree printer.
- JS parser extension: export from src/language-js/parse/<engine>.js via createParser, then re-export from parse/index.js. babel.js documents the add/remove plugin checklist (tests/format/js/babel-plugins + scripts/update-babel-plugin-tests.js).
- Doc IR extension for printers is the builders in src/document/builders (group, fill, ifBreak, indent, line, cursor, label, trim, …) plus printer.printDocToString. prettier.doc.utils exposes traverseDoc/mapDoc/willBreak. concat is a deprecated alias for identity on arrays, TODO remove in v4.
- CLI option tables: src/cli/cli-options.evaluate.js (CLI-only) merged with API options from getSupportInfo via apiOptionToCliOption in get-context-options.js. New forwarded flags belong in core-options.evaluate.js with cliName/cliCategory.
- CONFIG_FILES in config-searcher.js is the ordered list of config filenames; loaders.js maps extensions to load functions. Adding a config format means both arrays. Search uses search-closest FileSearcher with a stopDirectory mockable.
- Language metadata is linguist-languages plus createLanguage() in each languages.evaluate.js (extensions, filenames, vscodeLanguageIds, interpreters). inferParser matches basename, then extension, then shebang interpreter.
- In-repo extra plugins under packages/plugin-{oxc,hermes,yuku} are the template for shipping an alternate JS parser as a separate npm package: remap astFormat, reuse estree printer, export languages/options from the core tree. They are included in the dist build package list.
- __debug APIs (parse, formatAST, formatDoc, printToDoc, printDocToString) and __internal (shared with CLI) are the in-tree hooks tests and the CLI use; they are not documented as stable public API. mockable objects in src/common/mockable.js and src/cli/mockable.js exist so tests can stub writeFormattedFile/isTTY/getTimestamp.
- changelog_unreleased/<area>/<pr>.md plus yarn generate-changelog is the required contributor extension point for user-facing changes; PRs without it may be closed.
Contributor handoff
Read src/index.js, src/main/core.js, and the language module you are touching before editing. Do not add formatting options. Do not hand-edit generated builtin-plugin files; run generate-builtin-plugins. Format tests are snapshots — yarn test -u then git diff. Prefer small, reviewable diffs. Dev Node is >=22; production behavior is what dist/ does after yarn build. Run the matching tests/format/<lang> directory plus yarn test tests/unit if you change Doc/parser resolve.
- Must-read first files for any change: package.json (scripts/exports), src/index.js (API), src/main/core.js (pipeline), src/main/parser-and-printer.js (plugin resolve), and CONTRIBUTING.md (test DSL + no-new-options).
- Language work: edit src/language-<lang>/ (parse, print, embed, comments) and add a tests/format/<lang>/<topic>/ directory with format.test.js calling runFormatTest(import.meta, [parser]). Never add files next to format.test.js that you do not want snapshotted.
- After changing languages.evaluate.js, options.js, or plugin parser/printer exports, run yarn generate-builtin-plugins (or yarn fix:builtin-plugins) so development-plugins.js and production-plugins.js stay in sync.
- Safe edit boundaries: src/language-* printers and tests/format snapshots are the normal contributor surface. Avoid src/index.cjs dual ESM/CJS shim, bin/prettier.cjs version gate, and scripts/build/hacks/* unless the change is specifically about bundling. (inferred)
- Tests to run locally: yarn test <path> for a focused directory; yarn test -u to update snapshots; FULL_TEST=1 yarn test for AST/second-format/EOL/BOM. Production: yarn build then yarn test:production. Do not use yarn test:production without a dist/.
- yarn debug <file> runs bin/prettier.js with --ignore-path=.prettierignore against the source tree (no dist needed). yarn perf / perf:inspect / perf:benchmark require a production build.
- Trap: plugins listed last win. Tests that list ['babel','flow','typescript'] also run oxc/yuku/hermes/acorn/espree/meriyah implicitly — a JS print change can fail snapshots in those extra parsers even if babel looks fine.
- Trap: standalone and website need explicit plugins (estree must be loaded to print JS). Node API autoloads builtins; copying Node samples into src/standalone.js consumers will fail with ConfigError 'Plugins must be explicitly added to the standalone bundle.'
- Trap: prettier.util/doc are unavailable from the development CJS build. package.json main points at src/index.cjs, so require('prettier') in this checkout without NODE_ENV=production cannot use those namespaces.
- PRs must follow .github/PULL_REQUEST_TEMPLATE.md and add changelog_unreleased/<area>/<id>.md. AI-generated low-quality PRs are closed. ESLint + Prettier are required (yarn fix).
- This repo’s own prettier.config.js forces parser meriyah for **/*.{js,mjs,cjs}, trailingComma none for bin/prettier.cjs, and jsonc for .vscode/*.json and tsconfig.json — dogfooding, not defaults for users.
- Coverage tooling (c8 in package.json, codecov in Dev workflow) excludes src/standalone.js, src/index.cjs, src/plugins/**, and src/universal/*.browser.js — those paths can regress without coverage signal.
Languages
| Language | Files | Share of source |
|---|---|---|
| JavaScript | 5136 | 85% |
| TypeScript | 692 | 11% |
| Markdown | 478 | docs/config |
| YAML | 395 | docs/config |
| HTML | 251 | docs/config |
| CSS | 185 | docs/config |
| JSON | 112 | docs/config |
| Vue | 99 | 2% |
Start reading here
Probable source entrypoint: index.js — inferred from the directory tree and manifest, not the upstream docs.
Source roots
src/ (542 files) packages/ (12 files)
Source layout — src/
| Path | Files |
|---|---|
src/cli/ | 24 |
src/common/ | 7 |
src/config/ | 9 |
src/document/ | 29 |
src/experimental-cli/ | 3 |
src/language-css/ | 29 |
src/language-graphql/ | 0 |
src/constants.js | 1 |
src/index.cjs | 1 |
src/index.d.ts | 1 |
src/index.js | 1 |
Repository structure
| Top-level path | Files |
|---|---|
tests/ | 3036 |
src/ | 542 |
website/ | 182 |
scripts/ | 79 |
changelog_unreleased/ | 65 |
docs/ | 24 |
.github/ | 23 |
packages/ | 12 |
Docs found: CONTRIBUTING.mdREADME.mddocs/api.mddocs/browser.mddocs/ci.mddocs/cli.mddocs/comparison.mddocs/configuration.mddocs/editors.mddocs/for-enterprise.md
Package
package.json — prettier v3.10.0-dev, 80 declared dependencies.
@angular/compiler@babel/code-frame@babel/generator@babel/parser@babel/types@braindb/mdast-util-wiki-link@braindb/micromark-extension-wiki-link@bybrave/json5@clack/prompts@eslint/js@glimmer/syntax@prettier/cli
CI & config entrypoints
README.md— Repository readme.github/workflows/_build.yml— GitHub Actions workflow.github/workflows/autofix.yml— GitHub Actions workflow.github/workflows/bundler-friendly.yml— GitHub Actions workflow.github/workflows/check-sizes.yml— GitHub Actions workflow.github/workflows/cleanup-cspell.yml— GitHub Actions workflow.github/workflows/dev-package-test.yml— GitHub Actions workflow.github/workflows/dev-test.yml— GitHub Actions workflow.github/workflows/eslint-rules.yml— GitHub Actions workflow.github/workflows/lint.yml— GitHub Actions workflow
Structural health signals
Average score 7.2/10 across 96 analyzed source files; 19 hotspots surfaced.
src/language-js/parentheses/needs-parentheses.js— 1030 lines, score 5.5 (Large file, Deep nesting)src/index.d.ts— 959 lines, score 5.5 (Large file, Deep nesting)src/language-handlebars/printer-glimmer.js— 862 lines, score 5.5 (Large file, Deep nesting)
These are deterministic review leads — size, churn, and structural signals — not proof of a bug in this project.
Recent history
1a36a7defdcc2026-09-22 — Support configuration files with UTF-8 BOM (#20127)6bc99db278c82026-09-21 — Prevent comment after head of member chain move around (#20125)0bab82ffb8ca2026-09-21 — Update dependency oxc-parser to v0.151.0 (#20124)31c1ad8abe202026-09-21 — Update dependency eslint-plugin-unicorn to v76 (#20118)51ed2cbbf3b42026-09-20 — Simplify `interpolation` handling in `ngNonBindable` (#20115)