Merge branch 'main' into next
# Conflicts: # package.json # src/language-css/utils.js # yarn.lock
This commit is contained in:
commit
d3c58df606
514 files changed
+17403
-4727
No files matched your search
+15
-10
@@ -5,7 +5,6 @@ env:
|
||||
extends:
|
||||
- eslint:recommended
|
||||
- prettier
|
||||
- prettier/unicorn
|
||||
plugins:
|
||||
- import
|
||||
- unicorn
|
||||
@@ -18,16 +17,19 @@ rules:
|
||||
- as-needed
|
||||
curly: error
|
||||
dot-notation: error
|
||||
eqeqeq:
|
||||
- error
|
||||
- always
|
||||
- null: ignore
|
||||
eqeqeq: error
|
||||
import/no-extraneous-dependencies:
|
||||
- error
|
||||
- devDependencies: ["tests*/**", "scripts/**"]
|
||||
import/order: error
|
||||
no-else-return: error
|
||||
no-else-return:
|
||||
- error
|
||||
- allowElseIf: false
|
||||
no-implicit-coercion: error
|
||||
no-inner-declarations: error
|
||||
no-restricted-syntax:
|
||||
- error
|
||||
- 'BinaryExpression[operator=/^[!=]==$/] > UnaryExpression.left[operator="!"]'
|
||||
no-unneeded-ternary: error
|
||||
no-useless-return: error
|
||||
no-unused-vars:
|
||||
@@ -74,7 +76,9 @@ rules:
|
||||
unicorn/no-useless-undefined: error
|
||||
unicorn/prefer-includes: error
|
||||
unicorn/prefer-number-properties: error
|
||||
unicorn/prefer-optional-catch-binding: error
|
||||
unicorn/prefer-regexp-test: error
|
||||
unicorn/prefer-spread: error
|
||||
unicorn/prefer-string-slice: error
|
||||
overrides:
|
||||
- files:
|
||||
@@ -113,12 +117,12 @@ overrides:
|
||||
prettier-internal-rules/better-parent-property-check-in-needs-parens: error
|
||||
- files: src/**/*.js
|
||||
rules:
|
||||
prettier-internal-rules/flat-ast-path-call: error
|
||||
prettier-internal-rules/no-doc-builder-concat: error
|
||||
prettier-internal-rules/prefer-fast-path-each: error
|
||||
prettier-internal-rules/no-empty-flat-contents-for-if-break: error
|
||||
prettier-internal-rules/prefer-ast-path-each: error
|
||||
prettier-internal-rules/prefer-indent-if-break: error
|
||||
prettier-internal-rules/prefer-is-non-empty-array: error
|
||||
- files: src/document/*.js
|
||||
rules:
|
||||
prettier-internal-rules/no-doc-builder-concat: off
|
||||
- files:
|
||||
- src/language-*/**/*.js
|
||||
rules:
|
||||
@@ -136,3 +140,4 @@ overrides:
|
||||
- "src/language-js/parser-babel.js"
|
||||
- "src/language-js/parser-meriyah.js"
|
||||
- "src/language-js/pragma.js"
|
||||
- "src/language-js/parser/json.js"
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 🤔 Support question
|
||||
url: https://stackoverflow.com/questions/ask?tags=prettier
|
||||
about: Issues are dedicated to development purposes. If you have questions, please use Stack Overflow.
|
||||
@@ -3,15 +3,20 @@ updates:
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
open-pull-requests-limit: 20
|
||||
interval: weekly
|
||||
day: sunday
|
||||
time: "01:00"
|
||||
|
||||
- package-ecosystem: "npm"
|
||||
directory: "/website"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: weekly
|
||||
day: sunday
|
||||
time: "01:00"
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "daily"
|
||||
interval: weekly
|
||||
day: sunday
|
||||
time: "01:00"
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
|
||||
@@ -45,9 +45,12 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
# `codecov/codecov-action` require depth to be at least `2`, see #10219
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
@@ -66,8 +69,6 @@ jobs:
|
||||
uses: codecov/codecov-action@v1.2.1
|
||||
if: matrix.ENABLE_CODE_COVERAGE
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage/lcov.info
|
||||
fail_ci_if_error: true
|
||||
|
||||
# #8073 test
|
||||
@@ -80,6 +81,4 @@ jobs:
|
||||
uses: codecov/codecov-action@v1.2.1
|
||||
if: matrix.ENABLE_CODE_COVERAGE
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage/lcov.info
|
||||
fail_ci_if_error: true
|
||||
@@ -3,11 +3,11 @@ name: Internal_ESLint_Rules_Test
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- "scripts/tools/eslint-plugin-prettier-internal-rules"
|
||||
- "scripts/tools/eslint-plugin-prettier-internal-rules/**"
|
||||
- ".github/workflows/eslint-rules.yml"
|
||||
pull_request:
|
||||
paths:
|
||||
- "scripts/tools/eslint-plugin-prettier-internal-rules"
|
||||
- "scripts/tools/eslint-plugin-prettier-internal-rules/**"
|
||||
- ".github/workflows/eslint-rules.yml"
|
||||
|
||||
jobs:
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Mark Issue Duplicate
|
||||
|
||||
on:
|
||||
issue_comment:
|
||||
types:
|
||||
- created
|
||||
|
||||
jobs:
|
||||
mark-duplicate:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !github.event.issue.pull_request && startsWith(github.event.comment.body, 'Duplicate of ') }}
|
||||
steps:
|
||||
- uses: actions-cool/issues-helper@v2.1.2
|
||||
with:
|
||||
actions: "mark-duplicate"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
duplicate-labels: "type:duplicate"
|
||||
close-issue: true
|
||||
@@ -17,14 +17,14 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
|
||||
- name: Cache Build Results
|
||||
id: build-cache
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v2.1.4
|
||||
with:
|
||||
path: .cache
|
||||
key: v2-build-cache-${{ hashFiles('yarn.lock') }}-${{ hashFiles('scripts/build/**/*') }}-${{ github.ref }}-
|
||||
@@ -61,7 +61,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
|
||||
- name: Install Dependencies
|
||||
run: yarn install --frozen-lockfile
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
uses: actions/checkout@v2.3.4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v2.1.4
|
||||
uses: actions/setup-node@v2.1.5
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
name: "Support requests"
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled, reopened]
|
||||
|
||||
jobs:
|
||||
support:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/support-requests@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
support-label: "type:question"
|
||||
issue-comment: >
|
||||
:wave: @{issue-author}, we use the issue tracker exclusively for development purposes.
|
||||
For questions, please use [Stack Overflow](https://stackoverflow.com/questions/ask?tags=prettier).
|
||||
@@ -23,3 +23,4 @@ package-lock.json
|
||||
.pnp.*
|
||||
.nyc_output
|
||||
/scripts/tools/eslint-plugin-prettier-internal-rules/node_modules
|
||||
.devcontainer
|
||||
@@ -10,3 +10,4 @@ coverage/
|
||||
/website/static/lib/
|
||||
/website/static/playground.js
|
||||
cspell.json
|
||||
.nyc_output
|
||||
+76
-15
@@ -4,30 +4,76 @@ To get up and running, install the dependencies and run the tests:
|
||||
|
||||
```bash
|
||||
yarn
|
||||
yarn lint:eslint
|
||||
yarn test
|
||||
```
|
||||
|
||||
Here's what you need to know about the tests:
|
||||
## Tests
|
||||
|
||||
- The tests use [Jest snapshots](https://facebook.github.io/jest/docs/en/snapshot-testing.html).
|
||||
- You can make changes and run `jest -u` (or `yarn test -u`) to update the snapshots. Then run `git diff` to take a look at what changed. Always update the snapshots when opening a PR.
|
||||
- You can run `FULL_TEST=1 jest` for a more robust test run.
|
||||
- "compare AST" That re-parses the output, and compares the new AST with the original one and makes sure they are semantically equivalent.
|
||||
- "second format" That formats the output again, and checks that the second output is the same as the first.
|
||||
- "EOL "\r\n"" That replace end of line with `\r\n`, and checks that the output is the same as the first.
|
||||
- "EOL "\r"" That replace end of line with `\r`, and checks that the output is the same as the first.
|
||||
- "BOM" That add BOM(`U+FEFF`) to text, and checks that the output is the same as the first, and keeps the BOM.
|
||||
- Each test folder has a `jsfmt.spec.js` that runs the tests. For JavaScript files, generally you can just put `run_spec(__dirname, ["babel", "flow", "typescript"]);` there. This will verify that the output using each parser is the same. You can also pass options as the third argument, like this: `run_spec(__dirname, ["babel"], { trailingComma: "es5" });`
|
||||
- `tests/flow-repo/` contains the Flow test suite, and is not supposed to be edited by hand. To update it, clone the Flow repo next to the Prettier repo and run: `node scripts/sync-flow-tests.js ../flow/tests/`.
|
||||
- If you would like to debug prettier locally, you can either debug it in Node or the browser. The easiest way to debug it in Node, is to create a local test file with some example code you want formatted and either run it in an editor like VS Code or run it directly via `./bin/prettier.js <your_test_file>`. The easiest way to debug it in the browser is to build Prettier's website locally (see `website/README.md`).
|
||||
The tests use [Jest snapshots](https://facebook.github.io/jest/docs/en/snapshot-testing.html). You can make changes and run `jest -u` (or `yarn test -u`) to update the snapshots. Then run `git diff` to take a look at what changed. Always update the snapshots when opening a PR.
|
||||
|
||||
Run `yarn lint:eslint --fix` to automatically format files.
|
||||
Each test directory in `tests` has a `jsfmt.spec.js` file that controls how exactly the rest of the files in the directory are used for tests. This file must contain one or more calls to the `run_spec` global function. For example, in directories with JavaScript formatting tests, `jsfmt.spec.js` generally looks like this:
|
||||
|
||||
If you can, take look at [commands.md](commands.md) and check out [Wadler's paper](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) to understand how Prettier works.
|
||||
```js
|
||||
run_spec(__dirname, ["babel", "flow", "typescript"]);
|
||||
```
|
||||
|
||||
This verifies that for each file in the directory, the output matches the snapshot and is the same for each listed parser.
|
||||
|
||||
You can also pass options as the third argument:
|
||||
|
||||
```js
|
||||
run_spec(__dirname, ["babel"], { trailingComma: "es5" });
|
||||
```
|
||||
|
||||
Signature:
|
||||
|
||||
```ts
|
||||
function run_spec(
|
||||
fixtures:
|
||||
| string
|
||||
| {
|
||||
dirname: string;
|
||||
snippets?: Array<
|
||||
| string
|
||||
| { code: string; name?: string; filename?: string; output?: string }
|
||||
>;
|
||||
},
|
||||
parsers: string[],
|
||||
options?: PrettierOptions & {
|
||||
errors: true | { [parserName: string]: true | string[] };
|
||||
}
|
||||
): void;
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
- **`fixtures`**: Must be set to `__dirname` or to an object of the shape `{ dirname: __dirname, ... }`. The object may have the `snippets` property to specify an array of extra input entries in addition to the files in the current directory. For each input entry (a file or a snippet), `run_spec` configures and runs a number of tests. The main check is that for a given input the output should match the snapshot (for snippets, the expected output can also be specified directly). [Additional checks](#deeper-testing) are controlled by options and environment variables.
|
||||
- **`parsers`**: A list of parser names. The tests verify that the parsers in this list produce the same output. If the list includes `typescript`, then `babel-ts` is included implicitly. If the list includes `babel`, and the current directory is inside `tests/js`, then `espree` and `meriyah` are included implicitly.
|
||||
- **`options`**: In addition to Prettier's formatting option, can contain the `errors` property to specify that it's expected that the formatting shouldn't be successful and an error should be thrown for all (`errors: true`) or some combinations of input entries and parsers.
|
||||
|
||||
The implementation of `run_spec` can be found in [`tests_config/run_spec.js`](tests_config/run_spec.js).
|
||||
|
||||
`tests/flow-repo/` contains the Flow test suite and is not supposed to be edited by hand. To update it, clone the Flow repo next to the Prettier repo and run: `node scripts/sync-flow-tests.js ../flow/tests/`.
|
||||
|
||||
## Debugging
|
||||
|
||||
To debug Prettier locally, you can either debug it in Node (recommended) or the browser.
|
||||
|
||||
- The easiest way to debug it in Node is to create a local test file with some example code you want formatted and either run it in an editor like VS Code or run it directly via `./bin/prettier.js <your_test_file>`.
|
||||
- The easiest way to debug it in the browser is to build Prettier's website locally (see [`website/README.md`](website/README.md)).
|
||||
|
||||
## Other
|
||||
|
||||
The project uses ESLint for linting and Prettier for formatting. If your editor isn't set up to work with them, you can lint and format all files from the command line using `yarn fix`.
|
||||
|
||||
After opening a PR, describe your changes in a file in the `changelog_unreleased` directory following the template [`changelog_unreleased/TEMPLATE.md`](changelog_unreleased/TEMPLATE.md) and commit this file to your PR.
|
||||
|
||||
Take a look at [`commands.md`](commands.md) and, if you know Haskell, check out [Wadler's paper](http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf) to understand how Prettier works.
|
||||
|
||||
If you want to know more about Prettier's GitHub labels, see the [Issue Labels](https://github.com/prettier/prettier/wiki/Issue-Labels) page on the Wiki.
|
||||
|
||||
# Advanced topics
|
||||
|
||||
## Performance
|
||||
|
||||
If you're contributing a performance improvement, the following Prettier CLI options can help:
|
||||
@@ -52,3 +98,18 @@ In the above commands:
|
||||
- `> /dev/null` ensures the formatted output is discarded.
|
||||
|
||||
In addition to the options above, you can use [`node --prof` and `node --prof-process`](https://nodejs.org/en/docs/guides/simple-profiling/), as well as `node --trace-opt --trace-deopt`, to get more advanced performance insights.
|
||||
|
||||
## Regression testing
|
||||
|
||||
We have a cool tool for regression testing that runs on GitHub Actions. Have a look: https://github.com/prettier/prettier-regression-testing
|
||||
|
||||
## Deeper testing
|
||||
|
||||
You can run `FULL_TEST=1 jest` for a more robust test run, which includes the following additional checks:
|
||||
|
||||
- **compare AST** - re-parses the output and makes sure the new AST is equivalent to the original one.
|
||||
- **second format** - formats the output again and checks that the second output is the same as the first.
|
||||
- **EOL '\r\n'** and **EOL '\r'** - check that replacing line endings with `\r\n` or `\r` in the input doesn't affect the output.
|
||||
- **BOM** - checks that adding BOM (`U+FEFF`) to the input affects the output in only one way: the BOM is preserved.
|
||||
|
||||
Usually there is no need to run these extra checks locally, since they're run on the CI anyway.
|
||||
@@ -0,0 +1,34 @@
|
||||
#### Fix `lineSuffixBoundary` (#10122 by @thorn0)
|
||||
|
||||
There was a bug in the implementation of the [`lineSuffixBoundary`][lsb] command that significantly limited its usefulness: the printer algorithm didn't correctly consider it a potential line break. Now that the bug has been fixed, we urge plugin authors to give this command another try and see if it can help them simplify printing of trailing comments.
|
||||
|
||||
[lsb]: https://github.com/prettier/prettier/blob/main/commands.md#linesuffixboundary
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```jsx
|
||||
// Input
|
||||
group([
|
||||
"let foo = [",
|
||||
indent([
|
||||
softline,
|
||||
[lineSuffixBoundary, "item1,"],
|
||||
line,
|
||||
[lineSuffixBoundary, "item2,", lineSuffix(" // comment")],
|
||||
line,
|
||||
[lineSuffixBoundary, "item3"],
|
||||
]),
|
||||
softline,
|
||||
"];",
|
||||
])
|
||||
|
||||
// Prettier stable
|
||||
let foo = [item1, item2, // comment
|
||||
item3];
|
||||
|
||||
// Prettier main
|
||||
let foo = [
|
||||
item1,
|
||||
item2, // comment
|
||||
item3
|
||||
];
|
||||
```
|
||||
@@ -0,0 +1,3 @@
|
||||
#### Add `indentIfBreak` IR command (#10221 by @thorn)
|
||||
|
||||
`indentIfBreak(doc, { groupId })` is an optimized version of `ifBreak(indent(doc), doc, { groupId })`.
|
||||
@@ -4,6 +4,8 @@ To simplify the code of AST printers, the data structure for the concatenation c
|
||||
|
||||
If you're a plugin author, this change should only concern you if your plugin introspects or modifies composed docs. If it happens to be the case, please make your plugin compatible with future versions of Prettier by tweaking the introspecting code to support the new format. There also is an off-chance where this change can break things, namely if a plugin calls another plugin to [print an embedded language](https://prettier.io/docs/en/plugins.html#optional-embed) and then introspects the returned doc. There seems to be no reason for plugins to do that though.
|
||||
|
||||
To replace `concat(…)` calls in your plugins, you can try auto-fix by this ESLint rule [`prettier-doc/no-concat`](https://github.com/fisker/eslint-plugin-prettier-doc#no-concat).
|
||||
|
||||
```jsx
|
||||
// Prettier stable
|
||||
myDoc = group(concat(["foo", line, "bar"]));
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#### Add CLI flag for debugging comment attachment issues (#10124 by @thorn0)
|
||||
|
||||
A new `--debug-print-comments` CLI flag and corresponding functionality for the Playground.
|
||||
@@ -0,0 +1,3 @@
|
||||
#### Round-trippable `--debug-print-doc` (#10169, #10177 by @thorn0)
|
||||
|
||||
The idea is to make the output of `--debug-print-doc` closer to actual code for generating docs (Prettier's intermediate representation). Ideally, it should be possible for it to work without modification after copy-pasting into a JS file. That ideal hasn't probably been reached by this PR, but it's pretty close. This is going to make `--debug-print-doc` and the corresponding part of the Playground a bit more useful.
|
||||
@@ -0,0 +1,11 @@
|
||||
#### Print an error message when `--find-config-path` can't find config file (#10208 by @fisker)
|
||||
|
||||
```console
|
||||
# Prettier stable
|
||||
$ prettier --find-config-path /prettier.js
|
||||
# Silently failed
|
||||
|
||||
# Prettier main
|
||||
$ prettier --find-config-path /prettier.js
|
||||
[error] Can not find configure file for "/prettier.js"
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
#### Clear printed long filenames of formatting file (#10217 by @fisker)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```console
|
||||
# Prettier stable
|
||||
$ prettier tests/flow-repo/config_module_system_node_resolve_dirname --check
|
||||
Checking formatting...
|
||||
tests\flow-repo\config_module_system_node_resolve_dirname\custom_resolve_dir\tes
|
||||
tests\flow-repo\config_module_system_node_resolve_dirname\custom_resolve_dir\tes
|
||||
tests\flow-repo\config_module_system_node_resolve_dirname\subdir\custom_resolve_
|
||||
All matched files use Prettier code style!
|
||||
|
||||
// Prettier main
|
||||
$ prettier tests/flow-repo/config_module_system_node_resolve_dirname --check
|
||||
Checking formatting...
|
||||
All matched files use Prettier code style!
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
#### Don't skip file paths containing special words like `constructor` (#10256 by @ashlkv)
|
||||
|
||||
Directories whose names happened to coincide with the properties of `Object.prototype` were ignored by Prettier CLI because of a classic bug (introduced in Prettier 2.0.0) with object properties not being checked for being own.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```sh
|
||||
# Prettier stable
|
||||
$ prettier 'js/constructor/*.js' --write
|
||||
[error] No matching files. Patterns: js/constructor/*.js
|
||||
|
||||
# Prettier main
|
||||
prettier 'js/constructor/*.js' --write
|
||||
js/constructor/test.js 42ms
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
#### Add CLI option to prevent errors on unmatched pattern (#8233 by @daronmcintosh)
|
||||
|
||||
The following command will not throw an error when it doesn't find a YAML file.
|
||||
|
||||
```sh
|
||||
// Prettier stable
|
||||
$ npx prettier --check "prettier/docs/*.yaml"
|
||||
Checking formatting...
|
||||
[error] No files matching the pattern were found: "prettier/docs/*.yaml".
|
||||
All matched files use Prettier code style!
|
||||
|
||||
// Prettier main
|
||||
$ npx prettier --check --no-error-on-unmatched-pattern "prettier/docs/*.yaml"
|
||||
Checking formatting...
|
||||
All matched files use Prettier code style!
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
#### Support `this` type annotation in functions via `babel-flow` (#10397 by @sosukesuzuki)
|
||||
|
||||
`this` type annotation is supported since [Babel 7.13](https://babeljs.io/blog/2021/02/22/7.13.0#new-flow-features-12193httpsgithubcombabelbabelpull12193-12234httpsgithubcombabelbabelpull12234).
|
||||
|
||||
```js
|
||||
// Input
|
||||
var foo: (this: boolean) => void;
|
||||
|
||||
// Prettier stable
|
||||
SyntaxError: Unexpected token, expected ")" (1:15)
|
||||
|
||||
// Prettier main
|
||||
var foo: (this: boolean) => void;
|
||||
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
#### Fix formatting of attributes (#10145 by @thorn0)
|
||||
|
||||
- fix escaping of `{{` in attributes and text
|
||||
- fix the choice between `'` and `"` for attributes with interpolations
|
||||
- fix the bug with `[object Object]` printed in the `class` attribute
|
||||
- implement simple formatting for the `class` attribute, like Prettier formatted it in HTML before v2.3.0
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```html
|
||||
// Input
|
||||
<div class="
|
||||
foo"></div>
|
||||
<div bar='"{{expr}}"'></div>
|
||||
<div baz="\{{ non-expression }}"></div>
|
||||
|
||||
// Prettier stable
|
||||
<div class="[object Object],foo"></div>
|
||||
<div bar=""{{expr}}""></div>
|
||||
<div baz="{{ non-expression }}"></div>
|
||||
|
||||
// Prettier main
|
||||
<div class="foo"></div>
|
||||
<div bar='"{{expr}}"'></div>
|
||||
<div baz="\{{ non-expression }}"></div>
|
||||
```
|
||||
@@ -0,0 +1,20 @@
|
||||
#### Split text content on multiple lines (#10179 by @dcyriller)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```handlebars
|
||||
{{!-- Input --}}
|
||||
<div>
|
||||
A long enough string to trigger a line break that would prevent wrapping more and more.
|
||||
</div>
|
||||
|
||||
{{!-- Prettier stable --}}
|
||||
<div>
|
||||
A long enough string to trigger a line break that would prevent wrapping more and more.
|
||||
</div>
|
||||
|
||||
{{!-- Prettier main --}}
|
||||
<div>
|
||||
A long enough string to trigger a line break that would prevent wrapping more
|
||||
and more.
|
||||
</div>
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
#### Add a whitespace sensitive mode (#9885 by @dcyriller)
|
||||
|
||||
```sh
|
||||
--html-whitespace-sensitivity strict
|
||||
```
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```hbs
|
||||
{{!-- Input --}}
|
||||
<span>123 {{mustache}}</span>
|
||||
<span>
|
||||
123 {{mustache}}</span>
|
||||
<span>123 {{mustache}}
|
||||
</span>
|
||||
<span>
|
||||
123 {{mustache}}
|
||||
</span>
|
||||
|
||||
{{!-- Prettier stable --}}
|
||||
<span>
|
||||
123 {{mustache}}
|
||||
</span>
|
||||
<span>
|
||||
123 {{mustache}}
|
||||
</span>
|
||||
<span>
|
||||
123 {{mustache}}
|
||||
</span>
|
||||
<span>
|
||||
123 {{mustache}}
|
||||
</span>
|
||||
|
||||
{{!-- Prettier main --}}
|
||||
<span>123 {{mustache}}</span>
|
||||
<span>
|
||||
123
|
||||
{{mustache}}</span>
|
||||
<span>123
|
||||
{{mustache}}
|
||||
</span>
|
||||
<span>
|
||||
123
|
||||
{{mustache}}
|
||||
</span>
|
||||
```
|
||||
@@ -0,0 +1,45 @@
|
||||
#### Concise formatting of number-only arrays (#10106, #10160 by @thorn0)
|
||||
|
||||
While in general, Prettier avoids this kind of formatting because it's not diff-friendly, in this special case we decided that the benefits outweigh the risks.
|
||||
|
||||
If at least one element has a trailing single-line (`// ...`) comment on the same line, the concise formatting isn't applied. On the other hand, single-line comments placed on separate lines don't have such an effect and – as well as empty lines – can be used for logical grouping.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```jsx
|
||||
// Input
|
||||
const lazyCatererNumbers = [1, 2, 4, 7, 11, 16, 22, 29, 37, 46,
|
||||
|
||||
// n > 10
|
||||
56, 67, 79, 92, 106, 121, 137, 154, 172, 191, 211, 232, 254, 277, 301, 326, 352, 379, 407, 436, 466,
|
||||
497, 529, 562, 596, 631, 667, 704, 742, 781,
|
||||
// n > 40
|
||||
821, 862, 904, 947, 991, 1036, 1082, 1129, 1177, 1226, 1276, 1327, 1379];
|
||||
|
||||
// Prettier stable
|
||||
const lazyCatererNumbers = [
|
||||
1,
|
||||
2,
|
||||
4,
|
||||
7,
|
||||
11,
|
||||
16,
|
||||
22,
|
||||
29,
|
||||
37,
|
||||
// ... ✂ a lot of lines ✂ ...
|
||||
1379,
|
||||
];
|
||||
|
||||
|
||||
// Prettier main
|
||||
const lazyCatererNumbers = [
|
||||
1, 2, 4, 7, 11, 16, 22, 29, 37, 46,
|
||||
|
||||
// n > 10
|
||||
56, 67, 79, 92, 106, 121, 137, 154, 172, 191, 211, 232, 254, 277, 301, 326,
|
||||
352, 379, 407, 436, 466, 497, 529, 562, 596, 631, 667, 704, 742, 781,
|
||||
// n > 40
|
||||
821, 862, 904, 947, 991, 1036, 1082, 1129, 1177, 1226, 1276, 1327, 1379,
|
||||
];
|
||||
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
#### Break assignment whose rhs is wrapped by an unary expression (#10158 by @sosukesuzuki)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
const loooooooooooooooooooooooooong = !looooooooooooooong.looooooooooooooong.loooooong;
|
||||
|
||||
// Prettier stable
|
||||
const loooooooooooooooooooooooooong = !looooooooooooooong.looooooooooooooong
|
||||
.loooooong;
|
||||
|
||||
// Prettier main
|
||||
const loooooooooooooooooooooooooong =
|
||||
!looooooooooooooong.looooooooooooooong.loooooong;
|
||||
```
|
||||
@@ -0,0 +1,67 @@
|
||||
#### Consistent indentations for conditional operators (#10187, #10266 by @sosukesuzuki)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
const dotNotationMemberExpression = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
).TSESTree.BinaryExpression;
|
||||
const computedMemberExpression = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
)[TSESTree.BinaryExpression];
|
||||
const callExpressionCallee = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
)(TSESTree.BinaryExpression);
|
||||
const typeScriptAsExpression = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
) as TSESTree.BinaryExpression;
|
||||
|
||||
// Prettier stable
|
||||
const dotNotationMemberExpression = (callNode.parent?.type ===
|
||||
AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
).TSESTree.BinaryExpression;
|
||||
const computedMemberExpression = (callNode.parent?.type ===
|
||||
AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent)[TSESTree.BinaryExpression];
|
||||
const callExpressionCallee = (callNode.parent?.type ===
|
||||
AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent)(TSESTree.BinaryExpression);
|
||||
const typeScriptAsExpression = (callNode.parent?.type ===
|
||||
AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent) as TSESTree.BinaryExpression;
|
||||
|
||||
// Prettier main
|
||||
const dotNotationMemberExpression = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
).TSESTree.BinaryExpression;
|
||||
const computedMemberExpression = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
)[TSESTree.BinaryExpression];
|
||||
const callExpressionCallee = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
)(TSESTree.BinaryExpression);
|
||||
const typeScriptAsExpression = (
|
||||
callNode.parent?.type === AST_NODE_TYPES.ChainExpression
|
||||
? callNode.parent.parent
|
||||
: callNode.parent
|
||||
) as TSESTree.BinaryExpression;
|
||||
```
|
||||
@@ -0,0 +1,57 @@
|
||||
#### Format assignments more consistently (#10222 by @thorn0)
|
||||
|
||||
Previously, Prettier had a lot of trouble with figuring out how to break lines in assignments. E.g., long right-hand sides often stayed unbroken. Not anymore.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
aParticularlyLongAndObnoxiousNameForIllustrativePurposes =
|
||||
anotherVeryLongNameForIllustrativePurposes;
|
||||
|
||||
aParticularlyLongAndObnoxiousNameForIllustrativePurposes =
|
||||
'a very long string for illustrative purposes'.length;
|
||||
|
||||
someReallyLongThingStoredInAMapWithAReallyBigName[pageletID] =
|
||||
_someVariableThatWeAreCheckingForFalsiness
|
||||
? Date.now() - _someVariableThatWeAreCheckingForFalsiness
|
||||
: 0;
|
||||
|
||||
class x {
|
||||
private readonly rawConfigFromFile$: BehaviorSubject<any> = new BehaviorSubject(notRead);
|
||||
}
|
||||
|
||||
// Prettier stable
|
||||
aParticularlyLongAndObnoxiousNameForIllustrativePurposes = anotherVeryLongNameForIllustrativePurposes;
|
||||
|
||||
aParticularlyLongAndObnoxiousNameForIllustrativePurposes = "a very long string for illustrative purposes"
|
||||
.length;
|
||||
|
||||
someReallyLongThingStoredInAMapWithAReallyBigName[
|
||||
pageletID
|
||||
] = _someVariableThatWeAreCheckingForFalsiness
|
||||
? Date.now() - _someVariableThatWeAreCheckingForFalsiness
|
||||
: 0;
|
||||
|
||||
class x {
|
||||
private readonly rawConfigFromFile$: BehaviorSubject<any> = new BehaviorSubject(
|
||||
notRead
|
||||
);
|
||||
}
|
||||
|
||||
// Prettier main
|
||||
aParticularlyLongAndObnoxiousNameForIllustrativePurposes =
|
||||
anotherVeryLongNameForIllustrativePurposes;
|
||||
|
||||
aParticularlyLongAndObnoxiousNameForIllustrativePurposes =
|
||||
"a very long string for illustrative purposes".length;
|
||||
|
||||
someReallyLongThingStoredInAMapWithAReallyBigName[pageletID] =
|
||||
_someVariableThatWeAreCheckingForFalsiness
|
||||
? Date.now() - _someVariableThatWeAreCheckingForFalsiness
|
||||
: 0;
|
||||
|
||||
class x {
|
||||
private readonly rawConfigFromFile$: BehaviorSubject<any> =
|
||||
new BehaviorSubject(notRead);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
#### Improve formatting for React Hooks call (#10238 by @sosukesuzuki)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
const { firstName, lastName } = useMemo(
|
||||
() => parseFullName(fullName),
|
||||
[fullName]
|
||||
);
|
||||
|
||||
// Prettier stable
|
||||
const { firstName, lastName } = useMemo(() => parseFullName(fullName), [
|
||||
fullName,
|
||||
]);
|
||||
|
||||
// Prettier main
|
||||
const { firstName, lastName } = useMemo(
|
||||
() => parseFullName(fullName),
|
||||
[fullName]
|
||||
);
|
||||
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
#### Fix error thrown on expressions like `a(b => c => function (){})` (#10278 by @thorn0)
|
||||
|
||||
Regression from v2.2.0.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```jsx
|
||||
// Input
|
||||
a(b => c => function (){})
|
||||
|
||||
// Prettier stable
|
||||
TypeError: Cannot read property 'length' of undefined
|
||||
|
||||
// Prettier main
|
||||
a((b) => (c) => function () {});
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
#### Improve formatting for inline decorators (#10296 by @fisker)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```jsx
|
||||
// Input
|
||||
class Foo {
|
||||
@decorator([]) bar() {}
|
||||
@decorator(
|
||||
[]
|
||||
) baz() {}
|
||||
}
|
||||
|
||||
// Prettier stable
|
||||
class Foo {
|
||||
@decorator([]) bar() {}
|
||||
@decorator([])
|
||||
baz() {}
|
||||
}
|
||||
|
||||
// Prettier main
|
||||
class Foo {
|
||||
@decorator([]) bar() {}
|
||||
@decorator([]) baz() {}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
#### Disable Babel's error recovery for invalid escaped characters in identifiers (#10322 by @thorn0)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
const \u{20} = 1
|
||||
|
||||
// Prettier stable
|
||||
const = 1;
|
||||
|
||||
// Prettier main
|
||||
[error] stdin: SyntaxError: Invalid Unicode escape (1:7)
|
||||
[error] > 1 | const \u{20} = 1
|
||||
[error] | ^ ^
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
#### Fix ASI protection for private fields (#10334 by @thorn0)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```jsx
|
||||
// Input
|
||||
class C {
|
||||
#field = 'value';
|
||||
["method"]() {}
|
||||
}
|
||||
|
||||
// Prettier stable (with --no-semi)
|
||||
class C {
|
||||
#field = "value"
|
||||
["method"]() {}
|
||||
}
|
||||
|
||||
// Prettier main (with --no-semi)
|
||||
class C {
|
||||
#field = "value";
|
||||
["method"]() {}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,22 @@
|
||||
#### Prevent wrapping object properties with short keys (#10335 by @thorn0)
|
||||
|
||||
Line breaks after short property names in object literals often look unnatural. Even when such a line break yields a line length benefit of 1 or 2 characters, it rarely looks justified. Prettier main avoids line breaks after property names shorter than `tabWidth + 3`. The "+3" part has been chosen rather arbitrarily. It may be revised upward in future versions, provided there is a really good rationale.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
const importantLink = {
|
||||
url: "https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about"
|
||||
};
|
||||
|
||||
// Prettier stable
|
||||
const importantLink = {
|
||||
url:
|
||||
"https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about"
|
||||
};
|
||||
|
||||
// Prettier main
|
||||
const importantLink = {
|
||||
url: "https://prettier.io/docs/en/rationale.html#what-prettier-is-concerned-about"
|
||||
};
|
||||
```
|
||||
@@ -0,0 +1,34 @@
|
||||
#### Improve formatting for nested await expressions in heads of member and call expressions (#10342 by @thorn0)
|
||||
|
||||
Even though Prettier tries to be helpful here, please don't write code like this. Have mercy upon your teammates and use intermediate variables.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```jsx
|
||||
// Input
|
||||
const getAccountCount = async () =>
|
||||
(await
|
||||
(await (
|
||||
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
|
||||
).findItem("My bookmarks")).getChildren()
|
||||
).length
|
||||
|
||||
// Prettier stable
|
||||
const getAccountCount = async () =>
|
||||
(
|
||||
await (
|
||||
await (await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)).findItem(
|
||||
"My bookmarks"
|
||||
)
|
||||
).getChildren()
|
||||
).length;
|
||||
|
||||
// Prettier main
|
||||
const getAccountCount = async () =>
|
||||
(
|
||||
await (
|
||||
await (
|
||||
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
|
||||
).findItem("My bookmarks")
|
||||
).getChildren()
|
||||
).length;
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
#### Don't use smart quotes for JSON5 with `--quote-props=preserve` (#10323 by @thorn0)
|
||||
|
||||
With the `quoteProps` option set to `preserve` and `singleQuotes` to `false` (default), double quotes are always used for printing strings, including situations like `"bla\"bla"`. This effectively allows using `--parser json5` for "JSON with comments and trailing commas".
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
{
|
||||
"char": "\"",
|
||||
}
|
||||
|
||||
// Prettier stable
|
||||
{
|
||||
"char": '"',
|
||||
}
|
||||
|
||||
// Prettier main
|
||||
{
|
||||
"char": "\"",
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
#### Stricter JSON parser (#10346, #10443, #10456 by @fisker)
|
||||
|
||||
Prettier internally uses a JavaScript expression parser to parse JSON. That's why the `json` and `json5` parsers used to be very forgiving and allowed all kinds of JavaScript expressions. Now they are much stricter, although some simple non-standard syntax is still allowed (e.g., [JSON6](https://github.com/d3x0r/JSON6) is supported, except for multiple minus signs: `----123`).
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
[1, 2, 1 + 2]
|
||||
|
||||
// Prettier stable
|
||||
[1, 2, 1 + 2]
|
||||
|
||||
// Prettier main
|
||||
SyntaxError: BinaryExpression is not allowed in JSON. (1:8)
|
||||
> 1 | [1, 2, 1 + 2]
|
||||
| ^
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
#### Improve error message (#10433 by @fisker)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
{key: "foo" + "bar"}
|
||||
|
||||
// Prettier stable (--parser=json-stringify)
|
||||
SyntaxError: BinaryExpression is not allowed in JSON. (1:7)
|
||||
> 1 | {key: "foo" + "bar"}
|
||||
| ^
|
||||
|
||||
// Prettier main
|
||||
SyntaxError: BinaryExpression is not allowed in JSON. (1:7)
|
||||
> 1 | {key: "foo" + "bar"}
|
||||
| ^^^^^^^^^^^^^
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
#### Support YAML document end marker in front matter (#9878 by @michaelbeaumont)
|
||||
|
||||
Add the ability to delimit the end of front matter with `...`.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```markdown
|
||||
<!-- Input -->
|
||||
---
|
||||
title: Hello
|
||||
slug: home
|
||||
...
|
||||
|
||||
Markdown
|
||||
|
||||
<!-- Prettier stable -->
|
||||
---
|
||||
|
||||
title: Hello
|
||||
slug: home
|
||||
...
|
||||
|
||||
Markdown
|
||||
|
||||
<!-- Prettier main -->
|
||||
---
|
||||
title: Hello
|
||||
slug: home
|
||||
...
|
||||
|
||||
Markdown
|
||||
```
|
||||
@@ -0,0 +1,17 @@
|
||||
#### Fix maps with keys that are lists or maps (#10005 by @fisker)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```scss
|
||||
// Input
|
||||
$map: (
|
||||
('my list'): 'hello world',
|
||||
);
|
||||
|
||||
// Prettier stable
|
||||
TypeError: Cannot read property 'length' of undefined
|
||||
|
||||
// Prettier main
|
||||
$map: (
|
||||
("my list"): "hello world",
|
||||
);
|
||||
```
|
||||
@@ -0,0 +1,25 @@
|
||||
#### Fix unnecessary line breaks in method type declaration parameters (#10024 by @sosukesuzuki, #10357 by @thorn0)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```js
|
||||
// Input
|
||||
type Foo = {
|
||||
method(foo: "foo"): `
|
||||
`
|
||||
};
|
||||
|
||||
// Prettier stable
|
||||
type Foo = {
|
||||
method(
|
||||
foo: "foo"
|
||||
): `
|
||||
`;
|
||||
};
|
||||
|
||||
// Prettier main
|
||||
type Foo = {
|
||||
method(foo: "foo"): `
|
||||
`;
|
||||
};
|
||||
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
#### Print trailing commas in type parameters (#10109 by @sosukesuzuki, #10353 by @thorn0)
|
||||
|
||||
TypeScript has been supporting trailing commas in type parameters since TypeScript 2.7 released in January 2018. Prettier main prints them if the `trailingComma` option is set to `all`. Keep this option at the more conservative default value `es5` if compatibility with TypeScript 2.7 or older is needed. Note that TypeScript [still doesn't support][ts-issue-21984] trailing commas in type arguments (type parameter instantiations).
|
||||
|
||||
[ts-issue-21984]: https://github.com/microsoft/TypeScript/issues/21984
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
export class BaseSingleLevelProfileTargeting<
|
||||
T extends ValidSingleLevelProfileNode,
|
||||
> {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Prettier stable
|
||||
export class BaseSingleLevelProfileTargeting<
|
||||
T extends ValidSingleLevelProfileNode
|
||||
> {
|
||||
// ...
|
||||
}
|
||||
|
||||
// Prettier main with --trailling-comma=all
|
||||
export class BaseSingleLevelProfileTargeting<
|
||||
T extends ValidSingleLevelProfileNode,
|
||||
> {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,21 @@
|
||||
#### Allow hugging arguments that are non-concise arrow functions with return type annotations (#10316 by @thorn0)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
users.map((user: User): User => {
|
||||
return user;
|
||||
})
|
||||
|
||||
// Prettier stable
|
||||
users.map(
|
||||
(user: User): User => {
|
||||
return user;
|
||||
}
|
||||
);
|
||||
|
||||
// Prettier main
|
||||
users.map((user: User): User => {
|
||||
return user;
|
||||
})
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
#### Correct parentheses for non-null assertions (#10337 by @thorn0)
|
||||
|
||||
Necessary parentheses sometimes weren't printed in expressions containing non-null assertions. This has been fixed.
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
const myFunction2 = (key: string): number =>
|
||||
({
|
||||
a: 42,
|
||||
b: 42,
|
||||
}[key]!)
|
||||
|
||||
// Prettier stable (invalid syntax)
|
||||
const myFunction2 = (key: string): number =>
|
||||
{
|
||||
a: 42,
|
||||
b: 42,
|
||||
}[key]!;
|
||||
|
||||
// Prettier main
|
||||
const myFunction2 = (key: string): number =>
|
||||
({
|
||||
a: 42,
|
||||
b: 42,
|
||||
}[key]!);
|
||||
```
|
||||
@@ -0,0 +1,31 @@
|
||||
#### Indent type assertions in heads of member and call expressions (#10341 by @thorn0)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
const getAccountCount = async () =>
|
||||
(await
|
||||
((await (
|
||||
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
|
||||
).findItem("My bookmarks")) as TreeItem
|
||||
).getChildren()
|
||||
).length
|
||||
|
||||
// Prettier stable
|
||||
const getAccountCount = async () =>
|
||||
(
|
||||
await ((await (
|
||||
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
|
||||
).findItem("My bookmarks")) as TreeItem).getChildren()
|
||||
).length;
|
||||
|
||||
// Prettier main
|
||||
const getAccountCount = async () =>
|
||||
(
|
||||
await (
|
||||
(await (
|
||||
await focusOnSection(BOOKMARKED_PROJECTS_SECTION_NAME)
|
||||
).findItem("My bookmarks")) as TreeItem
|
||||
).getChildren()
|
||||
).length;
|
||||
```
|
||||
@@ -0,0 +1,14 @@
|
||||
#### Support intrinsic keyword (#10390 by @sosukesuzuki)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
type Uppercase<S extends string> = intrinsic;
|
||||
|
||||
// Prettier stable
|
||||
Error: unknown type: "TSIntrinsicKeyword"
|
||||
|
||||
// Prettier main
|
||||
type Uppercase<S extends string> = intrinsic;
|
||||
|
||||
```
|
||||
@@ -0,0 +1,16 @@
|
||||
#### Support TypeScript 4.2 `abstract` Construct Signatures (#10418, #10466 by @sosukesuzuki)
|
||||
|
||||
Support [`abstract` Construct Signatures](https://devblogs.microsoft.com/typescript/announcing-typescript-4-2/#abstract-construct-signatures).
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
type T = abstract new () => void;
|
||||
|
||||
// Prettier main
|
||||
SyntaxError: Unexpected token, expected ";" (1:19)
|
||||
|
||||
// Prettier stable
|
||||
type T = abstract new () => void;
|
||||
|
||||
```
|
||||
@@ -0,0 +1,13 @@
|
||||
#### Fix misplaced comments in unions and intersections (#10457 by @thorn0)
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
```ts
|
||||
// Input
|
||||
type Foo = "1" | "2" /* two */ | "3";
|
||||
|
||||
// Prettier stable
|
||||
type Foo = "1" | "2" | /* two */ "3";
|
||||
|
||||
// Prettier main
|
||||
type Foo = "1" | "2" /* two */ | "3";
|
||||
```
|
||||
+124
-62
@@ -1,20 +1,30 @@
|
||||
The core of the algorithm is implemented in `doc-{printer,builders,utils,debug}.js`. The printer should use the basic formatting abstractions provided to construct a format when printing a node. Parts of the API only exist to be compatible with recast's previous API to ease migration, but over time we can clean it up.
|
||||
The core of the algorithm is implemented in `src/document/doc-{printer,builders,utils}.js`. The printer uses the basic formatting abstractions provided to construct a format when printing a node.
|
||||
|
||||
The following commands are available:
|
||||
## Prettier's intermediate representation: `Doc`
|
||||
|
||||
### group
|
||||
A doc can be a string, an array of docs, or a command.
|
||||
|
||||
```ts
|
||||
type GroupOpts = {
|
||||
type Doc = string | Doc[] | DocCommand;
|
||||
```
|
||||
|
||||
- _strings_ are printed directly as is (however for the algorithm to work properly they shouldn't contain line break characters)
|
||||
- _arrays_ are used to concatenate a list of docs to be printed sequentially into a single doc
|
||||
- `DocCommand` is any of the following:
|
||||
|
||||
### `group`
|
||||
|
||||
```ts
|
||||
type GroupOptions = {
|
||||
shouldBreak?: boolean;
|
||||
expandedStates?: Doc[];
|
||||
id?: symbol;
|
||||
};
|
||||
declare function group(doc: Doc, opts?: GroupOpts): Doc;
|
||||
declare function group(doc: Doc, options?: GroupOptions): Doc;
|
||||
```
|
||||
|
||||
Mark a group of items which the printer should try to fit on one line. This is the basic command to tell the printer when to break. Groups are usually nested, and the printer will try to fit everything on one line, but if it doesn't fit it will break the outermost group first and try again. It will continue breaking groups until everything fits (or there are no more groups to break).
|
||||
|
||||
A document can force parent groups to break by including `breakParent` (see below). A hard and literal line automatically include this so they always break parent groups. Breaks are propagated to all parent groups, so if a deeply nested expression has a hard break, everything will break. This only matters for "hard" breaks, i.e. newlines that are printed no matter what and can be statically analyzed.
|
||||
A group is forced to break if it's created with the `shouldBreak` option set to `true` or if it includes [`breakParent`](#breakParent). A [hard](#hardline) and [literal](#literalline) line breaks automatically include this so they always break parent groups. Breaks are propagated to all parent groups, so if a deeply nested expression has a hard break, everything will break. This only matters for "hard" breaks, i.e. newlines that are printed no matter what and can be statically analyzed.
|
||||
|
||||
For example, an array will try to fit on one line:
|
||||
|
||||
@@ -36,17 +46,16 @@ However, if any of the items inside the array have a hard break, the array will
|
||||
|
||||
Functions always break after the opening curly brace no matter what, so the array breaks as well for consistent formatting. See [the implementation of `ArrayExpression`](#example) for an example.
|
||||
|
||||
### conditionalGroup
|
||||
The `id` option can be used in [`ifBreak`](#ifBreak) checks.
|
||||
|
||||
### `conditionalGroup`
|
||||
|
||||
This should be used as **last resort** as it triggers an exponential complexity when nested.
|
||||
|
||||
```ts
|
||||
type ConditionalGroupOpts = {
|
||||
shouldBreak?: boolean;
|
||||
};
|
||||
declare function conditionalGroup(
|
||||
alternatives: Doc[],
|
||||
opts?: ConditionalGroupOpts
|
||||
options?: GroupOptions
|
||||
): Doc;
|
||||
```
|
||||
|
||||
@@ -56,89 +65,97 @@ This will try to print the first argument, if it fit use it, otherwise go to the
|
||||
conditionalGroup([a, b, c]);
|
||||
```
|
||||
|
||||
### fill
|
||||
### `fill`
|
||||
|
||||
```ts
|
||||
declare function fill(docs: Doc[]): Doc;
|
||||
```
|
||||
|
||||
This is an alternative type of group which behaves like text layout: it's going to add a break whenever the next element doesn't fit in the line anymore. The difference with a typical group is that it's not going to break all the separators, just the ones that are at the end of lines.
|
||||
This is an alternative type of group which behaves like text layout: it's going to add a break whenever the next element doesn't fit in the line anymore. The difference with [`group`](#group) is that it's not going to break all the separators, just the ones that are at the end of lines.
|
||||
|
||||
```js
|
||||
fill(["I", line, "love", line, "prettier"]);
|
||||
fill(["I", line, "love", line, "Prettier"]);
|
||||
```
|
||||
|
||||
### ifBreak
|
||||
Expects the `docs` argument to be an array of alternating content and line breaks. In other words, elements with odd indices must be line breaks (e.g., [`softline`](#softline)).
|
||||
|
||||
### `ifBreak`
|
||||
|
||||
```ts
|
||||
declare function ifBreak(ifBreak: Doc, noBreak: Doc): Doc;
|
||||
declare function ifBreak(
|
||||
ifBreak: Doc,
|
||||
noBreak?: Doc,
|
||||
options?: { groupId?: symbol }
|
||||
): Doc;
|
||||
```
|
||||
|
||||
Prints something if the current group breaks and something else if it doesn't.
|
||||
Print something if the current `group` or the current element of `fill` breaks and something else if it doesn't.
|
||||
|
||||
```js
|
||||
ifBreak(";", " ");
|
||||
```
|
||||
|
||||
### breakParent
|
||||
`groupId` can be used to check another _already printed_ group instead of the current group.
|
||||
|
||||
### `breakParent`
|
||||
|
||||
```ts
|
||||
declare var breakParent: Doc;
|
||||
declare const breakParent: Doc;
|
||||
```
|
||||
|
||||
Include this anywhere to force all parent groups to break. See `group` for more info. Example:
|
||||
Include this anywhere to force all parent groups to break. See [`group`](#group) for more info. Example:
|
||||
|
||||
```js
|
||||
group([" ", expr, " ", breakParent]);
|
||||
```
|
||||
|
||||
### join
|
||||
### `join`
|
||||
|
||||
```ts
|
||||
declare function join(sep: Doc, docs: Doc[]): Doc;
|
||||
```
|
||||
|
||||
Join an array of items with a separator.
|
||||
Join an array of docs with a separator.
|
||||
|
||||
### line
|
||||
### `line`
|
||||
|
||||
```ts
|
||||
declare var line: Doc;
|
||||
declare const line: Doc;
|
||||
```
|
||||
|
||||
Specify a line break. If an expression fits on one line, the line break will be replaced with a space. Line breaks always indent the next line with the current level of indentation.
|
||||
|
||||
### softline
|
||||
### `softline`
|
||||
|
||||
```ts
|
||||
declare var softline: Doc;
|
||||
declare const softline: Doc;
|
||||
```
|
||||
|
||||
Specify a line break. The difference from `line` is that if the expression fits on one line, it will be replaced with nothing.
|
||||
|
||||
### hardline
|
||||
### `hardline`
|
||||
|
||||
```ts
|
||||
declare var hardline: Doc;
|
||||
declare const hardline: Doc;
|
||||
```
|
||||
|
||||
Specify a line break that is **always** included in the output, no matter if the expression fits on one line or not.
|
||||
|
||||
### literalline
|
||||
### `literalline`
|
||||
|
||||
```ts
|
||||
declare var literalline: Doc;
|
||||
declare const literalline: Doc;
|
||||
```
|
||||
|
||||
Specify a line break that is **always** included in the output, and don't indent the next line. This is used for template literals.
|
||||
Specify a line break that is **always** included in the output and doesn't indent the next line. Also, unlike `hardline`, this kind of line break preserves trailing whitespace on the line it ends. This is used for template literals.
|
||||
|
||||
### lineSuffix
|
||||
### `lineSuffix`
|
||||
|
||||
```ts
|
||||
declare function lineSuffix(suffix: Doc): Doc;
|
||||
```
|
||||
|
||||
This is used to implement trailing comments. In practice, it is not practical to find where the line ends and you don't want to accidentally print some code at the end of the comment. `lineSuffix` will buffer the output and flush it before any new line.
|
||||
This is used to implement trailing comments. It's not practical to constantly check where the line ends to avoid accidentally printing some code at the end of a comment. `lineSuffix` buffers docs passed to it and flushes them before any new line.
|
||||
|
||||
```js
|
||||
["a", lineSuffix(" // comment"), ";", hardline];
|
||||
@@ -150,13 +167,13 @@ will output
|
||||
a; // comment
|
||||
```
|
||||
|
||||
### lineSuffixBoundary
|
||||
### `lineSuffixBoundary`
|
||||
|
||||
```ts
|
||||
declare var lineSuffixBoundary: Doc;
|
||||
declare const lineSuffixBoundary: Doc;
|
||||
```
|
||||
|
||||
In cases where you embed code inside of templates, comments shouldn't be able to leave the code part. lineSuffixBoundary is an explicit marker you can use to flush code in addition to newlines.
|
||||
In cases where you embed code inside of templates, comments shouldn't be able to leave the code part. `lineSuffixBoundary` is an explicit marker you can use to flush the [`lineSuffix`](#lineSuffix) buffer in addition to line breaks.
|
||||
|
||||
```js
|
||||
["{", lineSuffix(" // comment"), lineSuffixBoundary, "}", hardline];
|
||||
@@ -177,7 +194,7 @@ and **not**
|
||||
{} // comment
|
||||
```
|
||||
|
||||
### indent
|
||||
### `indent`
|
||||
|
||||
```ts
|
||||
declare function indent(doc: Doc): Doc;
|
||||
@@ -185,7 +202,7 @@ declare function indent(doc: Doc): Doc;
|
||||
|
||||
Increase the level of indentation.
|
||||
|
||||
### dedent
|
||||
### `dedent`
|
||||
|
||||
```ts
|
||||
declare function dedent(doc: Doc): Doc;
|
||||
@@ -193,15 +210,15 @@ declare function dedent(doc: Doc): Doc;
|
||||
|
||||
Decrease the level of indentation. (Each `align` is considered one level of indentation.)
|
||||
|
||||
### align
|
||||
### `align`
|
||||
|
||||
```ts
|
||||
declare function align(n: number | string, doc: Doc): Doc;
|
||||
```
|
||||
|
||||
This is similar to indent but it increases the level of indentation by a fixed number or a string.
|
||||
Trailing alignments in indentation are still spaces, but middle ones are transformed into one tab per `align` when `useTabs` enabled.
|
||||
If it's using in a whitespace-sensitive language, e.g. markdown, you should use `n` with string value to force print it.
|
||||
Increase the indentation by a fixed number of spaces or a string. A variant of [`indent`](#indent).
|
||||
|
||||
When `useTabs` is enabled, trailing alignments in indentation are still spaces, but middle ones are transformed one tab per `align`. In a whitespace-sensitive context (e.g., Markdown), you should pass spaces to `align` as strings to prevent their replacement with tabs.
|
||||
|
||||
For example:
|
||||
|
||||
@@ -213,38 +230,93 @@ For example:
|
||||
- `<indent><align 2><indent><align 2>` -> `<tab><tab><tab><2 space>`
|
||||
- `<indent><align 4><indent><align 2>` -> `<tab><tab><tab><2 space>`
|
||||
|
||||
### markAsRoot
|
||||
### `markAsRoot`
|
||||
|
||||
```ts
|
||||
declare function markAsRoot(doc: Doc): Doc;
|
||||
```
|
||||
|
||||
This marks the current indentation as root for `dedentToRoot` and `literalline`s.
|
||||
Mark the current indentation as root for [`dedentToRoot`](#dedentToRoot) and [`literalline`](#literalline)s.
|
||||
|
||||
### dedentToRoot
|
||||
### `dedentToRoot`
|
||||
|
||||
```ts
|
||||
declare function dedentToRoot(doc: Doc): Doc;
|
||||
```
|
||||
|
||||
This will dedent the current indentation to the root marked by `markAsRoot`.
|
||||
Decrease the current indentation to the root marked by [`markAsRoot`](#markAsRoot).
|
||||
|
||||
### trim
|
||||
### `trim`
|
||||
|
||||
```ts
|
||||
declare var trim: Doc;
|
||||
declare const trim: Doc;
|
||||
```
|
||||
|
||||
This will trim any whitespace or tab character on the current line. This is used for preprocessor directives.
|
||||
Trim all the indentation on the current line. This can be used for preprocessor directives. Should be placed after a line break.
|
||||
|
||||
### cursor
|
||||
### `indentIfBreak`
|
||||
|
||||
_Added in v2.3.0_
|
||||
|
||||
```ts
|
||||
declare var cursor: Doc;
|
||||
declare function indentIfBreak(
|
||||
doc: Doc,
|
||||
opts: { groupId: symbol; negate?: boolean }
|
||||
): Doc;
|
||||
```
|
||||
|
||||
An optimized version of `ifBreak(indent(doc), doc, { groupId })`.
|
||||
|
||||
With `negate: true`, corresponds to `ifBreak(doc, indent(doc), { groupId })`
|
||||
|
||||
It doesn't make sense to apply `indentIfBreak` to the current group because "indent if the current group is broken" is the normal behavior of `indent`. That's why `groupId` is required.
|
||||
|
||||
### `label`
|
||||
|
||||
_Added in v2.3.0_
|
||||
|
||||
```ts
|
||||
declare function label(label: string, doc: Doc): Doc;
|
||||
```
|
||||
|
||||
Mark a doc with a string label. This doesn't affect how the doc is printed, but can be useful for heuristics based on doc introspection.
|
||||
|
||||
E.g., to decide how to print an assignment expression, we might want to know whether its right-hand side has been printed as a method call chain, not as a plain function call. If the method chain printing code uses `label` to mark its result, checking that condition can be as easy as `rightHandSideDoc.label === 'method-chain'`.
|
||||
|
||||
### `hardlineWithoutBreakParent` and `literallineWithoutBreakParent`
|
||||
|
||||
_Added in v2.3.0_
|
||||
|
||||
```ts
|
||||
declare const hardlineWithoutBreakParent: Doc;
|
||||
declare const literallineWithoutBreakParent: Doc;
|
||||
```
|
||||
|
||||
These are used very rarely, for advanced formatting tricks. Unlike their "normal" counterparts, they don't include an implicit (`breakParent`)[#breakParent].
|
||||
|
||||
Examples:
|
||||
|
||||
- `hardlineWithoutBreakParent` is used for printing tables in Prettier's Markdown printer. With `proseWrap` set to `never`, the columns are aligned only if none of the rows exceeds `printWidth`.
|
||||
- `literallineWithoutBreakParent` is used in the [Ruby plugin](https://github.com/prettier/plugin-ruby) for [printing heredoc syntax](https://github.com/prettier/plugin-ruby/blob/b6e7bd6bc3f70de8f146aa58ad0c8310518bf467/src/ruby/nodes/heredocs.js).
|
||||
|
||||
### `cursor`
|
||||
|
||||
```ts
|
||||
declare const cursor: Doc;
|
||||
```
|
||||
|
||||
This is a placeholder value where the cursor is in the original input in order to find where it would be printed.
|
||||
|
||||
### [Deprecated] `concat`
|
||||
|
||||
_This command has been deprecated in v2.3.0, use `Doc[]` instead_
|
||||
|
||||
```ts
|
||||
declare function concat(docs: Doc[]): Doc;
|
||||
```
|
||||
|
||||
Combine an array into a single doc.
|
||||
|
||||
## Example
|
||||
|
||||
For an example, here's the implementation of the `ArrayExpression` node type:
|
||||
@@ -270,13 +342,3 @@ group(
|
||||
```
|
||||
|
||||
This is a group with opening and closing brackets, and possibly indented contents. Because it's a `group` it will always be broken up if any of the sub-expressions are broken.
|
||||
|
||||
### [Deprecated] concat
|
||||
|
||||
> This command is deprecated, use `Doc[]` instead.
|
||||
|
||||
```ts
|
||||
declare function concat(docs: Doc[]): Doc;
|
||||
```
|
||||
|
||||
Combine an array into a single string.
|
||||
@@ -106,6 +106,7 @@
|
||||
"elektronik",
|
||||
"Eneman",
|
||||
"ENOENT",
|
||||
"eqeqeq",
|
||||
"Ericsburgh",
|
||||
"errored",
|
||||
"Esben",
|
||||
@@ -272,6 +273,7 @@
|
||||
"overscroll",
|
||||
"packagejson",
|
||||
"Panasenko",
|
||||
"Pandoc",
|
||||
"Pangsakulyanont",
|
||||
"paren",
|
||||
"parens",
|
||||
@@ -369,6 +371,7 @@
|
||||
"Tomasek",
|
||||
"Tradeshift",
|
||||
"Transloadit",
|
||||
"trippable",
|
||||
"TSAs",
|
||||
"tsep",
|
||||
"TSESTree",
|
||||
@@ -409,6 +412,7 @@
|
||||
"Vue's",
|
||||
"Wadler",
|
||||
"Wadler's",
|
||||
"wcwidth",
|
||||
"webcompat",
|
||||
"webstorm",
|
||||
"Weixin",
|
||||
|
||||
@@ -212,3 +212,7 @@ With `--ignore-unknown` (or `-u`), prettier will ignore unknown files matched by
|
||||
```console
|
||||
$ prettier "**/*" --write --ignore-unknown
|
||||
```
|
||||
|
||||
## `--no-error-on-unmatched-pattern`
|
||||
|
||||
Prevent errors when pattern is unmatched.
|
||||
@@ -159,3 +159,13 @@ hello: world
|
||||
{{/my-crazy-component}}
|
||||
</div>
|
||||
```
|
||||
|
||||
## Command Line File Patterns
|
||||
|
||||
For one-off commands, when you want to exclude some files without adding them to `.prettierignore`, negative patterns can come in handy:
|
||||
|
||||
```bash
|
||||
prettier --write . '!**/*.{js,jsx,vue}'
|
||||
```
|
||||
|
||||
See [fast-glob](https://prettier.io/docs/en/cli.html#file-patterns) to learn more about advanced glob syntax.
|
||||
+13
-4
@@ -89,6 +89,15 @@ Valid options:
|
||||
| ------------- | -------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| `"as-needed"` | <code>--quote-props <as-needed|consistent|preserve></code> | <code>quoteProps: "<as-needed|consistent|preserve>"</code> |
|
||||
|
||||
Note that Prettier never unquotes numeric property names in Angular expressions, TypeScript, and Flow because the distinction between string and numeric keys is significant in these languages. See: [Angular][quote-props-angular], [TypeScript][quote-props-typescript], [Flow][quote-props-flow]. Also Prettier doesn’t unquote numeric properties for Vue (see the [issue][quote-props-vue] about that).
|
||||
|
||||
[quote-props-angular]: https://codesandbox.io/s/hungry-morse-foj87?file=/src/app/app.component.html
|
||||
[quote-props-typescript]: https://www.typescriptlang.org/play?#code/DYUwLgBAhhC8EG8IEYBcKA0EBM7sQF8AoUSAIzkQgHJlr1ktrt6dCiiATEAY2CgBOICKWhR0AaxABPAPYAzCGGkAHEAugBuLr35CR4CGTKSZG5Wo1ltRKDHjHtQA
|
||||
[quote-props-flow]: https://flow.org/try/#0PQKgBAAgZgNg9gdzCYAoVBjOA7AzgFzAA8wBeMAb1TDAAYAuMARlQF8g
|
||||
[quote-props-vue]: https://github.com/prettier/prettier/issues/10127
|
||||
|
||||
If this option is set to `preserve`, `singleQuote` to `false` (default value), and `parser` to `json5`, double quotes are always used for strings. This effectively allows using the `json5` parser for “JSON with comments and trailing commas”.
|
||||
|
||||
## JSX Quotes
|
||||
|
||||
Use single quotes instead of double quotes in JSX.
|
||||
@@ -101,13 +110,13 @@ Use single quotes instead of double quotes in JSX.
|
||||
|
||||
_Default value changed from `none` to `es5` in v2.0.0_
|
||||
|
||||
Print trailing commas wherever possible when multi-line. (A single-line array, for example, never gets trailing commas.)
|
||||
Print trailing commas wherever possible in multi-line comma-separated syntactic structures. (A single-line array, for example, never gets trailing commas.)
|
||||
|
||||
Valid options:
|
||||
|
||||
- `"es5"` - Trailing commas where valid in ES5 (objects, arrays, etc.)
|
||||
- `"es5"` - Trailing commas where valid in ES5 (objects, arrays, etc.). No trailing commas in type parameters in TypeScript.
|
||||
- `"none"` - No trailing commas.
|
||||
- `"all"` - Trailing commas wherever possible (including [trailing commas in function parameter lists and calls](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas#Trailing_commas_in_functions)). This requires node 8 or a modern browser that supports ES2017 or transform with [babel](https://babeljs.io/docs/en/index).
|
||||
- `"all"` - Trailing commas wherever possible (including [function parameters and calls](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Trailing_commas#Trailing_commas_in_functions)). To run, JavaScript code formatted this way needs an engine that supports ES2017 (Node.js 8+ or a modern browser) or [downlevel compilation](https://babeljs.io/docs/en/index). This also enables trailing commas in type parameters in TypeScript (supported since TypeScript 2.7 released in January 2018).
|
||||
|
||||
| Default | CLI Override | API Override |
|
||||
| ------- | ------------------------------------------------------ | ------------------------------------------------------ |
|
||||
@@ -316,7 +325,7 @@ Specify the global whitespace sensitivity for HTML, Vue, Angular, and Handlebars
|
||||
|
||||
Valid options:
|
||||
|
||||
- `"css"` - Respect the default value of CSS `display` property. For Handlebars treated same as `ignore`.
|
||||
- `"css"` - Respect the default value of CSS `display` property. For Handlebars treated same as `strict`.
|
||||
- `"strict"` - Whitespace (or the lack of it) around all tags is considered significant.
|
||||
- `"ignore"` - Whitespace (or the lack of it) around all tags is considered insignificant.
|
||||
|
||||
|
||||
+8
-17
@@ -49,25 +49,14 @@ Providing at least one path to `--plugin-search-dir`/`pluginSearchDirs` turns of
|
||||
- [`prettier-plugin-elm`](https://github.com/gicentre/prettier-plugin-elm) by [**@giCentre**](https://github.com/gicentre)
|
||||
- [`prettier-plugin-go-template`](https://github.com/NiklasPor/prettier-plugin-go-template) by [**@NiklasPor**](https://github.com/NiklasPor)
|
||||
- [`prettier-plugin-java`](https://github.com/jhipster/prettier-java) by [**@JHipster**](https://github.com/jhipster)
|
||||
- [`prettier-plugin-jsdoc`](https://github.com/hosseinmd/prettier-plugin-jsdoc) by [**@hosseinmd**](https://github.com/hosseinmd)
|
||||
- [`prettier-plugin-kotlin`](https://github.com/Angry-Potato/prettier-plugin-kotlin) by [**@Angry-Potato**](https://github.com/Angry-Potato)
|
||||
- [`prettier-plugin-package`](https://github.com/shellscape/prettier-plugin-package) by [**@shellscape**](https://github.com/shellscape)
|
||||
- [`prettier-plugin-packagejson`](https://github.com/matzkoh/prettier-plugin-packagejson) by [**@matzkoh**](https://github.com/matzkoh)
|
||||
- [`prettier-plugin-pg`](https://github.com/benjie/prettier-plugin-pg) by [**@benjie**](https://github.com/benjie)
|
||||
- [`prettier-plugin-properties`](https://github.com/eemeli/prettier-plugin-properties) by [**@eemeli**](https://github.com/eemeli)
|
||||
- [`prettier-plugin-solidity`](https://github.com/prettier-solidity/prettier-plugin-solidity) by [**@mattiaerre**](https://github.com/mattiaerre)
|
||||
- [`prettier-plugin-svelte`](https://github.com/UnwrittenFun/prettier-plugin-svelte) by [**@UnwrittenFun**](https://github.com/UnwrittenFun)
|
||||
- [`prettier-plugin-tailwind`](https://github.com/Acidic9/prettier-plugin-tailwind) by [**@Acidic9**](https://github.com/Acidic9)
|
||||
- [`prettier-plugin-toml`](https://github.com/bd82/toml-tools/tree/master/packages/prettier-plugin-toml) by [**@bd82**](https://github.com/bd82)
|
||||
- [`prettier-plugin-pkg`](https://github.com/rx-ts/prettier/tree/master/packages/pkg) by [**@JounQin**](https://github.com/JounQin)
|
||||
- [`prettier-plugin-sh`](https://github.com/rx-ts/prettier/tree/master/packages/sh) by [**@JounQin**](https://github.com/JounQin)
|
||||
|
||||
## Experimental Plugins
|
||||
|
||||
These plugins provide functionality not supported with Plugin API. Use with caution.
|
||||
|
||||
- [`prettier-plugin-organize-imports`](https://github.com/simonhaenisch/prettier-plugin-organize-imports) by [**@simonhaenisch**](https://github.com/simonhaenisch)
|
||||
|
||||
## Developing Plugins
|
||||
|
||||
Prettier plugins are regular JavaScript modules with five exports:
|
||||
@@ -177,7 +166,7 @@ The printing process works as follows:
|
||||
|
||||
1. `preprocess(ast: AST, options: object): AST`, if available, is called. It is passed the AST from the _parser_. The AST returned by `preprocess` will be used by Prettier. If `preprocess` is not defined, the AST returned from the _parser_ will be used.
|
||||
2. Comments are attached to the AST (see _Handling comments in a printer_ for details).
|
||||
3. A Doc is recursively constructed from the AST. i) `embed(path: FastPath, print, textToDoc, options: object): Doc | null` is called on each AST node. If `embed` returns a Doc, that Doc is used. ii) If `embed` is undefined or returns a falsy value, `print(path: FastPath, options: object, print): Doc` is called on each AST node.
|
||||
3. A Doc is recursively constructed from the AST. i) `embed(path: AstPath, print, textToDoc, options: object): Doc | null` is called on each AST node. If `embed` returns a Doc, that Doc is used. ii) If `embed` is undefined or returns a falsy value, `print(path: AstPath, options: object, print): Doc` is called on each AST node.
|
||||
|
||||
#### `print`
|
||||
|
||||
@@ -186,10 +175,10 @@ Most of the work of a plugin's printer will take place in its `print` function,
|
||||
```ts
|
||||
function print(
|
||||
// Path to the AST node to print
|
||||
path: FastPath,
|
||||
path: AstPath,
|
||||
options: object,
|
||||
// Recursively print a child node
|
||||
print: (path: FastPath) => Doc
|
||||
print: (path: AstPath) => Doc
|
||||
): Doc;
|
||||
```
|
||||
|
||||
@@ -217,9 +206,9 @@ The `embed` function is called when the plugin needs to print one language insid
|
||||
```ts
|
||||
function embed(
|
||||
// Path to the current AST node
|
||||
path: FastPath,
|
||||
path: AstPath,
|
||||
// Print a node with the current printer
|
||||
print: (path: FastPath) => Doc,
|
||||
print: (path: AstPath) => Doc,
|
||||
// Parse and print some text using a different parser.
|
||||
// You should set `options.parser` to specify which parser to use.
|
||||
textToDoc: (text: string, options: object) => Doc,
|
||||
@@ -271,7 +260,7 @@ Called whenever a comment node needs to be printed. It has the signature:
|
||||
```ts
|
||||
function printComment(
|
||||
// Path to the current comment node
|
||||
commentPath: FastPath,
|
||||
commentPath: AstPath,
|
||||
// Current options
|
||||
options: object
|
||||
): Doc;
|
||||
@@ -351,6 +340,8 @@ Nodes with comments are expected to have a `comments` property containing an arr
|
||||
|
||||
The example above uses `util.addTrailingComment`, which automatically sets `comment.leading`/`trailing`/`printed` to appropriate values and adds the comment to the AST node's `comments` array.
|
||||
|
||||
The `--debug-print-comments` CLI flag can help with debugging comment attachment issues. It prints a detailed list of comments, which includes information on how every comment was classified (`ownLine`/`endOfLine`/`remaining`, `leading`/`trailing`/`dangling`) and to which node it was attached. For Prettier’s built-in languages, this information is also available on the Playground (the 'show comments' checkbox in the Debug section).
|
||||
|
||||
### `options`
|
||||
|
||||
`options` is an object containing the custom options your plugin supports.
|
||||
|
||||
@@ -28,6 +28,7 @@ title: Related Projects
|
||||
|
||||
## Misc
|
||||
|
||||
- [parallel-prettier](https://github.com/microsoft/parallel-prettier) is an alternative CLI that formats files in parallel to speed up large projects
|
||||
- [prettier_d](https://github.com/josephfrazier/prettier_d.js) runs Prettier as a server to avoid Node.js startup delay
|
||||
- [pretty-quick](https://github.com/azz/pretty-quick) formats changed files with Prettier
|
||||
- [rollup-plugin-prettier](https://github.com/mjeanroy/rollup-plugin-prettier) allows you to use Prettier with Rollup
|
||||
|
||||
+13
-5
@@ -1,16 +1,24 @@
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const installPrettier = require("./scripts/install-prettier");
|
||||
|
||||
const PROJECT_ROOT = __dirname;
|
||||
const isProduction = process.env.NODE_ENV === "production";
|
||||
const ENABLE_CODE_COVERAGE = !!process.env.ENABLE_CODE_COVERAGE;
|
||||
if (isProduction || process.env.INSTALL_PACKAGE) {
|
||||
process.env.PRETTIER_DIR = installPrettier();
|
||||
const ENABLE_CODE_COVERAGE = Boolean(process.env.ENABLE_CODE_COVERAGE);
|
||||
const TEST_STANDALONE = Boolean(process.env.TEST_STANDALONE);
|
||||
const INSTALL_PACKAGE = Boolean(process.env.INSTALL_PACKAGE);
|
||||
|
||||
let PRETTIER_DIR = isProduction
|
||||
? path.join(PROJECT_ROOT, "dist")
|
||||
: PROJECT_ROOT;
|
||||
if (INSTALL_PACKAGE || (isProduction && !TEST_STANDALONE)) {
|
||||
PRETTIER_DIR = installPrettier(PRETTIER_DIR);
|
||||
}
|
||||
const { TEST_STANDALONE } = process.env;
|
||||
process.env.PRETTIER_DIR = PRETTIER_DIR;
|
||||
|
||||
const testPathIgnorePatterns = [];
|
||||
let transform;
|
||||
let transform = {};
|
||||
if (TEST_STANDALONE) {
|
||||
testPathIgnorePatterns.push("<rootDir>/tests_integration/");
|
||||
}
|
||||
|
||||
+36
-36
@@ -20,17 +20,17 @@
|
||||
"bin"
|
||||
],
|
||||
"dependencies": {
|
||||
"@angular/compiler": "11.0.9",
|
||||
"@babel/code-frame": "7.12.11",
|
||||
"@babel/parser": "7.12.11",
|
||||
"@glimmer/syntax": "0.73.1",
|
||||
"@angular/compiler": "11.2.4",
|
||||
"@babel/code-frame": "7.12.13",
|
||||
"@babel/parser": "7.13.9",
|
||||
"@glimmer/syntax": "0.77.1",
|
||||
"@iarna/toml": "2.2.5",
|
||||
"@typescript-eslint/typescript-estree": "4.14.0",
|
||||
"@typescript-eslint/typescript-estree": "4.16.1",
|
||||
"angular-estree-parser": "2.3.0",
|
||||
"angular-html-parser": "1.7.1",
|
||||
"camelcase": "6.2.0",
|
||||
"chalk": "4.1.0",
|
||||
"ci-info": "watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
|
||||
"ci-info": "3.1.1",
|
||||
"cjk-regex": "2.0.1",
|
||||
"cosmiconfig": "7.0.0",
|
||||
"dashify": "2.0.0",
|
||||
@@ -40,26 +40,26 @@
|
||||
"escape-string-regexp": "4.0.0",
|
||||
"espree": "7.3.1",
|
||||
"esutils": "2.0.3",
|
||||
"fast-glob": "3.2.4",
|
||||
"fast-glob": "3.2.5",
|
||||
"fast-json-stable-stringify": "2.1.0",
|
||||
"find-parent-dir": "0.3.0",
|
||||
"flow-parser": "0.143.1",
|
||||
"flow-parser": "0.145.0",
|
||||
"get-stdin": "8.0.0",
|
||||
"globby": "11.0.2",
|
||||
"graphql": "15.4.0",
|
||||
"graphql": "15.5.0",
|
||||
"html-element-attributes": "2.3.0",
|
||||
"html-styles": "1.0.0",
|
||||
"html-tag-names": "1.1.5",
|
||||
"html-void-elements": "1.0.5",
|
||||
"ignore": "4.0.6",
|
||||
"jest-docblock": "26.0.0",
|
||||
"json5": "2.1.3",
|
||||
"json5": "2.2.0",
|
||||
"leven": "3.1.0",
|
||||
"lines-and-columns": "1.1.6",
|
||||
"linguist-languages": "7.12.2",
|
||||
"lodash": "4.17.20",
|
||||
"linguist-languages": "7.13.0",
|
||||
"lodash": "4.17.21",
|
||||
"mem": "8.0.0",
|
||||
"meriyah": "4.0.0",
|
||||
"meriyah": "4.1.5",
|
||||
"minimatch": "3.0.4",
|
||||
"minimist": "1.2.5",
|
||||
"n-readlines": "1.0.1",
|
||||
@@ -76,43 +76,44 @@
|
||||
"remark-footnotes": "2.0.0",
|
||||
"remark-math": "3.0.1",
|
||||
"remark-parse": "8.0.3",
|
||||
"resolve": "1.19.0",
|
||||
"resolve": "1.20.0",
|
||||
"semver": "7.3.4",
|
||||
"string-width": "4.2.0",
|
||||
"typescript": "4.1.3",
|
||||
"string-width": "4.2.2",
|
||||
"strip-ansi": "6.0.0",
|
||||
"typescript": "4.2.3",
|
||||
"unicode-regex": "3.0.0",
|
||||
"unified": "9.2.0",
|
||||
"unified": "9.2.1",
|
||||
"vnopts": "1.0.2",
|
||||
"wcwidth": "1.0.1",
|
||||
"yaml-unist-parser": "1.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.12.10",
|
||||
"@babel/preset-env": "7.12.11",
|
||||
"@babel/types": "7.12.12",
|
||||
"@glimmer/reference": "0.73.2",
|
||||
"@rollup/plugin-alias": "3.1.1",
|
||||
"@rollup/plugin-babel": "5.2.2",
|
||||
"@babel/core": "7.13.8",
|
||||
"@babel/preset-env": "7.13.9",
|
||||
"@babel/types": "7.13.0",
|
||||
"@glimmer/reference": "0.77.1",
|
||||
"@rollup/plugin-alias": "3.1.2",
|
||||
"@rollup/plugin-babel": "5.3.0",
|
||||
"@rollup/plugin-commonjs": "16.0.0",
|
||||
"@rollup/plugin-json": "4.1.0",
|
||||
"@rollup/plugin-node-resolve": "11.1.0",
|
||||
"@rollup/plugin-replace": "2.3.4",
|
||||
"@rollup/plugin-node-resolve": "11.2.0",
|
||||
"@rollup/plugin-replace": "2.4.1",
|
||||
"@types/estree": "0.0.46",
|
||||
"@types/node": "14.14.0",
|
||||
"@typescript-eslint/types": "4.14.0",
|
||||
"babel-jest": "26.6.3",
|
||||
"babel-loader": "8.2.2",
|
||||
"benchmark": "2.1.4",
|
||||
"builtin-modules": "3.2.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cspell": "4.2.5",
|
||||
"eslint": "7.18.0",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"cspell": "4.2.8",
|
||||
"eslint": "7.21.0",
|
||||
"eslint-config-prettier": "8.1.0",
|
||||
"eslint-formatter-friendly": "7.0.0",
|
||||
"eslint-plugin-import": "2.22.1",
|
||||
"eslint-plugin-jest": "24.1.3",
|
||||
"eslint-plugin-jest": "24.1.8",
|
||||
"eslint-plugin-prettier-internal-rules": "link:scripts/tools/eslint-plugin-prettier-internal-rules",
|
||||
"eslint-plugin-react": "7.22.0",
|
||||
"eslint-plugin-unicorn": "27.0.0",
|
||||
"eslint-plugin-unicorn": "28.0.2",
|
||||
"execa": "5.0.0",
|
||||
"jest": "26.6.3",
|
||||
"jest-snapshot-serializer-ansi": "1.0.0",
|
||||
@@ -121,14 +122,13 @@
|
||||
"npm-run-all": "4.1.5",
|
||||
"path-browserify": "1.0.1",
|
||||
"prettier": "2.2.1",
|
||||
"pretty-bytes": "5.5.0",
|
||||
"pretty-bytes": "5.6.0",
|
||||
"rimraf": "3.0.2",
|
||||
"rollup": "2.37.1",
|
||||
"rollup-plugin-node-globals": "1.4.0",
|
||||
"rollup": "2.40.0",
|
||||
"rollup-plugin-polyfill-node": "0.6.1",
|
||||
"rollup-plugin-terser": "7.0.2",
|
||||
"shelljs": "0.8.4",
|
||||
"snapshot-diff": "0.8.1",
|
||||
"strip-ansi": "6.0.0",
|
||||
"synchronous-promise": "2.0.15",
|
||||
"tempy": "1.0.0"
|
||||
},
|
||||
@@ -148,7 +148,7 @@
|
||||
"lint:eslint": "cross-env EFF_NO_LINK_RULES=true eslint . --format friendly",
|
||||
"lint:changelog": "node ./scripts/lint-changelog.js",
|
||||
"lint:prettier": "prettier . \"!test*\" --check",
|
||||
"lint:dist": "eslint --no-eslintrc --no-ignore --env=es6,browser --parser-options=ecmaVersion:2018 \"dist/!(bin-prettier|index|third-party).js\"",
|
||||
"lint:dist": "eslint --no-eslintrc --no-ignore --no-inline-config --env=es6,browser --parser-options=ecmaVersion:2018 \"dist/!(bin-prettier|index|third-party).js\"",
|
||||
"lint:spellcheck": "cspell \"**/*\" \".github/**/*\"",
|
||||
"lint:deps": "node ./scripts/check-deps.js",
|
||||
"fix": "run-s fix:eslint fix:prettier",
|
||||
|
||||
@@ -97,9 +97,7 @@ async function createBundle(bundleConfig, cache, options) {
|
||||
const esmFile = path.join("dist/esm", output.replace(".js", ".mjs"));
|
||||
sizeTexts.push(`esm ${await getSizeText(esmFile)}`);
|
||||
}
|
||||
process.stdout.write(
|
||||
fitTerminal(output, sizeTexts.join(", ").concat(" "))
|
||||
);
|
||||
process.stdout.write(fitTerminal(output, `${sizeTexts.join(", ")} `));
|
||||
}
|
||||
|
||||
console.log(status.DONE);
|
||||
@@ -129,7 +127,7 @@ async function cacheFiles(cache) {
|
||||
path.join(".cache", file.replace("dist", "files")),
|
||||
]);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Don't fail the build
|
||||
}
|
||||
}
|
||||
|
||||
+11
-10
@@ -5,12 +5,11 @@ const { rollup } = require("rollup");
|
||||
const { nodeResolve } = require("@rollup/plugin-node-resolve");
|
||||
const rollupPluginAlias = require("@rollup/plugin-alias");
|
||||
const commonjs = require("@rollup/plugin-commonjs");
|
||||
const nodeGlobals = require("rollup-plugin-node-globals");
|
||||
const rollupPluginPolyfillNode = require("rollup-plugin-polyfill-node");
|
||||
const json = require("@rollup/plugin-json");
|
||||
const replace = require("@rollup/plugin-replace");
|
||||
const { terser } = require("rollup-plugin-terser");
|
||||
const { babel } = require("@rollup/plugin-babel");
|
||||
const nativeShims = require("./rollup-plugins/native-shims");
|
||||
const executable = require("./rollup-plugins/executable");
|
||||
const evaluate = require("./rollup-plugins/evaluate");
|
||||
const externals = require("./rollup-plugins/externals");
|
||||
@@ -118,10 +117,13 @@ function getBabelConfig(bundle) {
|
||||
},
|
||||
],
|
||||
];
|
||||
config.plugins.push([
|
||||
require.resolve("@babel/plugin-proposal-object-rest-spread"),
|
||||
{ loose: true, useBuiltIns: true },
|
||||
]);
|
||||
config.plugins.push(
|
||||
[
|
||||
require.resolve("@babel/plugin-proposal-object-rest-spread"),
|
||||
{ loose: true, useBuiltIns: true },
|
||||
],
|
||||
require.resolve("@babel/plugin-proposal-optional-catch-binding")
|
||||
);
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -137,7 +139,8 @@ function getRollupConfig(bundle) {
|
||||
// ignore `MIXED_EXPORTS` warn
|
||||
warning.code === "MIXED_EXPORTS" ||
|
||||
(warning.code === "CIRCULAR_DEPENDENCY" &&
|
||||
warning.importer.startsWith("node_modules"))
|
||||
(warning.importer.startsWith("node_modules") ||
|
||||
warning.importer.startsWith("polyfill-node:")))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -190,8 +193,6 @@ function getRollupConfig(bundle) {
|
||||
evaluate(),
|
||||
json(),
|
||||
rollupPluginAlias(alias),
|
||||
bundle.target === "universal" &&
|
||||
nativeShims(path.resolve(__dirname, "shims")),
|
||||
nodeResolve({
|
||||
extensions: [".js", ".json"],
|
||||
preferBuiltins: bundle.target === "node",
|
||||
@@ -206,7 +207,7 @@ function getRollupConfig(bundle) {
|
||||
requireReturnsDefault: "preferred",
|
||||
}),
|
||||
externals(bundle.externals),
|
||||
bundle.target === "universal" && nodeGlobals(),
|
||||
bundle.target === "universal" && rollupPluginPolyfillNode(),
|
||||
babel(babelConfig),
|
||||
bundle.minify !== false &&
|
||||
bundle.target === "universal" &&
|
||||
|
||||
@@ -48,7 +48,7 @@ class Cache {
|
||||
for (const files of Object.values(this.files)) {
|
||||
assert.ok(Array.isArray(files));
|
||||
}
|
||||
} catch (err) {
|
||||
} catch {
|
||||
this.checksums = {};
|
||||
this.files = {};
|
||||
}
|
||||
@@ -97,7 +97,7 @@ class Cache {
|
||||
async save() {
|
||||
try {
|
||||
await fs.writeFile(this.manifest, JSON.stringify(this.updated, null, 2));
|
||||
} catch (err) {
|
||||
} catch {
|
||||
// Don't fail the build
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,9 @@ const parsers = [
|
||||
"extra.projects = prepareAndTransformProjects(":
|
||||
"extra.projects = [] || prepareAndTransformProjects(",
|
||||
"process.versions.node": "'999.999.999'",
|
||||
// `rollup-plugin-polyfill-node` don't have polyfill for these modules
|
||||
'require("perf_hooks")': "{}",
|
||||
'require("inspector")': "{}",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -156,7 +159,7 @@ function getFileOutput(bundle) {
|
||||
return bundle.output || path.basename(bundle.input);
|
||||
}
|
||||
|
||||
module.exports = coreBundles.concat(parsers).map((bundle) => ({
|
||||
module.exports = [...coreBundles, ...parsers].map((bundle) => ({
|
||||
...bundle,
|
||||
output: getFileOutput(bundle),
|
||||
}));
|
||||
@@ -1,34 +0,0 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const builtins = require("builtin-modules");
|
||||
|
||||
const EMPTY = "export default {};";
|
||||
const PREFIX = "\0shim:";
|
||||
|
||||
module.exports = function (dir) {
|
||||
return {
|
||||
resolveId(importee) {
|
||||
if (importee.startsWith(PREFIX)) {
|
||||
return importee;
|
||||
}
|
||||
|
||||
if (/\0/.test(importee) || !builtins.includes(importee)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shim = path.resolve(dir, importee + ".mjs");
|
||||
if (fs.existsSync(shim)) {
|
||||
return shim;
|
||||
}
|
||||
return PREFIX + importee;
|
||||
},
|
||||
|
||||
load(id) {
|
||||
if (id.startsWith(PREFIX)) {
|
||||
return EMPTY;
|
||||
}
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -6,12 +6,9 @@ const tempy = require("tempy");
|
||||
|
||||
shell.config.fatal = true;
|
||||
|
||||
const rootDir = path.join(__dirname, "..");
|
||||
const client = process.env.NPM_CLIENT || "yarn";
|
||||
const packageDir =
|
||||
process.env.NODE_ENV === "production" ? path.join(rootDir, "dist") : rootDir;
|
||||
|
||||
module.exports = () => {
|
||||
module.exports = (packageDir) => {
|
||||
const tmpDir = tempy.directory();
|
||||
const file = shell.exec("npm pack", { cwd: packageDir }).stdout.trim();
|
||||
shell.mv(path.join(packageDir, file), tmpDir);
|
||||
|
||||
@@ -34,10 +34,10 @@ function logPromise(name, promise) {
|
||||
}
|
||||
|
||||
function runYarn(script) {
|
||||
if (typeof script === "string") {
|
||||
script = [script];
|
||||
}
|
||||
return execa("yarn", ["--silent"].concat(script)).catch((error) => {
|
||||
return execa("yarn", [
|
||||
"--silent",
|
||||
...(Array.isArray(script) ? script : [script]),
|
||||
]).catch((error) => {
|
||||
throw new Error(`\`yarn ${script}\` failed\n${error.stdout}`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,9 +101,9 @@ function run(argv) {
|
||||
"but that's not interesting for Prettier's tests.",
|
||||
"This is the skipped stuff:",
|
||||
"",
|
||||
]
|
||||
.concat(skipped, "")
|
||||
.join("\n")
|
||||
...skipped,
|
||||
"",
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ module.exports = {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
url:
|
||||
"https://github.com/prettier/prettier/blob/main/scripts/eslint-plugin-prettier-internal-rules/better-parent-property-check-in-needs-parens.js",
|
||||
"https://github.com/prettier/prettier/blob/main/scripts/tools/eslint-plugin-prettier-internal-rules/better-parent-property-check-in-needs-parens.js",
|
||||
},
|
||||
messages: {
|
||||
[MESSAGE_ID_PREFER_NAME_CHECK]:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"use strict";
|
||||
|
||||
// This rule only work for nested `AstPath#call()` for now
|
||||
|
||||
function astPathCallSelector(path) {
|
||||
const prefix = path ? `${path}.` : "";
|
||||
return [
|
||||
`[${prefix}type="CallExpression"]`,
|
||||
`[${prefix}callee.type="MemberExpression"]`,
|
||||
`[${prefix}callee.property.type="Identifier"]`,
|
||||
`[${prefix}callee.property.name="call"]`,
|
||||
`[${prefix}arguments.length>1]`,
|
||||
`[${prefix}arguments.0.type!="SpreadElement"]`,
|
||||
].join("");
|
||||
}
|
||||
|
||||
// Matches:
|
||||
// ```
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ```
|
||||
const selector = [
|
||||
astPathCallSelector(),
|
||||
'[arguments.0.type="ArrowFunctionExpression"]',
|
||||
"[arguments.0.params.length=1]",
|
||||
'[arguments.0.params.0.type="Identifier"]',
|
||||
astPathCallSelector("arguments.0.body"),
|
||||
'[arguments.0.body.callee.object.type="Identifier"]',
|
||||
].join("");
|
||||
|
||||
const MESSAGE_ID = "flat-ast-path-call";
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
url:
|
||||
"https://github.com/prettier/prettier/blob/main/scripts/tools/eslint-plugin-prettier-internal-rules/flat-ast-path-call.js",
|
||||
},
|
||||
messages: {
|
||||
[MESSAGE_ID]: "Do not use nested `AstPath#{{method}}(…)`.",
|
||||
},
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
[selector](outerCall) {
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
const outerCallback = outerCall.arguments[0];
|
||||
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^^^^^^^^^
|
||||
const outerCallbackParameterName = outerCallback.params[0].name;
|
||||
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
const innerCall = outerCallback.body;
|
||||
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^^^^^^^^^
|
||||
const innerCallCalleeObjectName = innerCall.callee.object.name;
|
||||
|
||||
if (outerCallbackParameterName !== innerCallCalleeObjectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: innerCall,
|
||||
messageId: MESSAGE_ID,
|
||||
data: { method: "call" },
|
||||
*fix(fixer) {
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^^^^^
|
||||
const innerCallback = innerCall.arguments[0];
|
||||
|
||||
yield fixer.replaceTextRange(
|
||||
[
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^
|
||||
outerCallback.range[0],
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^
|
||||
innerCallback.range[0],
|
||||
],
|
||||
""
|
||||
);
|
||||
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^
|
||||
const innerNamesStart = innerCallback.range[1];
|
||||
|
||||
// path.call((childPath) => childPath.call(print, "b"), "a")
|
||||
// ^
|
||||
const innerNamesEnd = innerCall.range[1] - 1;
|
||||
|
||||
let innerNamesText = sourceCode.text.slice(
|
||||
innerNamesStart,
|
||||
innerNamesEnd
|
||||
);
|
||||
|
||||
yield fixer.replaceTextRange(
|
||||
[innerNamesStart, innerNamesEnd + 1],
|
||||
""
|
||||
);
|
||||
|
||||
const [penultimateToken, lastToken] = sourceCode.getLastTokens(
|
||||
outerCall,
|
||||
2
|
||||
);
|
||||
|
||||
// `outer` call has `trailing comma`
|
||||
if (
|
||||
penultimateToken &&
|
||||
penultimateToken.type === "Punctuator" &&
|
||||
penultimateToken.value === ","
|
||||
) {
|
||||
innerNamesText = innerNamesText.slice(1);
|
||||
}
|
||||
|
||||
yield fixer.insertTextBefore(lastToken, innerNamesText);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -4,10 +4,13 @@ module.exports = {
|
||||
rules: {
|
||||
"better-parent-property-check-in-needs-parens": require("./better-parent-property-check-in-needs-parens"),
|
||||
"directly-loc-start-end": require("./directly-loc-start-end"),
|
||||
"flat-ast-path-call": require("./flat-ast-path-call"),
|
||||
"jsx-identifier-case": require("./jsx-identifier-case"),
|
||||
"no-doc-builder-concat": require("./no-doc-builder-concat"),
|
||||
"no-empty-flat-contents-for-if-break": require("./no-empty-flat-contents-for-if-break"),
|
||||
"no-node-comments": require("./no-node-comments"),
|
||||
"prefer-fast-path-each": require("./prefer-fast-path-each"),
|
||||
"prefer-ast-path-each": require("./prefer-ast-path-each"),
|
||||
"prefer-indent-if-break": require("./prefer-indent-if-break"),
|
||||
"prefer-is-non-empty-array": require("./prefer-is-non-empty-array"),
|
||||
"require-json-extensions": require("./require-json-extensions"),
|
||||
},
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
"use strict";
|
||||
|
||||
const selector = [
|
||||
"CallExpression",
|
||||
"[optional=false]",
|
||||
'[callee.type="Identifier"]',
|
||||
'[callee.name="ifBreak"]',
|
||||
"[arguments.length=2]",
|
||||
'[arguments.1.type="Literal"]',
|
||||
'[arguments.1.value=""]',
|
||||
':not([arguments.0.type="SpreadElement"])',
|
||||
].join("");
|
||||
|
||||
const messageId = "no-empty-flat-contents-for-if-break";
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
url:
|
||||
"https://github.com/prettier/prettier/blob/main/scripts/eslint-plugin-prettier-internal-rules/no-empty-flat-contents-for-if-break.js",
|
||||
},
|
||||
messages: {
|
||||
[messageId]:
|
||||
"Please don't pass an empty string to second parameter of ifBreak.",
|
||||
},
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
return {
|
||||
[selector](node) {
|
||||
const [breakContents] = node.arguments;
|
||||
context.report({
|
||||
node,
|
||||
messageId,
|
||||
fix: (fixer) =>
|
||||
fixer.removeRange([breakContents.range[1], node.range[1] - 1]),
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
+2
-2
@@ -14,7 +14,7 @@ const selector = [
|
||||
'[name="map"]',
|
||||
].join("");
|
||||
|
||||
const messageId = "prefer-fast-path-each";
|
||||
const messageId = "prefer-ast-path-each";
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
@@ -24,7 +24,7 @@ module.exports = {
|
||||
"https://github.com/prettier/prettier/blob/main/scripts/eslint-plugin-prettier-internal-rules/require-json-extensions.js",
|
||||
},
|
||||
messages: {
|
||||
[messageId]: "Prefer `FastPath#each()` over `FastPath#map()`.",
|
||||
[messageId]: "Prefer `AstPath#each()` over `AstPath#map()`.",
|
||||
},
|
||||
fixable: "code",
|
||||
},
|
||||
@@ -0,0 +1,67 @@
|
||||
"use strict";
|
||||
|
||||
const selector = [
|
||||
"CallExpression",
|
||||
"[optional=false]",
|
||||
'[callee.type="Identifier"]',
|
||||
'[callee.name="ifBreak"]',
|
||||
"[arguments.length=3]",
|
||||
'[arguments.0.type="CallExpression"]',
|
||||
"[arguments.0.optional=false]",
|
||||
'[arguments.0.callee.type="Identifier"]',
|
||||
'[arguments.0.callee.name="indent"]',
|
||||
"[arguments.0.arguments.length=1]",
|
||||
'[arguments.0.arguments.0.type!="SpreadElement"]',
|
||||
'[arguments.1.type!="SpreadElement"]',
|
||||
'[arguments.2.type!="SpreadElement"]',
|
||||
].join("");
|
||||
|
||||
const messageId = "prefer-indent-if-break";
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
url:
|
||||
"https://github.com/prettier/prettier/blob/main/scripts/eslint-plugin-prettier-internal-rules/prefer-indent-if-break.js",
|
||||
},
|
||||
messages: {
|
||||
[messageId]: "Prefer `indentIfBreak(…)` over `ifBreak(indent(…), …)`.",
|
||||
},
|
||||
fixable: "code",
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
return {
|
||||
[selector](node) {
|
||||
const indentDoc = node.arguments[0].arguments[0];
|
||||
const doc = node.arguments[1];
|
||||
|
||||
// Use text to compare same `doc`
|
||||
if (sourceCode.getText(indentDoc) !== sourceCode.getText(doc)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node,
|
||||
messageId,
|
||||
*fix(fixer) {
|
||||
yield fixer.replaceText(node.callee, "indentIfBreak");
|
||||
const openingParenthesisToken = sourceCode.getTokenAfter(
|
||||
node.callee
|
||||
);
|
||||
const commaToken = sourceCode.getTokenBefore(
|
||||
doc,
|
||||
({ type, value }) => type === "Punctuator" && value === ","
|
||||
);
|
||||
yield fixer.replaceTextRange(
|
||||
[openingParenthesisToken.range[1], commaToken.range[1]],
|
||||
""
|
||||
);
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -112,7 +112,7 @@ module.exports = {
|
||||
left = left.right;
|
||||
}
|
||||
|
||||
if (!left.type === "UnaryExpression" || left.operator !== "!") {
|
||||
if (left.type !== "UnaryExpression" || left.operator !== "!") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,33 @@ test("directly-loc-start-end", {
|
||||
],
|
||||
});
|
||||
|
||||
test("flat-ast-path-call", {
|
||||
valid: [
|
||||
'path.call((childPath) => childPath.notCall(print, "b"), "a")',
|
||||
'path.notCall((childPath) => childPath.call(print, "b"), "a")',
|
||||
'path.call((childPath) => childPath.call(print, "b"))',
|
||||
'path.call((childPath) => childPath.call(print), "a")',
|
||||
'path.call((childPath) => notChildPath.call(print), "a")',
|
||||
'path.call(functionReference, "a")',
|
||||
// Only check `arrow function`
|
||||
'path.call((childPath) => {return childPath.call(print, "b")}, "a")',
|
||||
'path.call(function(childPath) {return childPath.call(print, "b")}, "a")',
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: 'path.call((childPath) => childPath.call(print, "b"), "a")',
|
||||
output: 'path.call(print, "a", "b")',
|
||||
errors: [{ message: "Do not use nested `AstPath#call(…)`." }],
|
||||
},
|
||||
{
|
||||
// Trailing comma
|
||||
code: 'path.call((childPath) => childPath.call(print, "b"), "a",)',
|
||||
output: 'path.call(print, "a", "b")',
|
||||
errors: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("jsx-identifier-case", {
|
||||
valid: [
|
||||
{
|
||||
@@ -144,7 +171,7 @@ test("no-node-comments", {
|
||||
],
|
||||
});
|
||||
|
||||
test("prefer-fast-path-each", {
|
||||
test("prefer-ast-path-each", {
|
||||
valid: ["const foo = path.map()"],
|
||||
invalid: [
|
||||
{
|
||||
@@ -155,6 +182,35 @@ test("prefer-fast-path-each", {
|
||||
],
|
||||
});
|
||||
|
||||
test("prefer-indent-if-break", {
|
||||
valid: [
|
||||
"ifBreak(indent(doc))",
|
||||
"notIfBreak(indent(doc), doc, options)",
|
||||
"ifBreak(indent(doc), doc, )",
|
||||
"ifBreak(...a, ...b, ...c)",
|
||||
"ifBreak(notIndent(doc), doc, options)",
|
||||
"ifBreak(indent(doc), notSameDoc, options)",
|
||||
"ifBreak(indent(...a), a, options)",
|
||||
"ifBreak(indent(a, b), a, options)",
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "ifBreak(indent(doc), doc, options)",
|
||||
output: "indentIfBreak( doc, options)",
|
||||
errors: [
|
||||
{
|
||||
message: "Prefer `indentIfBreak(…)` over `ifBreak(indent(…), …)`.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "ifBreak((indent(doc)), (doc), options)",
|
||||
output: "indentIfBreak( (doc), options)",
|
||||
errors: 1,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("prefer-is-non-empty-array", {
|
||||
valid: [
|
||||
// `isNonEmptyArray` self is ignored
|
||||
@@ -204,3 +260,46 @@ test("require-json-extensions", {
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
test("no-empty-flat-contents-for-if-break", {
|
||||
valid: [
|
||||
"ifBreak('foo', 'bar')",
|
||||
"ifBreak(doc1, doc2)",
|
||||
"ifBreak(',')",
|
||||
"ifBreak(doc)",
|
||||
"ifBreak('foo', '', { groupId })",
|
||||
"ifBreak(...foo, { groupId })",
|
||||
],
|
||||
invalid: [
|
||||
{
|
||||
code: "ifBreak('foo', '')",
|
||||
output: "ifBreak('foo')",
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"Please don't pass an empty string to second parameter of ifBreak.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "ifBreak('foo' , '' )",
|
||||
output: "ifBreak('foo')",
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"Please don't pass an empty string to second parameter of ifBreak.",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "ifBreak(doc, '')",
|
||||
output: "ifBreak(doc)",
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
"Please don't pass an empty string to second parameter of ifBreak.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
@@ -132,6 +132,9 @@ const options = {
|
||||
"debug-print-doc": {
|
||||
type: "boolean",
|
||||
},
|
||||
"debug-print-comments": {
|
||||
type: "boolean",
|
||||
},
|
||||
"debug-repeat": {
|
||||
// Repeat the formatting a few times and measure the average duration.
|
||||
type: "int",
|
||||
@@ -145,6 +148,10 @@ const options = {
|
||||
"Don't take .editorconfig into account when parsing configuration.",
|
||||
default: true,
|
||||
},
|
||||
"error-on-unmatched-pattern": {
|
||||
type: "boolean",
|
||||
oppositeDescription: "Prevent errors when pattern is unmatched.",
|
||||
},
|
||||
"find-config-path": {
|
||||
type: "path",
|
||||
category: coreOptions.CATEGORY_CONFIG,
|
||||
|
||||
+78
-71
@@ -4,10 +4,9 @@ const pick = require("lodash/pick");
|
||||
|
||||
// eslint-disable-next-line no-restricted-modules
|
||||
const prettier = require("../index");
|
||||
const { createLogger } = require("./logger");
|
||||
const {
|
||||
optionsModule,
|
||||
optionsNormalizer,
|
||||
optionsNormalizer: { normalizeCliOptions },
|
||||
utils: { arrayify },
|
||||
} = require("./prettier-internal");
|
||||
const minimist = require("./minimist");
|
||||
@@ -21,7 +20,7 @@ const createMinimistOptions = require("./create-minimist-options");
|
||||
/**
|
||||
* @typedef {Object} Context
|
||||
* @property logger
|
||||
* @property {string[]} args
|
||||
* @property {string[]} rawArguments
|
||||
* @property argv
|
||||
* @property {string[]} filePatterns
|
||||
* @property {any[]} supportOptions
|
||||
@@ -30,24 +29,29 @@ const createMinimistOptions = require("./create-minimist-options");
|
||||
* @property apiDefaultOptions
|
||||
* @property languages
|
||||
* @property {Partial<Context>[]} stack
|
||||
* @property initContext
|
||||
* @property pushContextPlugins
|
||||
* @property popContextPlugins
|
||||
*/
|
||||
|
||||
class Context {
|
||||
constructor(args) {
|
||||
this.args = args;
|
||||
constructor({ rawArguments, logger }) {
|
||||
this.rawArguments = rawArguments;
|
||||
this.logger = logger;
|
||||
this.stack = [];
|
||||
this._updateContextArgv();
|
||||
this._normalizeContextArgv(["loglevel", "plugin", "plugin-search-dir"]);
|
||||
this.logger = createLogger(this.argv.loglevel);
|
||||
this._updateContextArgv(this.argv.plugin, this.argv["plugin-search-dir"]);
|
||||
}
|
||||
|
||||
initContext() {
|
||||
// split into 2 step so that we could wrap this in a `try..catch` in cli/index.js
|
||||
this._normalizeContextArgv();
|
||||
const {
|
||||
plugin: plugins,
|
||||
"plugin-search-dir": pluginSearchDirs,
|
||||
} = parseArgvWithoutPlugins(rawArguments, logger, [
|
||||
"plugin",
|
||||
"plugin-search-dir",
|
||||
]);
|
||||
|
||||
this.pushContextPlugins(plugins, pluginSearchDirs);
|
||||
|
||||
const argv = parseArgv(rawArguments, this.detailedOptions, logger);
|
||||
this.argv = argv;
|
||||
this.filePatterns = argv._.map((file) => String(file));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,67 +68,70 @@ class Context {
|
||||
"languages",
|
||||
])
|
||||
);
|
||||
this._updateContextOptions(plugins, pluginSearchDirs);
|
||||
|
||||
Object.assign(this, getContextOptions(plugins, pluginSearchDirs));
|
||||
}
|
||||
|
||||
popContextPlugins() {
|
||||
Object.assign(this, this.stack.pop());
|
||||
}
|
||||
|
||||
_updateContextArgv(plugins, pluginSearchDirs) {
|
||||
this.pushContextPlugins(plugins, pluginSearchDirs);
|
||||
const minimistOptions = createMinimistOptions(this.detailedOptions);
|
||||
const argv = minimist(this.args, minimistOptions);
|
||||
this.argv = argv;
|
||||
this.filePatterns = argv._.map((file) => String(file));
|
||||
}
|
||||
|
||||
_normalizeContextArgv(keys) {
|
||||
const detailedOptions = !keys
|
||||
? this.detailedOptions
|
||||
: this.detailedOptions.filter((option) => keys.includes(option.name));
|
||||
const argv = !keys ? this.argv : pick(this.argv, keys);
|
||||
this.argv = optionsNormalizer.normalizeCliOptions(argv, detailedOptions, {
|
||||
logger: this.logger,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string[]} plugins
|
||||
* @param {string[]=} pluginSearchDirs
|
||||
*/
|
||||
_updateContextOptions(plugins, pluginSearchDirs) {
|
||||
const { options: supportOptions, languages } = prettier.getSupportInfo({
|
||||
showDeprecated: true,
|
||||
showUnreleased: true,
|
||||
showInternal: true,
|
||||
plugins,
|
||||
pluginSearchDirs,
|
||||
});
|
||||
const detailedOptionMap = normalizeDetailedOptionMap({
|
||||
...createDetailedOptionMap(supportOptions),
|
||||
...constant.options,
|
||||
});
|
||||
|
||||
const detailedOptions = arrayify(detailedOptionMap, "name");
|
||||
|
||||
const apiDefaultOptions = {
|
||||
...optionsModule.hiddenDefaults,
|
||||
...fromPairs(
|
||||
supportOptions
|
||||
.filter(({ deprecated }) => !deprecated)
|
||||
.map((option) => [option.name, option.default])
|
||||
),
|
||||
};
|
||||
|
||||
Object.assign(this, {
|
||||
supportOptions,
|
||||
detailedOptions,
|
||||
detailedOptionMap,
|
||||
apiDefaultOptions,
|
||||
languages,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Context;
|
||||
function getContextOptions(plugins, pluginSearchDirs) {
|
||||
const { options: supportOptions, languages } = prettier.getSupportInfo({
|
||||
showDeprecated: true,
|
||||
showUnreleased: true,
|
||||
showInternal: true,
|
||||
plugins,
|
||||
pluginSearchDirs,
|
||||
});
|
||||
const detailedOptionMap = normalizeDetailedOptionMap({
|
||||
...createDetailedOptionMap(supportOptions),
|
||||
...constant.options,
|
||||
});
|
||||
|
||||
const detailedOptions = arrayify(detailedOptionMap, "name");
|
||||
|
||||
const apiDefaultOptions = {
|
||||
...optionsModule.hiddenDefaults,
|
||||
...fromPairs(
|
||||
supportOptions
|
||||
.filter(({ deprecated }) => !deprecated)
|
||||
.map((option) => [option.name, option.default])
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
supportOptions,
|
||||
detailedOptions,
|
||||
detailedOptionMap,
|
||||
apiDefaultOptions,
|
||||
languages,
|
||||
};
|
||||
}
|
||||
|
||||
function parseArgv(rawArguments, detailedOptions, logger, keys) {
|
||||
const minimistOptions = createMinimistOptions(detailedOptions);
|
||||
let argv = minimist(rawArguments, minimistOptions);
|
||||
|
||||
if (keys) {
|
||||
detailedOptions = detailedOptions.filter((option) =>
|
||||
keys.includes(option.name)
|
||||
);
|
||||
argv = pick(argv, keys);
|
||||
}
|
||||
|
||||
return normalizeCliOptions(argv, detailedOptions, { logger });
|
||||
}
|
||||
|
||||
const detailedOptionsWithoutPlugins = getContextOptions().detailedOptions;
|
||||
function parseArgvWithoutPlugins(rawArguments, logger, keys) {
|
||||
return parseArgv(
|
||||
rawArguments,
|
||||
detailedOptionsWithoutPlugins,
|
||||
logger,
|
||||
typeof keys === "string" ? [keys] : keys
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = { Context, parseArgvWithoutPlugins };
|
||||
+7
-5
@@ -8,21 +8,21 @@ const stringify = require("fast-json-stable-stringify");
|
||||
const prettier = require("../index");
|
||||
|
||||
const { format, formatStdin, formatFiles } = require("./format");
|
||||
const Context = require("./context");
|
||||
const { Context, parseArgvWithoutPlugins } = require("./context");
|
||||
const {
|
||||
normalizeDetailedOptionMap,
|
||||
createDetailedOptionMap,
|
||||
} = require("./option-map");
|
||||
const { createDetailedUsage, createUsage } = require("./usage");
|
||||
const { createLogger } = require("./logger");
|
||||
|
||||
function logResolvedConfigPathOrDie(context) {
|
||||
const configFile = prettier.resolveConfigFile.sync(
|
||||
context.argv["find-config-path"]
|
||||
);
|
||||
const file = context.argv["find-config-path"];
|
||||
const configFile = prettier.resolveConfigFile.sync(file);
|
||||
if (configFile) {
|
||||
context.logger.log(path.relative(process.cwd(), configFile));
|
||||
} else {
|
||||
process.exit(1);
|
||||
throw new Error(`Can not find configure file for "${file}"`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +54,6 @@ module.exports = {
|
||||
logResolvedConfigPathOrDie,
|
||||
logFileInfoOrDie,
|
||||
normalizeDetailedOptionMap,
|
||||
parseArgvWithoutPlugins,
|
||||
createLogger,
|
||||
};
|
||||
+15
-17
@@ -33,7 +33,7 @@ function* expandPatterns(context) {
|
||||
yield relativePath;
|
||||
}
|
||||
|
||||
if (noResults) {
|
||||
if (noResults && context.argv["error-on-unmatched-pattern"] !== false) {
|
||||
// If there was no files and no other errors, let's yield a general error.
|
||||
yield {
|
||||
error: `No matching files. Patterns: ${context.filePatterns.join(" ")}`,
|
||||
@@ -46,18 +46,13 @@ function* expandPatterns(context) {
|
||||
*/
|
||||
function* expandPatternsInternal(context) {
|
||||
// Ignores files in version control systems directories and `node_modules`
|
||||
const silentlyIgnoredDirs = {
|
||||
".git": true,
|
||||
".svn": true,
|
||||
".hg": true,
|
||||
node_modules: context.argv["with-node-modules"] !== true,
|
||||
};
|
||||
|
||||
const silentlyIgnoredDirs = [".git", ".svn", ".hg"];
|
||||
if (context.argv["with-node-modules"] !== true) {
|
||||
silentlyIgnoredDirs.push("node_modules");
|
||||
}
|
||||
const globOptions = {
|
||||
dot: true,
|
||||
ignore: Object.keys(silentlyIgnoredDirs)
|
||||
.filter((dir) => silentlyIgnoredDirs[dir])
|
||||
.map((dir) => "**/" + dir),
|
||||
ignore: silentlyIgnoredDirs.map((dir) => "**/" + dir),
|
||||
};
|
||||
|
||||
let supportedFilesGlob;
|
||||
@@ -116,7 +111,9 @@ function* expandPatternsInternal(context) {
|
||||
}
|
||||
|
||||
if (result.length === 0) {
|
||||
yield { error: `${errorMessages.emptyResults[type]}: "${input}".` };
|
||||
if (context.argv["error-on-unmatched-pattern"] !== false) {
|
||||
yield { error: `${errorMessages.emptyResults[type]}: "${input}".` };
|
||||
}
|
||||
} else {
|
||||
yield* sortPaths(result);
|
||||
}
|
||||
@@ -130,9 +127,10 @@ function* expandPatternsInternal(context) {
|
||||
const filenames = flat(
|
||||
context.languages.map((lang) => lang.filenames || [])
|
||||
);
|
||||
supportedFilesGlob = `**/{${extensions
|
||||
.map((ext) => "*" + (ext[0] === "." ? ext : "." + ext))
|
||||
.concat(filenames)}}`;
|
||||
supportedFilesGlob = `**/{${[
|
||||
...extensions.map((ext) => "*" + (ext[0] === "." ? ext : "." + ext)),
|
||||
...filenames,
|
||||
]}}`;
|
||||
}
|
||||
return supportedFilesGlob;
|
||||
}
|
||||
@@ -154,13 +152,13 @@ const errorMessages = {
|
||||
/**
|
||||
* @param {string} absolutePath
|
||||
* @param {string} cwd
|
||||
* @param {Record<string, boolean>} ignoredDirectories
|
||||
* @param {string[]} ignoredDirectories
|
||||
*/
|
||||
function containsIgnoredPathSegment(absolutePath, cwd, ignoredDirectories) {
|
||||
return path
|
||||
.relative(cwd, absolutePath)
|
||||
.split(path.sep)
|
||||
.some((dir) => ignoredDirectories[dir]);
|
||||
.some((dir) => ignoredDirectories.includes(dir));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+28
-16
@@ -1,7 +1,6 @@
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs");
|
||||
const readline = require("readline");
|
||||
const path = require("path");
|
||||
|
||||
const chalk = require("chalk");
|
||||
@@ -22,13 +21,15 @@ function diff(a, b) {
|
||||
});
|
||||
}
|
||||
|
||||
function handleError(context, filename, error) {
|
||||
function handleError(context, filename, error, printedFilename) {
|
||||
if (error instanceof errors.UndefinedParserError) {
|
||||
// Can't test on CI, `isTTY()` is always false, see ./is-tty.js
|
||||
/* istanbul ignore next */
|
||||
if ((context.argv.write || context.argv["ignore-unknown"]) && isTTY()) {
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0, null);
|
||||
if (
|
||||
(context.argv.write || context.argv["ignore-unknown"]) &&
|
||||
printedFilename
|
||||
) {
|
||||
printedFilename.clear();
|
||||
}
|
||||
if (context.argv["ignore-unknown"]) {
|
||||
return;
|
||||
@@ -113,7 +114,16 @@ function format(context, input, opt) {
|
||||
|
||||
if (context.argv["debug-print-doc"]) {
|
||||
const doc = prettier.__debug.printToDoc(input, opt);
|
||||
return { formatted: prettier.__debug.formatDoc(doc) };
|
||||
return { formatted: prettier.__debug.formatDoc(doc) + "\n" };
|
||||
}
|
||||
|
||||
if (context.argv["debug-print-comments"]) {
|
||||
return {
|
||||
formatted: prettier.format(
|
||||
JSON.stringify(prettier.formatWithCursor(input, opt).comments || []),
|
||||
{ parser: "json" }
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
if (context.argv["debug-check"]) {
|
||||
@@ -155,7 +165,7 @@ function format(context, input, opt) {
|
||||
let benchmark;
|
||||
try {
|
||||
benchmark = eval("require")("benchmark");
|
||||
} catch (err) {
|
||||
} catch {
|
||||
context.logger.debug(
|
||||
"'--debug-benchmark' requires the 'benchmark' package to be installed."
|
||||
);
|
||||
@@ -188,13 +198,12 @@ function format(context, input, opt) {
|
||||
repeat +
|
||||
" times."
|
||||
);
|
||||
// should be using `performance.now()`, but only `Date` is cross-platform enough
|
||||
const now = Date.now ? () => Date.now() : () => +new Date();
|
||||
let totalMs = 0;
|
||||
for (let i = 0; i < repeat; ++i) {
|
||||
const startMs = now();
|
||||
// should be using `performance.now()`, but only `Date` is cross-platform enough
|
||||
const startMs = Date.now();
|
||||
prettier.formatWithCursor(input, opt);
|
||||
totalMs += now() - startMs;
|
||||
totalMs += Date.now() - startMs;
|
||||
}
|
||||
const averageMs = totalMs / repeat;
|
||||
const results = {
|
||||
@@ -300,8 +309,12 @@ function formatFiles(context) {
|
||||
filepath: filename,
|
||||
};
|
||||
|
||||
let printedFilename;
|
||||
if (isTTY()) {
|
||||
context.logger.log(filename, { newline: false });
|
||||
printedFilename = context.logger.log(filename, {
|
||||
newline: false,
|
||||
clearable: true,
|
||||
});
|
||||
}
|
||||
|
||||
let input;
|
||||
@@ -339,16 +352,15 @@ function formatFiles(context) {
|
||||
result = format(context, input, options);
|
||||
output = result.formatted;
|
||||
} catch (error) {
|
||||
handleError(context, filename, error);
|
||||
handleError(context, filename, error, printedFilename);
|
||||
continue;
|
||||
}
|
||||
|
||||
const isDifferent = output !== input;
|
||||
|
||||
if (isTTY()) {
|
||||
if (printedFilename) {
|
||||
// Remove previously printed filename to log it with duration.
|
||||
readline.clearLine(process.stdout, 0);
|
||||
readline.cursorTo(process.stdout, 0, null);
|
||||
printedFilename.clear();
|
||||
}
|
||||
|
||||
if (context.argv.write) {
|
||||
|
||||
+76
-66
@@ -8,77 +8,87 @@ const stringify = require("fast-json-stable-stringify");
|
||||
const prettier = require("../index");
|
||||
const core = require("./core");
|
||||
|
||||
function run(args) {
|
||||
const context = new core.Context(args);
|
||||
function run(rawArguments) {
|
||||
// Create a default level logger, so we can log errors during `logLevel` parsing
|
||||
let logger = core.createLogger();
|
||||
|
||||
try {
|
||||
context.initContext();
|
||||
|
||||
context.logger.debug(`normalized argv: ${JSON.stringify(context.argv)}`);
|
||||
|
||||
if (context.argv.check && context.argv["list-different"]) {
|
||||
context.logger.error("Cannot use --check and --list-different together.");
|
||||
process.exit(1);
|
||||
const logLevel = core.parseArgvWithoutPlugins(
|
||||
rawArguments,
|
||||
logger,
|
||||
"loglevel"
|
||||
).loglevel;
|
||||
if (logLevel !== logger.logLevel) {
|
||||
logger = core.createLogger(logLevel);
|
||||
}
|
||||
|
||||
if (context.argv.write && context.argv["debug-check"]) {
|
||||
context.logger.error("Cannot use --write and --debug-check together.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (context.argv["find-config-path"] && context.filePatterns.length > 0) {
|
||||
context.logger.error("Cannot use --find-config-path with multiple files");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (context.argv["file-info"] && context.filePatterns.length > 0) {
|
||||
context.logger.error("Cannot use --file-info with multiple files");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (context.argv.version) {
|
||||
context.logger.log(prettier.version);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (context.argv.help !== undefined) {
|
||||
context.logger.log(
|
||||
typeof context.argv.help === "string" && context.argv.help !== ""
|
||||
? core.createDetailedUsage(context, context.argv.help)
|
||||
: core.createUsage(context)
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (context.argv["support-info"]) {
|
||||
context.logger.log(
|
||||
prettier.format(stringify(prettier.getSupportInfo()), {
|
||||
parser: "json",
|
||||
})
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const hasFilePatterns = context.filePatterns.length > 0;
|
||||
const useStdin =
|
||||
!hasFilePatterns &&
|
||||
(!process.stdin.isTTY || context.args["stdin-filepath"]);
|
||||
|
||||
if (context.argv["find-config-path"]) {
|
||||
core.logResolvedConfigPathOrDie(context);
|
||||
} else if (context.argv["file-info"]) {
|
||||
core.logFileInfoOrDie(context);
|
||||
} else if (useStdin) {
|
||||
core.formatStdin(context);
|
||||
} else if (hasFilePatterns) {
|
||||
core.formatFiles(context);
|
||||
} else {
|
||||
context.logger.log(core.createUsage(context));
|
||||
process.exit(1);
|
||||
}
|
||||
main(rawArguments, logger);
|
||||
} catch (error) {
|
||||
context.logger.error(error.message);
|
||||
process.exit(1);
|
||||
logger.error(error.message);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function main(rawArguments, logger) {
|
||||
const context = new core.Context({ rawArguments, logger });
|
||||
|
||||
logger.debug(`normalized argv: ${JSON.stringify(context.argv)}`);
|
||||
|
||||
if (context.argv.check && context.argv["list-different"]) {
|
||||
throw new Error("Cannot use --check and --list-different together.");
|
||||
}
|
||||
|
||||
if (context.argv.write && context.argv["debug-check"]) {
|
||||
throw new Error("Cannot use --write and --debug-check together.");
|
||||
}
|
||||
|
||||
if (context.argv["find-config-path"] && context.filePatterns.length > 0) {
|
||||
throw new Error("Cannot use --find-config-path with multiple files");
|
||||
}
|
||||
|
||||
if (context.argv["file-info"] && context.filePatterns.length > 0) {
|
||||
throw new Error("Cannot use --file-info with multiple files");
|
||||
}
|
||||
|
||||
if (context.argv.version) {
|
||||
logger.log(prettier.version);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.argv.help !== undefined) {
|
||||
logger.log(
|
||||
typeof context.argv.help === "string" && context.argv.help !== ""
|
||||
? core.createDetailedUsage(context, context.argv.help)
|
||||
: core.createUsage(context)
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.argv["support-info"]) {
|
||||
logger.log(
|
||||
prettier.format(stringify(prettier.getSupportInfo()), {
|
||||
parser: "json",
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const hasFilePatterns = context.filePatterns.length > 0;
|
||||
const useStdin =
|
||||
!hasFilePatterns &&
|
||||
(!process.stdin.isTTY || context.argv["stdin-filepath"]);
|
||||
|
||||
if (context.argv["find-config-path"]) {
|
||||
core.logResolvedConfigPathOrDie(context);
|
||||
} else if (context.argv["file-info"]) {
|
||||
core.logFileInfoOrDie(context);
|
||||
} else if (useStdin) {
|
||||
core.formatStdin(context);
|
||||
} else if (hasFilePatterns) {
|
||||
core.formatFiles(context);
|
||||
} else {
|
||||
logger.log(core.createUsage(context));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+45
-6
@@ -1,9 +1,36 @@
|
||||
"use strict";
|
||||
|
||||
const readline = require("readline");
|
||||
const chalk = require("chalk");
|
||||
const stripAnsi = require("strip-ansi");
|
||||
const wcwidth = require("wcwidth");
|
||||
|
||||
function createLogger(logLevel) {
|
||||
const countLines = (stream, text) => {
|
||||
const columns = stream.columns || 80;
|
||||
let lineCount = 0;
|
||||
for (const line of stripAnsi(text).split("\n")) {
|
||||
lineCount += Math.max(1, Math.ceil(wcwidth(line) / columns));
|
||||
}
|
||||
return lineCount;
|
||||
};
|
||||
|
||||
const clear = (stream, text) => () => {
|
||||
const lineCount = countLines(stream, text);
|
||||
|
||||
for (let line = 0; line < lineCount; line++) {
|
||||
if (line > 0) {
|
||||
readline.moveCursor(stream, 0, -1);
|
||||
}
|
||||
|
||||
readline.clearLine(stream, 0);
|
||||
readline.cursorTo(stream, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const emptyLogResult = { clear() {} };
|
||||
function createLogger(logLevel = "log") {
|
||||
return {
|
||||
logLevel,
|
||||
warn: createLogFunc("warn", "yellow"),
|
||||
error: createLogFunc("error", "red"),
|
||||
debug: createLogFunc("debug", "blue"),
|
||||
@@ -12,14 +39,26 @@ function createLogger(logLevel) {
|
||||
|
||||
function createLogFunc(loggerName, color) {
|
||||
if (!shouldLog(loggerName)) {
|
||||
return () => {};
|
||||
return () => emptyLogResult;
|
||||
}
|
||||
|
||||
const prefix = color ? `[${chalk[color](loggerName)}] ` : "";
|
||||
return function (message, opts) {
|
||||
opts = { newline: true, ...opts };
|
||||
const stream = process[loggerName === "log" ? "stdout" : "stderr"];
|
||||
stream.write(message.replace(/^/gm, prefix) + (opts.newline ? "\n" : ""));
|
||||
const stream = process[loggerName === "log" ? "stdout" : "stderr"];
|
||||
|
||||
return (message, options) => {
|
||||
options = {
|
||||
newline: true,
|
||||
clearable: false,
|
||||
...options,
|
||||
};
|
||||
message = message.replace(/^/gm, prefix) + (options.newline ? "\n" : "");
|
||||
stream.write(message);
|
||||
|
||||
if (options.clearable) {
|
||||
return {
|
||||
clear: clear(stream, message),
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+8
-7
@@ -20,13 +20,14 @@ function getOptions(argv, detailedOptions) {
|
||||
}
|
||||
|
||||
function cliifyOptions(object, apiDetailedOptionMap) {
|
||||
return Object.keys(object || {}).reduce((output, key) => {
|
||||
const apiOption = apiDetailedOptionMap[key];
|
||||
const cliKey = apiOption ? apiOption.name : key;
|
||||
return fromPairs(
|
||||
Object.entries(object || {}).map(([key, value]) => {
|
||||
const apiOption = apiDetailedOptionMap[key];
|
||||
const cliKey = apiOption ? apiOption.name : key;
|
||||
|
||||
output[dashify(cliKey)] = object[key];
|
||||
return output;
|
||||
}, {});
|
||||
return [dashify(cliKey), value];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function createApiDetailedOptionMap(detailedOptions) {
|
||||
@@ -46,7 +47,7 @@ function parseArgsToOptions(context, overrideDefaults) {
|
||||
);
|
||||
return getOptions(
|
||||
optionsNormalizer.normalizeCliOptions(
|
||||
minimist(context.args, {
|
||||
minimist(context.rawArguments, {
|
||||
string: minimistOptions.string,
|
||||
boolean: minimistOptions.boolean,
|
||||
default: cliifyOptions(overrideDefaults, apiDetailedOptionMap),
|
||||
|
||||
+3
-6
@@ -148,7 +148,7 @@ function createUsage(context) {
|
||||
return `${category} options:\n\n${indent(categoryOptions, 2)}`;
|
||||
});
|
||||
|
||||
return [constant.usageSummary].concat(optionsUsage, [""]).join("\n\n");
|
||||
return [constant.usageSummary, ...optionsUsage, ""].join("\n\n");
|
||||
}
|
||||
|
||||
function createDetailedUsage(context, flag) {
|
||||
@@ -176,11 +176,8 @@ function createDetailedUsage(context, flag) {
|
||||
|
||||
const pluginDefaults =
|
||||
option.pluginDefaults && Object.keys(option.pluginDefaults).length > 0
|
||||
? `\nPlugin defaults:${Object.keys(option.pluginDefaults).map(
|
||||
(key) =>
|
||||
`\n* ${key}: ${createDefaultValueDisplay(
|
||||
option.pluginDefaults[key]
|
||||
)}`
|
||||
? `\nPlugin defaults:${Object.entries(option.pluginDefaults).map(
|
||||
([key, value]) => `\n* ${key}: ${createDefaultValueDisplay(value)}`
|
||||
)}`
|
||||
: "";
|
||||
return `${header}${description}${choices}${defaults}${pluginDefaults}`;
|
||||
|
||||
@@ -16,7 +16,7 @@ function getNodeStackIndexHelper(stack, count) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
class FastPath {
|
||||
class AstPath {
|
||||
constructor(value) {
|
||||
this.stack = [value];
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class FastPath {
|
||||
|
||||
// Temporarily push properties named by string arguments given after the
|
||||
// callback function onto this.stack, then call the callback with a
|
||||
// reference to this (modified) FastPath object. Note that the stack will
|
||||
// reference to this (modified) AstPath object. Note that the stack will
|
||||
// be restored to its original state after the callback is finished, so it
|
||||
// is probably a mistake to retain a reference to the path.
|
||||
call(callback, ...names) {
|
||||
@@ -76,7 +76,7 @@ class FastPath {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Similar to FastPath.prototype.call, except that the value obtained by
|
||||
// Similar to AstPath.prototype.call, except that the value obtained by
|
||||
// accessing this.getValue()[name1][name2]... should be array. The
|
||||
// callback will be called with a reference to this path object for each
|
||||
// element of the array.
|
||||
@@ -99,7 +99,7 @@ class FastPath {
|
||||
stack.length = length;
|
||||
}
|
||||
|
||||
// Similar to FastPath.prototype.each, except that the results of the
|
||||
// Similar to AstPath.prototype.each, except that the results of the
|
||||
// callback function invocations are stored in an array and returned at
|
||||
// the end of the iteration.
|
||||
map(callback, ...names) {
|
||||
@@ -146,6 +146,37 @@ class FastPath {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Traverses the ancestors of the current node heading toward the tree root
|
||||
* until it finds a node that matches the provided predicate function. Will
|
||||
* return the first matching ancestor. If no such node exists, returns undefined.
|
||||
* @param {(node: any, name: string, number: number | null) => boolean} predicate
|
||||
* @internal Unstable API. Don't use in plugins for now.
|
||||
*/
|
||||
findAncestor(predicate) {
|
||||
let stackPointer = this.stack.length - 1;
|
||||
|
||||
let name = null;
|
||||
let node = this.stack[stackPointer--];
|
||||
|
||||
while (node) {
|
||||
// skip index/array
|
||||
let number = null;
|
||||
if (typeof name === "number") {
|
||||
number = name;
|
||||
name = this.stack[stackPointer--];
|
||||
node = this.stack[stackPointer--];
|
||||
}
|
||||
|
||||
if (name !== null && predicate(node, name, number)) {
|
||||
return node;
|
||||
}
|
||||
|
||||
name = this.stack[stackPointer--];
|
||||
node = this.stack[stackPointer--];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FastPath;
|
||||
module.exports = AstPath;
|
||||
+15
-13
@@ -4,6 +4,7 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
const uniqBy = require("lodash/uniqBy");
|
||||
const partition = require("lodash/partition");
|
||||
const flatten = require("lodash/flatten");
|
||||
const globby = require("globby");
|
||||
const mem = require("mem");
|
||||
const internalPlugins = require("../languages");
|
||||
@@ -44,7 +45,7 @@ function load(plugins, pluginSearchDirs) {
|
||||
try {
|
||||
// try local files
|
||||
requirePath = resolve(path.resolve(process.cwd(), pluginName));
|
||||
} catch (_) {
|
||||
} catch {
|
||||
// try node modules
|
||||
requirePath = resolve(pluginName, { paths: [process.cwd()] });
|
||||
}
|
||||
@@ -56,8 +57,8 @@ function load(plugins, pluginSearchDirs) {
|
||||
}
|
||||
);
|
||||
|
||||
const externalAutoLoadPluginInfos = pluginSearchDirs
|
||||
.map((pluginSearchDir) => {
|
||||
const externalAutoLoadPluginInfos = flatten(
|
||||
pluginSearchDirs.map((pluginSearchDir) => {
|
||||
const resolvedPluginSearchDir = path.resolve(
|
||||
process.cwd(),
|
||||
pluginSearchDir
|
||||
@@ -85,19 +86,20 @@ function load(plugins, pluginSearchDirs) {
|
||||
requirePath: resolve(pluginName, { paths: [resolvedPluginSearchDir] }),
|
||||
}));
|
||||
})
|
||||
.reduce((a, b) => a.concat(b), []);
|
||||
);
|
||||
|
||||
const externalPlugins = uniqBy(
|
||||
externalManualLoadPluginInfos.concat(externalAutoLoadPluginInfos),
|
||||
"requirePath"
|
||||
)
|
||||
.map((externalPluginInfo) => ({
|
||||
const externalPlugins = [
|
||||
...uniqBy(
|
||||
[...externalManualLoadPluginInfos, ...externalAutoLoadPluginInfos],
|
||||
"requirePath"
|
||||
).map((externalPluginInfo) => ({
|
||||
name: externalPluginInfo.name,
|
||||
...eval("require")(externalPluginInfo.requirePath),
|
||||
}))
|
||||
.concat(externalPluginInstances);
|
||||
})),
|
||||
...externalPluginInstances,
|
||||
];
|
||||
|
||||
return internalPlugins.concat(externalPlugins);
|
||||
return [...internalPlugins, ...externalPlugins];
|
||||
}
|
||||
|
||||
function findPluginsInNodeModules(nodeModulesDir) {
|
||||
@@ -118,7 +120,7 @@ function findPluginsInNodeModules(nodeModulesDir) {
|
||||
function isDirectory(dir) {
|
||||
try {
|
||||
return fs.statSync(dir).isDirectory();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+27
-26
@@ -164,8 +164,7 @@ function skipNewline(text, index, opts) {
|
||||
* @param {SkipOptions=} opts
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasNewline(text, index, opts) {
|
||||
opts = opts || {};
|
||||
function hasNewline(text, index, opts = {}) {
|
||||
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts);
|
||||
const idx2 = skipNewline(text, idx, opts);
|
||||
return idx !== idx2;
|
||||
@@ -292,8 +291,7 @@ function getNextNonSpaceNonCommentCharacter(text, node, locEnd) {
|
||||
* @param {SkipOptions=} opts
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasSpaces(text, index, opts) {
|
||||
opts = opts || {};
|
||||
function hasSpaces(text, index, opts = {}) {
|
||||
const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts);
|
||||
return idx !== index;
|
||||
}
|
||||
@@ -304,9 +302,7 @@ function hasSpaces(text, index, opts) {
|
||||
* @param {number=} startIndex
|
||||
* @returns {number}
|
||||
*/
|
||||
function getAlignmentSize(value, tabWidth, startIndex) {
|
||||
startIndex = startIndex || 0;
|
||||
|
||||
function getAlignmentSize(value, tabWidth, startIndex = 0) {
|
||||
let size = 0;
|
||||
for (let i = startIndex; i < value.length; ++i) {
|
||||
if (value[i] === "\t") {
|
||||
@@ -385,35 +381,22 @@ function getPreferredQuote(raw, preferredQuote) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function printString(raw, options, isDirectiveLiteral) {
|
||||
function printString(raw, options) {
|
||||
// `rawContent` is the string exactly like it appeared in the input source
|
||||
// code, without its enclosing quotes.
|
||||
const rawContent = raw.slice(1, -1);
|
||||
|
||||
// Check for the alternate quote, to determine if we're allowed to swap
|
||||
// the quotes on a DirectiveLiteral.
|
||||
const canChangeDirectiveQuotes =
|
||||
!rawContent.includes('"') && !rawContent.includes("'");
|
||||
|
||||
/** @type {Quote} */
|
||||
const enclosingQuote =
|
||||
options.parser === "json"
|
||||
options.parser === "json" ||
|
||||
(options.parser === "json5" &&
|
||||
options.quoteProps === "preserve" &&
|
||||
!options.singleQuote)
|
||||
? '"'
|
||||
: options.__isInHtmlAttribute
|
||||
? "'"
|
||||
: getPreferredQuote(raw, options.singleQuote ? "'" : '"');
|
||||
|
||||
// Directives are exact code unit sequences, which means that you can't
|
||||
// change the escape sequences they use.
|
||||
// See https://github.com/prettier/prettier/issues/1555
|
||||
// and https://tc39.github.io/ecma262/#directive-prologue
|
||||
if (isDirectiveLiteral) {
|
||||
if (canChangeDirectiveQuotes) {
|
||||
return enclosingQuote + rawContent + enclosingQuote;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
// It might sound unnecessary to use `makeString` even if the string already
|
||||
// is enclosed with `enclosingQuote`, but it isn't. The string could contain
|
||||
// unnecessary escapes (such as in `"\'"`). Always using `makeString` makes
|
||||
@@ -425,7 +408,7 @@ function printString(raw, options, isDirectiveLiteral) {
|
||||
options.parser === "css" ||
|
||||
options.parser === "less" ||
|
||||
options.parser === "scss" ||
|
||||
options.embeddedInHtml
|
||||
options.__embeddedInHtml
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -561,6 +544,7 @@ function addCommentHelper(node, comment) {
|
||||
const comments = node.comments || (node.comments = []);
|
||||
comments.push(comment);
|
||||
comment.printed = false;
|
||||
comment.nodeDescription = describeNodeForDebugging(node);
|
||||
}
|
||||
|
||||
function addLeadingComment(node, comment) {
|
||||
@@ -642,6 +626,23 @@ function createGroupIdMapper(description) {
|
||||
};
|
||||
}
|
||||
|
||||
function describeNodeForDebugging(node) {
|
||||
const nodeType = node.type || node.kind || "(unknown type)";
|
||||
let nodeName = String(
|
||||
node.name ||
|
||||
(node.id && (typeof node.id === "object" ? node.id.name : node.id)) ||
|
||||
(node.key && (typeof node.key === "object" ? node.key.name : node.key)) ||
|
||||
(node.value &&
|
||||
(typeof node.value === "object" ? "" : String(node.value))) ||
|
||||
node.operator ||
|
||||
""
|
||||
);
|
||||
if (nodeName.length > 20) {
|
||||
nodeName = nodeName.slice(0, 19) + "…";
|
||||
}
|
||||
return nodeType + (nodeName ? " " + nodeName : "");
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
inferParserByLanguage,
|
||||
replaceEndOfLineWith,
|
||||
|
||||
@@ -68,9 +68,9 @@ function getExplorer(opts) {
|
||||
function _resolveConfig(filePath, opts, sync) {
|
||||
opts = { useCache: true, ...opts };
|
||||
const loadOpts = {
|
||||
cache: !!opts.useCache,
|
||||
sync: !!sync,
|
||||
editorconfig: !!opts.editorconfig,
|
||||
cache: Boolean(opts.useCache),
|
||||
sync: Boolean(sync),
|
||||
editorconfig: Boolean(opts.editorconfig),
|
||||
};
|
||||
const { load, search } = getExplorer(loadOpts);
|
||||
const loadEditorConfig = resolveEditorConfig.getLoadFunction(loadOpts);
|
||||
@@ -154,9 +154,11 @@ function mergeOverrides(configResult, filePath) {
|
||||
}
|
||||
|
||||
// Based on eslint: https://github.com/eslint/eslint/blob/master/lib/config/config-ops.js
|
||||
function pathMatchesGlobs(filePath, patterns, excludedPatterns) {
|
||||
const patternList = [].concat(patterns);
|
||||
const excludedPatternList = [].concat(excludedPatterns || []);
|
||||
function pathMatchesGlobs(filePath, patterns, excludedPatterns = []) {
|
||||
const patternList = Array.isArray(patterns) ? patterns : [patterns];
|
||||
const excludedPatternList = Array.isArray(excludedPatterns)
|
||||
? excludedPatterns
|
||||
: [excludedPatterns];
|
||||
const opts = { matchBase: true, dot: true };
|
||||
|
||||
return (
|
||||
|
||||
@@ -86,9 +86,7 @@ function align(n, contents) {
|
||||
* @param {object} [opts] - TBD ???
|
||||
* @returns Doc
|
||||
*/
|
||||
function group(contents, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
function group(contents, opts = {}) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
assertDoc(contents);
|
||||
}
|
||||
@@ -97,7 +95,7 @@ function group(contents, opts) {
|
||||
type: "group",
|
||||
id: opts.id,
|
||||
contents,
|
||||
break: !!opts.shouldBreak,
|
||||
break: Boolean(opts.shouldBreak),
|
||||
expandedStates: opts.expandedStates,
|
||||
};
|
||||
}
|
||||
@@ -156,9 +154,7 @@ function fill(parts) {
|
||||
* @param {object} [opts] - TBD ???
|
||||
* @returns Doc
|
||||
*/
|
||||
function ifBreak(breakContents, flatContents, opts) {
|
||||
opts = opts || {};
|
||||
|
||||
function ifBreak(breakContents, flatContents, opts = {}) {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
if (breakContents) {
|
||||
assertDoc(breakContents);
|
||||
@@ -176,6 +172,21 @@ function ifBreak(breakContents, flatContents, opts) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized version of `ifBreak(indent(doc), doc, { groupId: ... })`
|
||||
* @param {Doc} contents
|
||||
* @param {{ groupId: symbol, negate?: boolean }} opts
|
||||
* @returns Doc
|
||||
*/
|
||||
function indentIfBreak(contents, opts) {
|
||||
return {
|
||||
type: "indent-if-break",
|
||||
contents,
|
||||
groupId: opts.groupId,
|
||||
negate: opts.negate,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Doc} contents
|
||||
* @returns Doc
|
||||
@@ -190,13 +201,21 @@ function lineSuffix(contents) {
|
||||
const lineSuffixBoundary = { type: "line-suffix-boundary" };
|
||||
const breakParent = { type: "break-parent" };
|
||||
const trim = { type: "trim" };
|
||||
|
||||
const hardlineWithoutBreakParent = { type: "line", hard: true };
|
||||
const literallineWithoutBreakParent = {
|
||||
type: "line",
|
||||
hard: true,
|
||||
literal: true,
|
||||
};
|
||||
|
||||
const line = { type: "line" };
|
||||
const softline = { type: "line", soft: true };
|
||||
const hardline = concat([{ type: "line", hard: true }, breakParent]);
|
||||
const literalline = concat([
|
||||
{ type: "line", hard: true, literal: true },
|
||||
breakParent,
|
||||
]);
|
||||
// eslint-disable-next-line prettier-internal-rules/no-doc-builder-concat
|
||||
const hardline = concat([hardlineWithoutBreakParent, breakParent]);
|
||||
// eslint-disable-next-line prettier-internal-rules/no-doc-builder-concat
|
||||
const literalline = concat([literallineWithoutBreakParent, breakParent]);
|
||||
|
||||
const cursor = { type: "cursor", placeholder: Symbol("cursor") };
|
||||
|
||||
/**
|
||||
@@ -215,6 +234,7 @@ function join(sep, arr) {
|
||||
res.push(arr[i]);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prettier-internal-rules/no-doc-builder-concat
|
||||
return concat(res);
|
||||
}
|
||||
|
||||
@@ -239,6 +259,10 @@ function addAlignmentToDoc(doc, size, tabWidth) {
|
||||
return aligned;
|
||||
}
|
||||
|
||||
function label(label, contents) {
|
||||
return { type: "label", label, contents };
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
concat,
|
||||
join,
|
||||
@@ -256,9 +280,13 @@ module.exports = {
|
||||
ifBreak,
|
||||
trim,
|
||||
indent,
|
||||
indentIfBreak,
|
||||
align,
|
||||
addAlignmentToDoc,
|
||||
markAsRoot,
|
||||
dedentToRoot,
|
||||
dedent,
|
||||
hardlineWithoutBreakParent,
|
||||
literallineWithoutBreakParent,
|
||||
label,
|
||||
};
|
||||
+183
-104
@@ -3,15 +3,17 @@
|
||||
const { isConcat, getDocParts } = require("./doc-utils");
|
||||
|
||||
function flattenDoc(doc) {
|
||||
if (!doc) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (isConcat(doc)) {
|
||||
const res = [];
|
||||
const parts = getDocParts(doc);
|
||||
for (let i = 0; i < parts.length; ++i) {
|
||||
const doc2 = parts[i];
|
||||
if (typeof doc2 !== "string" && isConcat(doc2)) {
|
||||
res.push(...flattenDoc(doc2).parts);
|
||||
for (const part of getDocParts(doc)) {
|
||||
if (isConcat(part)) {
|
||||
res.push(...flattenDoc(part).parts);
|
||||
} else {
|
||||
const flattened = flattenDoc(doc2);
|
||||
const flattened = flattenDoc(part);
|
||||
if (flattened !== "") {
|
||||
res.push(flattened);
|
||||
}
|
||||
@@ -19,116 +21,193 @@ function flattenDoc(doc) {
|
||||
}
|
||||
|
||||
return { type: "concat", parts: res };
|
||||
} else if (doc.type === "if-break") {
|
||||
return {
|
||||
...doc,
|
||||
breakContents:
|
||||
doc.breakContents != null ? flattenDoc(doc.breakContents) : null,
|
||||
flatContents:
|
||||
doc.flatContents != null ? flattenDoc(doc.flatContents) : null,
|
||||
};
|
||||
} else if (doc.type === "group") {
|
||||
return {
|
||||
...doc,
|
||||
contents: flattenDoc(doc.contents),
|
||||
expandedStates: doc.expandedStates
|
||||
? doc.expandedStates.map(flattenDoc)
|
||||
: doc.expandedStates,
|
||||
};
|
||||
} else if (doc.contents) {
|
||||
return { ...doc, contents: flattenDoc(doc.contents) };
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
function printDoc(doc) {
|
||||
if (typeof doc === "string") {
|
||||
return JSON.stringify(doc);
|
||||
}
|
||||
|
||||
if (isConcat(doc)) {
|
||||
return "[" + getDocParts(doc).map(printDoc).join(", ") + "]";
|
||||
}
|
||||
|
||||
if (doc.type === "line") {
|
||||
if (doc.literal) {
|
||||
return "literalline";
|
||||
}
|
||||
if (doc.hard) {
|
||||
return "hardline";
|
||||
}
|
||||
if (doc.soft) {
|
||||
return "softline";
|
||||
}
|
||||
return "line";
|
||||
}
|
||||
|
||||
if (doc.type === "break-parent") {
|
||||
return "breakParent";
|
||||
}
|
||||
|
||||
if (doc.type === "trim") {
|
||||
return "trim";
|
||||
}
|
||||
|
||||
if (doc.type === "indent") {
|
||||
return "indent(" + printDoc(doc.contents) + ")";
|
||||
}
|
||||
|
||||
if (doc.type === "align") {
|
||||
return doc.n === Number.NEGATIVE_INFINITY
|
||||
? "dedentToRoot(" + printDoc(doc.contents) + ")"
|
||||
: doc.n < 0
|
||||
? "dedent(" + printDoc(doc.contents) + ")"
|
||||
: doc.n.type === "root"
|
||||
? "markAsRoot(" + printDoc(doc.contents) + ")"
|
||||
: "align(" + JSON.stringify(doc.n) + ", " + printDoc(doc.contents) + ")";
|
||||
}
|
||||
|
||||
if (doc.type === "if-break") {
|
||||
return (
|
||||
"ifBreak(" +
|
||||
printDoc(doc.breakContents) +
|
||||
(doc.flatContents ? ", " + printDoc(doc.flatContents) : "") +
|
||||
")"
|
||||
);
|
||||
return {
|
||||
...doc,
|
||||
breakContents: flattenDoc(doc.breakContents),
|
||||
flatContents: flattenDoc(doc.flatContents),
|
||||
};
|
||||
}
|
||||
|
||||
if (doc.type === "group") {
|
||||
if (doc.expandedStates) {
|
||||
return (
|
||||
"conditionalGroup(" +
|
||||
"[" +
|
||||
doc.expandedStates.map(printDoc).join(",") +
|
||||
"])"
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
(doc.break ? "wrappedGroup" : "group") +
|
||||
"(" +
|
||||
printDoc(doc.contents) +
|
||||
")"
|
||||
);
|
||||
return {
|
||||
...doc,
|
||||
contents: flattenDoc(doc.contents),
|
||||
expandedStates: doc.expandedStates && doc.expandedStates.map(flattenDoc),
|
||||
};
|
||||
}
|
||||
|
||||
if (doc.type === "fill") {
|
||||
return "fill" + "(" + doc.parts.map(printDoc).join(", ") + ")";
|
||||
return { type: "fill", parts: doc.parts.map(flattenDoc) };
|
||||
}
|
||||
|
||||
if (doc.type === "line-suffix") {
|
||||
return "lineSuffix(" + printDoc(doc.contents) + ")";
|
||||
if (doc.contents) {
|
||||
return { ...doc, contents: flattenDoc(doc.contents) };
|
||||
}
|
||||
|
||||
if (doc.type === "line-suffix-boundary") {
|
||||
return "lineSuffixBoundary";
|
||||
}
|
||||
|
||||
throw new Error("Unknown doc type " + doc.type);
|
||||
return doc;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
printDocToDebug(doc) {
|
||||
return printDoc(flattenDoc(doc));
|
||||
},
|
||||
};
|
||||
function printDocToDebug(doc) {
|
||||
/** @type Record<symbol, string> */
|
||||
const printedSymbols = Object.create(null);
|
||||
/** @type Set<string> */
|
||||
const usedKeysForSymbols = new Set();
|
||||
return printDoc(flattenDoc(doc));
|
||||
|
||||
function printDoc(doc, index, parentParts) {
|
||||
if (typeof doc === "string") {
|
||||
return JSON.stringify(doc);
|
||||
}
|
||||
|
||||
if (isConcat(doc)) {
|
||||
const printed = getDocParts(doc).map(printDoc).filter(Boolean);
|
||||
return printed.length === 1 ? printed[0] : `[${printed.join(", ")}]`;
|
||||
}
|
||||
|
||||
if (doc.type === "line") {
|
||||
const withBreakParent =
|
||||
Array.isArray(parentParts) &&
|
||||
parentParts[index + 1] &&
|
||||
parentParts[index + 1].type === "break-parent";
|
||||
if (doc.literal) {
|
||||
return withBreakParent
|
||||
? "literalline"
|
||||
: "literallineWithoutBreakParent";
|
||||
}
|
||||
if (doc.hard) {
|
||||
return withBreakParent ? "hardline" : "hardlineWithoutBreakParent";
|
||||
}
|
||||
if (doc.soft) {
|
||||
return "softline";
|
||||
}
|
||||
return "line";
|
||||
}
|
||||
|
||||
if (doc.type === "break-parent") {
|
||||
const afterHardline =
|
||||
Array.isArray(parentParts) &&
|
||||
parentParts[index - 1] &&
|
||||
parentParts[index - 1].type === "line" &&
|
||||
parentParts[index - 1].hard;
|
||||
return afterHardline ? undefined : "breakParent";
|
||||
}
|
||||
|
||||
if (doc.type === "trim") {
|
||||
return "trim";
|
||||
}
|
||||
|
||||
if (doc.type === "indent") {
|
||||
return "indent(" + printDoc(doc.contents) + ")";
|
||||
}
|
||||
|
||||
if (doc.type === "align") {
|
||||
return doc.n === Number.NEGATIVE_INFINITY
|
||||
? "dedentToRoot(" + printDoc(doc.contents) + ")"
|
||||
: doc.n < 0
|
||||
? "dedent(" + printDoc(doc.contents) + ")"
|
||||
: doc.n.type === "root"
|
||||
? "markAsRoot(" + printDoc(doc.contents) + ")"
|
||||
: "align(" +
|
||||
JSON.stringify(doc.n) +
|
||||
", " +
|
||||
printDoc(doc.contents) +
|
||||
")";
|
||||
}
|
||||
|
||||
if (doc.type === "if-break") {
|
||||
return (
|
||||
"ifBreak(" +
|
||||
printDoc(doc.breakContents) +
|
||||
(doc.flatContents ? ", " + printDoc(doc.flatContents) : "") +
|
||||
(doc.groupId
|
||||
? (!doc.flatContents ? ', ""' : "") +
|
||||
`, { groupId: ${printGroupId(doc.groupId)} }`
|
||||
: "") +
|
||||
")"
|
||||
);
|
||||
}
|
||||
|
||||
if (doc.type === "indent-if-break") {
|
||||
const optionsParts = [];
|
||||
|
||||
if (doc.negate) {
|
||||
optionsParts.push("negate: true");
|
||||
}
|
||||
|
||||
if (doc.groupId) {
|
||||
optionsParts.push(`groupId: ${printGroupId(doc.groupId)}`);
|
||||
}
|
||||
|
||||
const options =
|
||||
optionsParts.length > 0 ? `, { ${optionsParts.join(", ")} }` : "";
|
||||
|
||||
return `indentIfBreak(${printDoc(doc.contents)}${options})`;
|
||||
}
|
||||
|
||||
if (doc.type === "group") {
|
||||
const optionsParts = [];
|
||||
|
||||
if (doc.break && doc.break !== "propagated") {
|
||||
optionsParts.push("shouldBreak: true");
|
||||
}
|
||||
|
||||
if (doc.id) {
|
||||
optionsParts.push(`id: ${printGroupId(doc.id)}`);
|
||||
}
|
||||
|
||||
const options =
|
||||
optionsParts.length > 0 ? `, { ${optionsParts.join(", ")} }` : "";
|
||||
|
||||
if (doc.expandedStates) {
|
||||
return `conditionalGroup([${doc.expandedStates
|
||||
.map((part) => printDoc(part))
|
||||
.join(",")}]${options})`;
|
||||
}
|
||||
|
||||
return `group(${printDoc(doc.contents)}${options})`;
|
||||
}
|
||||
|
||||
if (doc.type === "fill") {
|
||||
return `fill([${doc.parts.map((part) => printDoc(part)).join(", ")}])`;
|
||||
}
|
||||
|
||||
if (doc.type === "line-suffix") {
|
||||
return "lineSuffix(" + printDoc(doc.contents) + ")";
|
||||
}
|
||||
|
||||
if (doc.type === "line-suffix-boundary") {
|
||||
return "lineSuffixBoundary";
|
||||
}
|
||||
|
||||
if (doc.type === "label") {
|
||||
return `label(${JSON.stringify(doc.label)}, ${printDoc(doc.contents)})`;
|
||||
}
|
||||
|
||||
throw new Error("Unknown doc type " + doc.type);
|
||||
}
|
||||
|
||||
function printGroupId(id) {
|
||||
if (typeof id !== "symbol") {
|
||||
return JSON.stringify(String(id));
|
||||
}
|
||||
|
||||
if (id in printedSymbols) {
|
||||
return printedSymbols[id];
|
||||
}
|
||||
|
||||
// TODO: use Symbol.prototype.description instead of slice once Node 10 is dropped
|
||||
const prefix = String(id).slice(7, -1) || "symbol";
|
||||
for (let counter = 0; ; counter++) {
|
||||
const key = prefix + (counter > 0 ? ` #${counter}` : "");
|
||||
if (!usedKeysForSymbols.has(key)) {
|
||||
usedKeysForSymbols.add(key);
|
||||
return (printedSymbols[id] = `Symbol.for(${JSON.stringify(key)})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { printDocToDebug };
|
||||
+79
-21
@@ -1,8 +1,8 @@
|
||||
"use strict";
|
||||
|
||||
const { getStringWidth } = require("../common/util");
|
||||
const { getStringWidth, getLast } = require("../common/util");
|
||||
const { convertEndOfLineToChars } = require("../common/end-of-line");
|
||||
const { concat, fill, cursor } = require("./doc-builders");
|
||||
const { fill, cursor, indent } = require("./doc-builders");
|
||||
const { isConcat, getDocParts } = require("./doc-utils");
|
||||
|
||||
/** @type {Record<symbol, typeof MODE_BREAK | typeof MODE_FLAT>} */
|
||||
@@ -45,7 +45,7 @@ function generateInd(ind, newPart, options) {
|
||||
const queue =
|
||||
newPart.type === "dedent"
|
||||
? ind.queue.slice(0, -1)
|
||||
: ind.queue.concat(newPart);
|
||||
: [...ind.queue, newPart];
|
||||
|
||||
let value = "";
|
||||
let length = 0;
|
||||
@@ -144,7 +144,7 @@ function trim(out) {
|
||||
return trimCount;
|
||||
}
|
||||
|
||||
function fits(next, restCommands, width, options, mustBeFlat) {
|
||||
function fits(next, restCommands, width, options, hasLineSuffix, mustBeFlat) {
|
||||
let restIdx = restCommands.length;
|
||||
const cmds = [next];
|
||||
// `out` is only used for width counting because `trim` requires to look
|
||||
@@ -187,32 +187,54 @@ function fits(next, restCommands, width, options, mustBeFlat) {
|
||||
width += trim(out);
|
||||
|
||||
break;
|
||||
case "group":
|
||||
case "group": {
|
||||
if (mustBeFlat && doc.break) {
|
||||
return false;
|
||||
}
|
||||
cmds.push([ind, doc.break ? MODE_BREAK : mode, doc.contents]);
|
||||
const groupMode = doc.break ? MODE_BREAK : mode;
|
||||
cmds.push([
|
||||
ind,
|
||||
groupMode,
|
||||
// The most expanded state takes up the least space on the current line.
|
||||
doc.expandedStates && groupMode === MODE_BREAK
|
||||
? getLast(doc.expandedStates)
|
||||
: doc.contents,
|
||||
]);
|
||||
|
||||
if (doc.id) {
|
||||
groupModeMap[doc.id] = cmds[cmds.length - 1][1];
|
||||
groupModeMap[doc.id] = groupMode;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "fill":
|
||||
for (let i = doc.parts.length - 1; i >= 0; i--) {
|
||||
cmds.push([ind, mode, doc.parts[i]]);
|
||||
}
|
||||
|
||||
break;
|
||||
case "if-break": {
|
||||
case "if-break":
|
||||
case "indent-if-break": {
|
||||
const groupMode = doc.groupId ? groupModeMap[doc.groupId] : mode;
|
||||
if (groupMode === MODE_BREAK) {
|
||||
if (doc.breakContents) {
|
||||
cmds.push([ind, mode, doc.breakContents]);
|
||||
const breakContents =
|
||||
doc.type === "if-break"
|
||||
? doc.breakContents
|
||||
: doc.negate
|
||||
? doc.contents
|
||||
: indent(doc.contents);
|
||||
if (breakContents) {
|
||||
cmds.push([ind, mode, breakContents]);
|
||||
}
|
||||
}
|
||||
if (groupMode === MODE_FLAT) {
|
||||
if (doc.flatContents) {
|
||||
cmds.push([ind, mode, doc.flatContents]);
|
||||
const flatContents =
|
||||
doc.type === "if-break"
|
||||
? doc.flatContents
|
||||
: doc.negate
|
||||
? indent(doc.contents)
|
||||
: doc.contents;
|
||||
if (flatContents) {
|
||||
cmds.push([ind, mode, flatContents]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +259,17 @@ function fits(next, restCommands, width, options, mustBeFlat) {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case "line-suffix":
|
||||
hasLineSuffix = true;
|
||||
break;
|
||||
case "line-suffix-boundary":
|
||||
if (hasLineSuffix) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case "label":
|
||||
cmds.push([ind, mode, doc.contents]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -306,8 +339,9 @@ function printDocToString(doc, options) {
|
||||
|
||||
const next = [ind, MODE_FLAT, doc.contents];
|
||||
const rem = width - pos;
|
||||
const hasLineSuffix = lineSuffix.length > 0;
|
||||
|
||||
if (!doc.break && fits(next, cmds, rem, options)) {
|
||||
if (!doc.break && fits(next, cmds, rem, options, hasLineSuffix)) {
|
||||
cmds.push(next);
|
||||
} else {
|
||||
// Expanded states are a rare case where a document
|
||||
@@ -335,7 +369,7 @@ function printDocToString(doc, options) {
|
||||
const state = doc.expandedStates[i];
|
||||
const cmd = [ind, MODE_FLAT, state];
|
||||
|
||||
if (fits(cmd, cmds, rem, options)) {
|
||||
if (fits(cmd, cmds, rem, options, hasLineSuffix)) {
|
||||
cmds.push(cmd);
|
||||
|
||||
break;
|
||||
@@ -387,7 +421,14 @@ function printDocToString(doc, options) {
|
||||
const [content, whitespace] = parts;
|
||||
const contentFlatCmd = [ind, MODE_FLAT, content];
|
||||
const contentBreakCmd = [ind, MODE_BREAK, content];
|
||||
const contentFits = fits(contentFlatCmd, [], rem, options, true);
|
||||
const contentFits = fits(
|
||||
contentFlatCmd,
|
||||
[],
|
||||
rem,
|
||||
options,
|
||||
lineSuffix.length > 0,
|
||||
true
|
||||
);
|
||||
|
||||
if (parts.length === 1) {
|
||||
if (contentFits) {
|
||||
@@ -423,13 +464,14 @@ function printDocToString(doc, options) {
|
||||
const firstAndSecondContentFlatCmd = [
|
||||
ind,
|
||||
MODE_FLAT,
|
||||
concat([content, whitespace, secondContent]),
|
||||
[content, whitespace, secondContent],
|
||||
];
|
||||
const firstAndSecondContentFits = fits(
|
||||
firstAndSecondContentFlatCmd,
|
||||
[],
|
||||
rem,
|
||||
options,
|
||||
lineSuffix.length > 0,
|
||||
true
|
||||
);
|
||||
|
||||
@@ -442,16 +484,29 @@ function printDocToString(doc, options) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "if-break": {
|
||||
case "if-break":
|
||||
case "indent-if-break": {
|
||||
const groupMode = doc.groupId ? groupModeMap[doc.groupId] : mode;
|
||||
if (groupMode === MODE_BREAK) {
|
||||
if (doc.breakContents) {
|
||||
cmds.push([ind, mode, doc.breakContents]);
|
||||
const breakContents =
|
||||
doc.type === "if-break"
|
||||
? doc.breakContents
|
||||
: doc.negate
|
||||
? doc.contents
|
||||
: indent(doc.contents);
|
||||
if (breakContents) {
|
||||
cmds.push([ind, mode, breakContents]);
|
||||
}
|
||||
}
|
||||
if (groupMode === MODE_FLAT) {
|
||||
if (doc.flatContents) {
|
||||
cmds.push([ind, mode, doc.flatContents]);
|
||||
const flatContents =
|
||||
doc.type === "if-break"
|
||||
? doc.flatContents
|
||||
: doc.negate
|
||||
? indent(doc.contents)
|
||||
: doc.contents;
|
||||
if (flatContents) {
|
||||
cmds.push([ind, mode, flatContents]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -510,6 +565,9 @@ function printDocToString(doc, options) {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case "label":
|
||||
cmds.push([ind, mode, doc.contents]);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
+35
-33
@@ -1,9 +1,20 @@
|
||||
"use strict";
|
||||
|
||||
const { literalline, concat } = require("./doc-builders");
|
||||
const { literalline } = require("./doc-builders");
|
||||
|
||||
const isConcat = (doc) => Array.isArray(doc) || (doc && doc.type === "concat");
|
||||
const getDocParts = (doc) => (Array.isArray(doc) ? doc : doc.parts);
|
||||
const getDocParts = (doc) => {
|
||||
if (Array.isArray(doc)) {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/* istanbul ignore next */
|
||||
if (doc.type !== "concat" && doc.type !== "fill") {
|
||||
throw new Error("Expect doc type to be `concat` or `fill`.");
|
||||
}
|
||||
|
||||
return doc.parts;
|
||||
};
|
||||
|
||||
// Using a unique object to compare by reference.
|
||||
const traverseDocOnExitStackMarker = {};
|
||||
@@ -62,14 +73,20 @@ function traverseDoc(doc, onEnter, onExit, shouldTraverseConditionalGroups) {
|
||||
function mapDoc(doc, cb) {
|
||||
if (Array.isArray(doc)) {
|
||||
return cb(doc.map((part) => mapDoc(part, cb)));
|
||||
} else if (doc.type === "concat" || doc.type === "fill") {
|
||||
}
|
||||
|
||||
if (doc.type === "concat" || doc.type === "fill") {
|
||||
const parts = doc.parts.map((part) => mapDoc(part, cb));
|
||||
return cb({ ...doc, parts });
|
||||
} else if (doc.type === "if-break") {
|
||||
}
|
||||
|
||||
if (doc.type === "if-break") {
|
||||
const breakContents = doc.breakContents && mapDoc(doc.breakContents, cb);
|
||||
const flatContents = doc.flatContents && mapDoc(doc.flatContents, cb);
|
||||
return cb({ ...doc, breakContents, flatContents });
|
||||
} else if (doc.contents) {
|
||||
}
|
||||
|
||||
if (doc.contents) {
|
||||
const contents = mapDoc(doc.contents, cb);
|
||||
return cb({ ...doc, contents });
|
||||
}
|
||||
@@ -93,23 +110,6 @@ function findInDoc(doc, fn, defaultValue) {
|
||||
return result;
|
||||
}
|
||||
|
||||
function isEmpty(n) {
|
||||
return typeof n === "string" && n.length === 0;
|
||||
}
|
||||
|
||||
function isLineNextFn(doc) {
|
||||
if (typeof doc === "string") {
|
||||
return false;
|
||||
}
|
||||
if (doc.type === "line") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isLineNext(doc) {
|
||||
return findInDoc(doc, isLineNextFn, false);
|
||||
}
|
||||
|
||||
function willBreakFn(doc) {
|
||||
if (doc.type === "group" && doc.break) {
|
||||
return true;
|
||||
@@ -131,8 +131,10 @@ function breakParentGroup(groupStack) {
|
||||
const parentGroup = groupStack[groupStack.length - 1];
|
||||
// Breaks are not propagated through conditional groups because
|
||||
// the user is expected to manually handle what breaks.
|
||||
if (!parentGroup.expandedStates) {
|
||||
parentGroup.break = true;
|
||||
if (!parentGroup.expandedStates && !parentGroup.break) {
|
||||
// An alternative truthy value allows to distinguish propagated group breaks
|
||||
// and not to print them as `group(..., { break: true })` in `--debug-print-doc`.
|
||||
parentGroup.break = "propagated";
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@@ -176,9 +178,12 @@ function removeLinesFn(doc) {
|
||||
// of breaking existing assumptions otherwise.
|
||||
if (doc.type === "line" && !doc.hard) {
|
||||
return doc.soft ? "" : " ";
|
||||
} else if (doc.type === "if-break") {
|
||||
}
|
||||
|
||||
if (doc.type === "if-break") {
|
||||
return doc.flatContents || "";
|
||||
}
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
@@ -214,8 +219,10 @@ function stripDocTrailingHardlineFromDoc(doc) {
|
||||
switch (doc.type) {
|
||||
case "align":
|
||||
case "indent":
|
||||
case "indent-if-break":
|
||||
case "group":
|
||||
case "line-suffix": {
|
||||
case "line-suffix":
|
||||
case "label": {
|
||||
const contents = stripDocTrailingHardlineFromDoc(doc.contents);
|
||||
return { ...doc, contents };
|
||||
}
|
||||
@@ -257,6 +264,7 @@ function cleanDocFn(doc) {
|
||||
break;
|
||||
case "align":
|
||||
case "indent":
|
||||
case "indent-if-break":
|
||||
case "line-suffix":
|
||||
if (!doc.contents) {
|
||||
return "";
|
||||
@@ -358,11 +366,7 @@ function normalizeDoc(doc) {
|
||||
function replaceNewlinesWithLiterallines(doc) {
|
||||
return mapDoc(doc, (currentDoc) =>
|
||||
typeof currentDoc === "string" && currentDoc.includes("\n")
|
||||
? concat(
|
||||
currentDoc
|
||||
.split(/(\n)/g)
|
||||
.map((v, i) => (i % 2 === 0 ? v : literalline))
|
||||
)
|
||||
? currentDoc.split(/(\n)/g).map((v, i) => (i % 2 === 0 ? v : literalline))
|
||||
: currentDoc
|
||||
);
|
||||
}
|
||||
@@ -370,9 +374,7 @@ function replaceNewlinesWithLiterallines(doc) {
|
||||
module.exports = {
|
||||
isConcat,
|
||||
getDocParts,
|
||||
isEmpty,
|
||||
willBreak,
|
||||
isLineNext,
|
||||
traverseDoc,
|
||||
findInDoc,
|
||||
mapDoc,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
const {
|
||||
builders: { hardline },
|
||||
} = require("../document");
|
||||
const { print: printFrontMatter } = require("../utils/front-matter");
|
||||
const printFrontMatter = require("../utils/front-matter/print");
|
||||
|
||||
function embed(path, print, textToDoc /*, options */) {
|
||||
const node = path.getValue();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const createError = require("../common/parser-create-error");
|
||||
const { parse: parseFrontMatter } = require("../utils/front-matter");
|
||||
const parseFrontMatter = require("../utils/front-matter/parse");
|
||||
const { hasPragma } = require("./pragma");
|
||||
const {
|
||||
hasSCSSInterpolation,
|
||||
@@ -71,7 +71,7 @@ function parseValueNode(valueNode, options) {
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const group = groups[i];
|
||||
if (group.type === "comma_group") {
|
||||
groupList = groupList.concat(group.groups);
|
||||
groupList = [...groupList, ...group.groups];
|
||||
} else {
|
||||
groupList.push(group);
|
||||
}
|
||||
@@ -212,7 +212,7 @@ function parseValue(value, options) {
|
||||
|
||||
try {
|
||||
result = valueParser(value, { loose: true }).parse();
|
||||
} catch (e) {
|
||||
} catch {
|
||||
return {
|
||||
type: "value-unknown",
|
||||
value,
|
||||
@@ -246,7 +246,7 @@ function parseSelector(selector) {
|
||||
selectorParser((result_) => {
|
||||
result = result_;
|
||||
}).process(selector);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Fail silently. It's better to print it as is than to try and parse it
|
||||
// Note: A common failure is for SCSS nested properties. `background:
|
||||
// none { color: red; }` is parsed as a NestedDeclaration by
|
||||
@@ -269,7 +269,7 @@ function parseMediaQuery(params) {
|
||||
|
||||
try {
|
||||
result = mediaParser(params);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Ignore bad media queries
|
||||
/* istanbul ignore next */
|
||||
return {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
const jsPragma = require("../language-js/pragma");
|
||||
const { parse: parseFrontMatter } = require("../utils/front-matter");
|
||||
const parseFrontMatter = require("../utils/front-matter/parse");
|
||||
|
||||
function hasPragma(text) {
|
||||
return jsPragma.hasPragma(parseFrontMatter(text).content);
|
||||
|
||||
@@ -55,6 +55,7 @@ const {
|
||||
hasParensAroundNode,
|
||||
hasEmptyRawBefore,
|
||||
isKeyValuePairNode,
|
||||
isKeyInValuePairNode,
|
||||
isDetachedRulesetCallNode,
|
||||
isTemplatePlaceholderNode,
|
||||
isTemplatePropNode,
|
||||
@@ -187,7 +188,7 @@ function genericPrint(path, options, print) {
|
||||
options.originalText[locEnd(node) - 1] !== ";"
|
||||
? ""
|
||||
: options.__isHTMLStyleAttribute && isLastNode(path, node)
|
||||
? ifBreak(";", "")
|
||||
? ifBreak(";")
|
||||
: ";",
|
||||
];
|
||||
}
|
||||
@@ -865,8 +866,9 @@ function genericPrint(path, options, print) {
|
||||
|
||||
const lastItem = node.groups[node.groups.length - 1];
|
||||
const isLastItemComment = lastItem && lastItem.type === "value-comment";
|
||||
const isKey = isKeyInValuePairNode(node, parentNode);
|
||||
|
||||
return group(
|
||||
const printed = group(
|
||||
[
|
||||
node.open ? path.call(print, "open") : "",
|
||||
indent([
|
||||
@@ -882,6 +884,7 @@ function genericPrint(path, options, print) {
|
||||
isKeyValuePairNode(node) &&
|
||||
node.type === "value-comma_group" &&
|
||||
node.groups &&
|
||||
node.groups[0].type !== "value-paren_group" &&
|
||||
node.groups[2] &&
|
||||
node.groups[2].type === "value-paren_group"
|
||||
) {
|
||||
@@ -907,9 +910,11 @@ function genericPrint(path, options, print) {
|
||||
node.close ? path.call(print, "close") : "",
|
||||
],
|
||||
{
|
||||
shouldBreak: isSCSSMapItem,
|
||||
shouldBreak: isSCSSMapItem && !isKey,
|
||||
}
|
||||
);
|
||||
|
||||
return isKey ? dedent(printed) : printed;
|
||||
}
|
||||
case "value-func": {
|
||||
return [
|
||||
@@ -943,7 +948,9 @@ function genericPrint(path, options, print) {
|
||||
return [
|
||||
node.value,
|
||||
// Don't add spaces on escaped colon `:`, e.g: grid-template-rows: [row-1-00\:00] auto;
|
||||
(prevNode && prevNode.value[prevNode.value.length - 1] === "\\") ||
|
||||
(prevNode &&
|
||||
typeof prevNode.value === "string" &&
|
||||
prevNode.value[prevNode.value.length - 1] === "\\") ||
|
||||
// Don't add spaces on `:` in `url` function (i.e. `url(fbglyph: cross-outline, fig-white)`)
|
||||
insideValueFunctionNode(path, "url")
|
||||
? ""
|
||||
|
||||
@@ -30,7 +30,7 @@ const colorAdjusterFunctions = new Set([
|
||||
]);
|
||||
|
||||
function getAncestorCounter(path, typeOrTypes) {
|
||||
const types = [].concat(typeOrTypes);
|
||||
const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes];
|
||||
|
||||
let counter = -1;
|
||||
let ancestorNode;
|
||||
@@ -146,7 +146,9 @@ function insideICSSRuleNode(path) {
|
||||
}
|
||||
|
||||
function insideAtRuleNode(path, atRuleNameOrAtRuleNames) {
|
||||
const atRuleNames = [].concat(atRuleNameOrAtRuleNames);
|
||||
const atRuleNames = Array.isArray(atRuleNameOrAtRuleNames)
|
||||
? atRuleNameOrAtRuleNames
|
||||
: [atRuleNameOrAtRuleNames];
|
||||
const atRuleAncestorNode = getAncestorNode(path, "css-atrule");
|
||||
|
||||
return (
|
||||
@@ -407,7 +409,22 @@ function isWordNode(node) {
|
||||
}
|
||||
|
||||
function isColonNode(node) {
|
||||
return node.type === "value-colon";
|
||||
return node && node.type === "value-colon";
|
||||
}
|
||||
|
||||
function isKeyInValuePairNode(node, parentNode) {
|
||||
if (!isKeyValuePairNode(parentNode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { groups } = parentNode;
|
||||
const index = groups.indexOf(node);
|
||||
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isColonNode(groups[index + 1]);
|
||||
}
|
||||
|
||||
function isMediaAndSupportsKeywords(node) {
|
||||
@@ -502,6 +519,7 @@ module.exports = {
|
||||
isPostcssSimpleVarNode,
|
||||
isKeyValuePairNode,
|
||||
isKeyValuePairInParenGroupNode,
|
||||
isKeyInValuePairNode,
|
||||
isSCSSMapItemNode,
|
||||
isInlineValueCommentNode,
|
||||
isHashNode,
|
||||
|
||||
@@ -35,7 +35,7 @@ function genericPrint(path, options, print) {
|
||||
}
|
||||
case "OperationDefinition": {
|
||||
const hasOperation = options.originalText[locStart(n)] !== "{";
|
||||
const hasName = !!n.name;
|
||||
const hasName = Boolean(n.name);
|
||||
return [
|
||||
hasOperation ? n.operation : "",
|
||||
hasOperation && hasName ? [" ", path.call(print, "name")] : "",
|
||||
|
||||
@@ -1,15 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
function clean(ast, newNode /*, parent*/) {
|
||||
// (Glimmer/HTML) ignore TextNode whitespace
|
||||
// (Glimmer/HTML) ignore TextNode
|
||||
if (ast.type === "TextNode") {
|
||||
const trimmed = ast.chars.trim();
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
newNode.chars = trimmed;
|
||||
newNode.chars = trimmed.replace(/[\t\n\f\r ]+/g, " ");
|
||||
}
|
||||
|
||||
// `class` is reformatted
|
||||
if (ast.type === "AttrNode" && ast.name.toLowerCase() === "class") {
|
||||
delete newNode.value;
|
||||
}
|
||||
}
|
||||
|
||||
clean.ignoredProperties = new Set(["loc", "selfClosing"]);
|
||||
|
||||
module.exports = clean;
|
||||
@@ -1,11 +1,21 @@
|
||||
"use strict";
|
||||
|
||||
const {
|
||||
builders: { dedent, group, hardline, ifBreak, indent, join, line, softline },
|
||||
builders: {
|
||||
dedent,
|
||||
fill,
|
||||
group,
|
||||
hardline,
|
||||
ifBreak,
|
||||
indent,
|
||||
join,
|
||||
line,
|
||||
softline,
|
||||
literalline,
|
||||
},
|
||||
utils: { getDocParts },
|
||||
} = require("../document");
|
||||
const { isNonEmptyArray } = require("../common/util");
|
||||
|
||||
const { isNonEmptyArray, replaceEndOfLineWith } = require("../common/util");
|
||||
const { locStart, locEnd } = require("./loc");
|
||||
const clean = require("./clean");
|
||||
const {
|
||||
@@ -21,6 +31,8 @@ const {
|
||||
isWhitespaceNode,
|
||||
} = require("./utils");
|
||||
|
||||
const NEWLINES_TO_PRESERVE_MAX = 2;
|
||||
|
||||
// Formatter based on @glimmerjs/syntax's built-in test formatter:
|
||||
// https://github.com/glimmerjs/glimmer-vm/blob/master/packages/%40glimmer/syntax/lib/generation/print.ts
|
||||
|
||||
@@ -47,7 +59,7 @@ function print(path, options, print) {
|
||||
const startingTag = group(printStartingTag(path, print));
|
||||
|
||||
const escapeNextElementNode =
|
||||
options.htmlWhitespaceSensitivity !== "strict" &&
|
||||
options.htmlWhitespaceSensitivity === "ignore" &&
|
||||
isNextNodeOfSomeType(path, ["ElementNode"])
|
||||
? softline
|
||||
: "";
|
||||
@@ -62,7 +74,7 @@ function print(path, options, print) {
|
||||
return [startingTag, indent(endingTag), escapeNextElementNode];
|
||||
}
|
||||
|
||||
if (options.htmlWhitespaceSensitivity !== "strict") {
|
||||
if (options.htmlWhitespaceSensitivity === "ignore") {
|
||||
return [
|
||||
startingTag,
|
||||
indent(printChildren(path, options, print)),
|
||||
@@ -152,23 +164,34 @@ function print(path, options, print) {
|
||||
if (isEmptyText && locStart(n.value) === locEnd(n.value)) {
|
||||
return n.name;
|
||||
}
|
||||
const value = path.call(print, "value");
|
||||
const quotedValue = isText
|
||||
? printStringLiteral(
|
||||
typeof value === "string" ? value : getDocParts(value).join(),
|
||||
options
|
||||
)
|
||||
: value;
|
||||
return [n.name, "=", quotedValue];
|
||||
|
||||
// Let's assume quotes inside the content of text nodes are already
|
||||
// properly escaped with entities, otherwise the parse wouldn't have parsed them.
|
||||
const quote = isText
|
||||
? chooseEnclosingQuote(options, n.value.chars).quote
|
||||
: n.value.type === "ConcatStatement"
|
||||
? chooseEnclosingQuote(
|
||||
options,
|
||||
n.value.parts
|
||||
.filter((part) => part.type === "TextNode")
|
||||
.map((part) => part.chars)
|
||||
.join("")
|
||||
).quote
|
||||
: "";
|
||||
|
||||
const valueDoc = path.call(print, "value");
|
||||
|
||||
return [
|
||||
n.name,
|
||||
"=",
|
||||
quote,
|
||||
n.name === "class" && quote ? group(indent(valueDoc)) : valueDoc,
|
||||
quote,
|
||||
];
|
||||
}
|
||||
|
||||
case "ConcatStatement": {
|
||||
const quote = options.singleQuote ? "'" : '"';
|
||||
return [
|
||||
quote,
|
||||
...path.map((partPath) => print(partPath), "parts"),
|
||||
quote,
|
||||
];
|
||||
return path.map(print, "parts");
|
||||
}
|
||||
|
||||
case "Hash": {
|
||||
@@ -178,20 +201,74 @@ function print(path, options, print) {
|
||||
return [n.key, "=", path.call(print, "value")];
|
||||
}
|
||||
case "TextNode": {
|
||||
const inAttrNode = path.stack.includes("attributes");
|
||||
/* if `{{my-component}}` (or any text containing "{{")
|
||||
* makes it to the TextNode, it means it was escaped,
|
||||
* so let's print it escaped, ie.; `\{{my-component}}` */
|
||||
let text = n.chars.replace(/{{/g, "\\{{");
|
||||
|
||||
if (options.htmlWhitespaceSensitivity === "strict" && !inAttrNode) {
|
||||
const attrName = getCurrentAttributeName(path);
|
||||
|
||||
if (attrName) {
|
||||
// TODO: format style and srcset attributes
|
||||
if (attrName === "class") {
|
||||
const formattedClasses = text.trim().split(/\s+/).join(" ");
|
||||
|
||||
let leadingSpace = false;
|
||||
let trailingSpace = false;
|
||||
|
||||
if (isParentOfSomeType(path, ["ConcatStatement"])) {
|
||||
if (
|
||||
isPreviousNodeOfSomeType(path, ["MustacheStatement"]) &&
|
||||
/^\s/.test(text)
|
||||
) {
|
||||
leadingSpace = true;
|
||||
}
|
||||
if (
|
||||
isNextNodeOfSomeType(path, ["MustacheStatement"]) &&
|
||||
/\s$/.test(text) &&
|
||||
formattedClasses !== ""
|
||||
) {
|
||||
trailingSpace = true;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
leadingSpace ? line : "",
|
||||
formattedClasses,
|
||||
trailingSpace ? line : "",
|
||||
];
|
||||
}
|
||||
|
||||
return replaceEndOfLineWith(text, literalline);
|
||||
}
|
||||
|
||||
const whitespacesOnlyRE = /^[\t\n\f\r ]*$/;
|
||||
const isWhitespaceOnly = whitespacesOnlyRE.test(text);
|
||||
const isFirstElement = !getPreviousNode(path);
|
||||
const isLastElement = !getNextNode(path);
|
||||
|
||||
if (options.htmlWhitespaceSensitivity !== "ignore") {
|
||||
// https://infra.spec.whatwg.org/#ascii-whitespace
|
||||
const leadingWhitespacesRE = /^[\t\n\f\r ]*/;
|
||||
const trailingWhitespacesRE = /[\t\n\f\r ]*$/;
|
||||
const whitespacesOnlyRE = /^[\t\n\f\r ]*$/;
|
||||
|
||||
if (whitespacesOnlyRE.test(n.chars)) {
|
||||
// let's remove the file's final newline
|
||||
// https://github.com/ember-cli/ember-new-output/blob/1a04c67ddd02ccb35e0ff41bb5cbce34b31173ef/.editorconfig#L16
|
||||
const shouldTrimTrailingNewlines =
|
||||
isLastElement && isParentOfSomeType(path, ["Template"]);
|
||||
const shouldTrimLeadingNewlines =
|
||||
isFirstElement && isParentOfSomeType(path, ["Template"]);
|
||||
|
||||
if (isWhitespaceOnly) {
|
||||
if (shouldTrimLeadingNewlines || shouldTrimTrailingNewlines) {
|
||||
return "";
|
||||
}
|
||||
|
||||
let breaks = [line];
|
||||
|
||||
const newlines = countNewLines(n.chars);
|
||||
const newlines = countNewLines(text);
|
||||
if (newlines) {
|
||||
breaks = generateHardlines(newlines, 2);
|
||||
breaks = generateHardlines(newlines);
|
||||
}
|
||||
|
||||
if (isLastNodeOfSiblings(path)) {
|
||||
@@ -201,10 +278,8 @@ function print(path, options, print) {
|
||||
return breaks;
|
||||
}
|
||||
|
||||
const [lead] = n.chars.match(leadingWhitespacesRE);
|
||||
const [tail] = n.chars.match(trailingWhitespacesRE);
|
||||
|
||||
let text = n.chars;
|
||||
const [lead] = text.match(leadingWhitespacesRE);
|
||||
const [tail] = text.match(trailingWhitespacesRE);
|
||||
|
||||
let leadBreaks = [];
|
||||
if (lead) {
|
||||
@@ -212,7 +287,7 @@ function print(path, options, print) {
|
||||
|
||||
const leadingNewlines = countNewLines(lead);
|
||||
if (leadingNewlines) {
|
||||
leadBreaks = generateHardlines(countNewLines(lead) || 1, 2);
|
||||
leadBreaks = generateHardlines(leadingNewlines);
|
||||
}
|
||||
|
||||
text = text.replace(leadingWhitespacesRE, "");
|
||||
@@ -220,11 +295,13 @@ function print(path, options, print) {
|
||||
|
||||
let trailBreaks = [];
|
||||
if (tail) {
|
||||
trailBreaks = [line];
|
||||
if (!shouldTrimTrailingNewlines) {
|
||||
trailBreaks = [line];
|
||||
|
||||
const trailingNewlines = countNewLines(tail);
|
||||
if (trailingNewlines) {
|
||||
trailBreaks = generateHardlines(trailingNewlines || 1, 2);
|
||||
const trailingNewlines = countNewLines(tail);
|
||||
if (trailingNewlines) {
|
||||
trailBreaks = generateHardlines(trailingNewlines);
|
||||
}
|
||||
|
||||
if (isLastNodeOfSiblings(path)) {
|
||||
trailBreaks = trailBreaks.map((hardline) => dedent(hardline));
|
||||
@@ -234,17 +311,13 @@ function print(path, options, print) {
|
||||
text = text.replace(trailingWhitespacesRE, "");
|
||||
}
|
||||
|
||||
return [...leadBreaks, text, ...trailBreaks];
|
||||
return [...leadBreaks, fill(getTextValueParts(text)), ...trailBreaks];
|
||||
}
|
||||
|
||||
const maxLineBreaksToPreserve = 2;
|
||||
const isFirstElement = !getPreviousNode(path);
|
||||
const isLastElement = !getNextNode(path);
|
||||
const isWhitespaceOnly = !/\S/.test(n.chars);
|
||||
const lineBreaksCount = countNewLines(n.chars);
|
||||
const lineBreaksCount = countNewLines(text);
|
||||
|
||||
let leadingLineBreaksCount = countLeadingNewLines(n.chars);
|
||||
let trailingLineBreaksCount = countTrailingNewLines(n.chars);
|
||||
let leadingLineBreaksCount = countLeadingNewLines(text);
|
||||
let trailingLineBreaksCount = countTrailingNewLines(text);
|
||||
|
||||
if (
|
||||
(isFirstElement || isLastElement) &&
|
||||
@@ -257,7 +330,7 @@ function print(path, options, print) {
|
||||
if (isWhitespaceOnly && lineBreaksCount) {
|
||||
leadingLineBreaksCount = Math.min(
|
||||
lineBreaksCount,
|
||||
maxLineBreaksToPreserve
|
||||
NEWLINES_TO_PRESERVE_MAX
|
||||
);
|
||||
trailingLineBreaksCount = 0;
|
||||
} else {
|
||||
@@ -270,34 +343,6 @@ function print(path, options, print) {
|
||||
}
|
||||
}
|
||||
|
||||
if (inAttrNode) {
|
||||
// TODO: format style and srcset attributes
|
||||
if (!isInAttributeOfName(path, "class")) {
|
||||
return n.chars;
|
||||
}
|
||||
|
||||
let leadingSpace = "";
|
||||
let trailingSpace = "";
|
||||
|
||||
if (isParentOfSomeType(path, ["ConcatStatement"])) {
|
||||
if (isPreviousNodeOfSomeType(path, ["MustacheStatement"])) {
|
||||
leadingSpace = " ";
|
||||
}
|
||||
if (isNextNodeOfSomeType(path, ["MustacheStatement"])) {
|
||||
trailingSpace = " ";
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
...generateHardlines(leadingLineBreaksCount, maxLineBreaksToPreserve),
|
||||
n.chars.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
|
||||
...generateHardlines(
|
||||
trailingLineBreaksCount,
|
||||
maxLineBreaksToPreserve
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
let leadingSpace = "";
|
||||
let trailingSpace = "";
|
||||
|
||||
@@ -325,19 +370,14 @@ function print(path, options, print) {
|
||||
trailingSpace = "";
|
||||
}
|
||||
|
||||
let text = n.chars;
|
||||
/* if `{{my-component}}` (or any text starting with a mustache)
|
||||
* makes it to the TextNode,
|
||||
* it means it was escaped,
|
||||
* so let's print it escaped, ie.; `\{{my-component}}` */
|
||||
if (text.startsWith("{{") && text.includes("}}")) {
|
||||
text = "\\" + text;
|
||||
}
|
||||
text = text
|
||||
.replace(/^[\t\n\f\r ]+/g, leadingSpace)
|
||||
.replace(/[\t\n\f\r ]+$/, trailingSpace);
|
||||
|
||||
return [
|
||||
...generateHardlines(leadingLineBreaksCount, maxLineBreaksToPreserve),
|
||||
text.replace(/^\s+/g, leadingSpace).replace(/\s+$/, trailingSpace),
|
||||
...generateHardlines(trailingLineBreaksCount, maxLineBreaksToPreserve),
|
||||
...generateHardlines(leadingLineBreaksCount),
|
||||
fill(getTextValueParts(text)),
|
||||
...generateHardlines(trailingLineBreaksCount),
|
||||
];
|
||||
}
|
||||
case "MustacheCommentStatement": {
|
||||
@@ -395,26 +435,14 @@ function print(path, options, print) {
|
||||
function printStartingTag(path, print) {
|
||||
const node = path.getValue();
|
||||
|
||||
const attributesLike = [];
|
||||
if (isNonEmptyArray(node.attributes)) {
|
||||
const attributes = join(line, path.map(print, "attributes"));
|
||||
attributesLike.push(line, attributes);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(node.modifiers)) {
|
||||
const modifiers = join(line, path.map(print, "modifiers"));
|
||||
attributesLike.push(line, modifiers);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(node.comments)) {
|
||||
const comments = join(line, path.map(print, "comments"));
|
||||
attributesLike.push(line, comments);
|
||||
}
|
||||
|
||||
if (isNonEmptyArray(node.blockParams)) {
|
||||
const blockParams = printBlockParams(node);
|
||||
attributesLike.push(line, blockParams);
|
||||
}
|
||||
const attributesLike = ["attributes", "modifiers", "comments", "blockParams"]
|
||||
.filter((property) => isNonEmptyArray(node[property]))
|
||||
.map((property) => [
|
||||
line,
|
||||
property === "blockParams"
|
||||
? printBlockParams(node)
|
||||
: join(line, path.map(print, property)),
|
||||
]);
|
||||
|
||||
return [
|
||||
"<",
|
||||
@@ -427,14 +455,14 @@ function printStartingTag(path, print) {
|
||||
function printChildren(path, options, print) {
|
||||
const node = path.getValue();
|
||||
const isEmpty = node.children.every((n) => isWhitespaceNode(n));
|
||||
if (options.htmlWhitespaceSensitivity !== "strict" && isEmpty) {
|
||||
if (options.htmlWhitespaceSensitivity === "ignore" && isEmpty) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return path.map((childPath, childIndex) => {
|
||||
const printedChild = print(childPath, options, print);
|
||||
|
||||
if (childIndex === 0 && options.htmlWhitespaceSensitivity !== "strict") {
|
||||
if (childIndex === 0 && options.htmlWhitespaceSensitivity === "ignore") {
|
||||
return [softline, printedChild];
|
||||
}
|
||||
|
||||
@@ -530,7 +558,7 @@ function printOpenBlock(path, print) {
|
||||
|
||||
function printElseBlock(node, options) {
|
||||
return [
|
||||
options.htmlWhitespaceSensitivity !== "strict" ? hardline : "",
|
||||
options.htmlWhitespaceSensitivity === "ignore" ? hardline : "",
|
||||
printInverseBlockOpeningMustache(node),
|
||||
"else",
|
||||
printInverseBlockClosingMustache(node),
|
||||
@@ -551,7 +579,7 @@ function printElseIfBlock(path, print) {
|
||||
function printCloseBlock(path, print, options) {
|
||||
const node = path.getValue();
|
||||
|
||||
if (options.htmlWhitespaceSensitivity !== "strict") {
|
||||
if (options.htmlWhitespaceSensitivity === "ignore") {
|
||||
const escape = blockStatementHasOnlyWhitespaceInProgram(node)
|
||||
? softline
|
||||
: hardline;
|
||||
@@ -600,7 +628,7 @@ function printProgram(path, print, options) {
|
||||
|
||||
const program = path.call(print, "program");
|
||||
|
||||
if (options.htmlWhitespaceSensitivity !== "strict") {
|
||||
if (options.htmlWhitespaceSensitivity === "ignore") {
|
||||
return indent([hardline, program]);
|
||||
}
|
||||
|
||||
@@ -612,7 +640,7 @@ function printInverse(path, print, options) {
|
||||
|
||||
const inverse = path.call(print, "inverse");
|
||||
const printed =
|
||||
options.htmlWhitespaceSensitivity !== "strict"
|
||||
options.htmlWhitespaceSensitivity === "ignore"
|
||||
? [hardline, inverse]
|
||||
: inverse;
|
||||
|
||||
@@ -629,13 +657,21 @@ function printInverse(path, print, options) {
|
||||
|
||||
/* TextNode print helpers */
|
||||
|
||||
function isInAttributeOfName(path, type) {
|
||||
return (
|
||||
(isParentOfSomeType(path, ["AttrNode"]) &&
|
||||
path.getParentNode().name.toLowerCase() === type) ||
|
||||
(isParentOfSomeType(path, ["ConcatStatement"]) &&
|
||||
path.getParentNode(1).name.toLowerCase() === type)
|
||||
);
|
||||
function getTextValueParts(value) {
|
||||
return getDocParts(join(line, splitByHtmlWhitespace(value)));
|
||||
}
|
||||
|
||||
function splitByHtmlWhitespace(string) {
|
||||
return string.split(/[\t\n\f\r ]+/);
|
||||
}
|
||||
|
||||
function getCurrentAttributeName(path) {
|
||||
for (let depth = 0; depth < 2; depth++) {
|
||||
const parentNode = path.getParentNode(depth);
|
||||
if (parentNode && parentNode.type === "AttrNode") {
|
||||
return parentNode.name.toLowerCase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function countNewLines(string) {
|
||||
@@ -658,8 +694,8 @@ function countTrailingNewLines(string) {
|
||||
return countNewLines(newLines);
|
||||
}
|
||||
|
||||
function generateHardlines(number = 0, max = 0) {
|
||||
return new Array(Math.min(number, max)).fill(hardline);
|
||||
function generateHardlines(number = 0) {
|
||||
return new Array(Math.min(number, NEWLINES_TO_PRESERVE_MAX)).fill(hardline);
|
||||
}
|
||||
|
||||
/* StringLiteral print helpers */
|
||||
@@ -674,6 +710,11 @@ function generateHardlines(number = 0, max = 0) {
|
||||
* @param {object} options - the prettier options object
|
||||
*/
|
||||
function printStringLiteral(stringLiteral, options) {
|
||||
const { quote, regex } = chooseEnclosingQuote(options, stringLiteral);
|
||||
return [quote, stringLiteral.replace(regex, `\\${quote}`), quote];
|
||||
}
|
||||
|
||||
function chooseEnclosingQuote(options, stringLiteral) {
|
||||
const double = { quote: '"', regex: /"/g };
|
||||
const single = { quote: "'", regex: /'/g };
|
||||
|
||||
@@ -697,13 +738,7 @@ function printStringLiteral(stringLiteral, options) {
|
||||
shouldUseAlternateQuote = numPreferredQuotes > numAlternateQuotes;
|
||||
}
|
||||
|
||||
const enclosingQuote = shouldUseAlternateQuote ? alternate : preferred;
|
||||
const escapedStringLiteral = stringLiteral.replace(
|
||||
enclosingQuote.regex,
|
||||
`\\${enclosingQuote.quote}`
|
||||
);
|
||||
|
||||
return [enclosingQuote.quote, escapedStringLiteral, enclosingQuote.quote];
|
||||
return shouldUseAlternateQuote ? alternate : preferred;
|
||||
}
|
||||
|
||||
/* SubExpression print helpers */
|
||||
|
||||
Loaded 100 of 514 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user