Merge branch 'master' into new-playground

master
Lucas Duailibe 2018-04-10 21:49:46 -03:00
commit d474d5c550
510 changed files with 36766 additions and 4370 deletions

View File

@ -1,4 +1,22 @@
<!-- BUGGY OR UGLY? Please use this template.
<!--
BEFORE SUBMITTING AN ISSUE:
1. Search for your issue on GitHub: https://github.com/prettier/prettier/issues
A large number of opened issues are duplicates of existing issues.
If someone has already opened an issue for what you are experiencing,
  you do not need to open a new issue — please add a 👍 reaction to the
existing issue instead.
2. We get a lot of requests for adding options, but Prettier is
built on the principle of being opinionated about code formatting.
This means we have a very high bar for adding new options.
Find out more: https://prettier.io/docs/en/option-philosophy.html
3. If your issue is with a prettier editor extension or add-on, please open the
issue in the repo for that extension or add-on, instead of this repo.
For ugly or incorrect code issues: Please use the below template.
Tip! Don't write this stuff manually.
@ -8,7 +26,7 @@ Tip! Don't write this stuff manually.
-->
**Prettier 1.9.2**
**Prettier 1.11.1**
[Playground link](https://prettier.io/playground/#.....)
```sh
# Options (if any):

10
.github/no-response.yml vendored Normal file
View File

@ -0,0 +1,10 @@
# Configuration for probot-no-response - https://github.com/probot/no-response
daysUntilClose: 14
responseRequiredLabel: "status:awaiting response"
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

1
.gitignore vendored
View File

@ -13,3 +13,4 @@
.DS_Store
coverage
.idea
package-lock.json

View File

@ -2,5 +2,5 @@
name: prettier
entry: prettier --write
language: node
# From https://github.com/prettier/prettier/blob/133303f47a30f6b3e46ffdf9d5c2d6609d65c416/src/options.js#L32-L42
files: \.(css|less|scss|html|ts|tsx|graphql|gql|json|js|jsx)$
# From https://github.com/prettier/prettier/blob/7a7eb170/docs/index.md
files: \.(css|less|scss|html|ts|tsx|graphql|gql|json|js|jsx|md)$

View File

@ -6,7 +6,7 @@ node_js:
cache:
yarn: true
directories:
- node_modules
- node_modules
env:
- NODE_ENV=development
- NODE_ENV=production
@ -20,5 +20,9 @@ before_script:
script:
- yarn lint
- yarn lint-docs
- AST_COMPARE=1 yarn test -- --runInBand
- if [ "${NODE_ENV}" = "production" ]; then yarn test:dist; fi
- if [ "${NODE_ENV}" = "development" ]; then AST_COMPARE=1 yarn test -- --runInBand; fi
- if [ "${NODE_ENV}" = "development" ]; then yarn codecov; fi
branches:
only:
- master

View File

@ -1,3 +1,32 @@
# 1.11.1
[link](https://github.com/prettier/prettier/compare/1.11.0...1.11.1)
* 1.11.0 was incorrectly shipped with the wrong version of the TypeScript parser, which broke conditional types. This release fixes it.
* Fixed an issue relating to deprecated parsers ([#4072](https://github.com/prettier/prettier/pull/4072))
# 1.11.0
* [Release Notes](https://prettier.io/blog/2018/02/26/1.11.0.html)
# 1.10.2
[link](https://github.com/prettier/prettier/compare/1.10.1...1.10.2)
* Fixed an issue printing .vue files with self-closing tags. (#3705 by duailibe)
# 1.10.1
[link](https://github.com/prettier/prettier/compare/1.10.0...1.10.1)
* Fixed an issue where the CLI fails to resolve a file.
# 1.10.0
[link](https://github.com/prettier/prettier/compare/1.9.2...1.10.0)
* [Release Notes](https://prettier.io/blog/2018/01/10/1.10.0.html)
# 1.9.2
[link](https://github.com/prettier/prettier/compare/1.9.1...1.9.2)

View File

@ -10,7 +10,7 @@ yarn test
Here's what you need to know about the tests:
* The tests uses [Jest](https://facebook.github.io/jest/) snapshots.
* 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 `AST_COMPARE=1 jest` for a more robust test run. That formats each file, re-parses it, and compares the new AST with the original one and makes sure they are semantically equivalent.
* Each test folder has a `jsfmt.spec.js` that runs the tests. For JavaScript files, generally you can just put `run_spec(__dirname, ["babylon", "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, ["babylon"], { trailingComma: "es5" });`

View File

@ -1,4 +1,4 @@
Copyright 2017 James Long
Copyright 2017-2018 James Long
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

View File

@ -15,6 +15,9 @@
· GraphQL
· JSON
· Markdown
· <a href="https://prettier.io/docs/en/plugins.html">
Your favorite language?
</a>
</em>
</p>
@ -23,7 +26,7 @@
<img alt="Gitter" src="https://img.shields.io/gitter/room/jlongster/prettier.svg?style=flat-square">
</a>
<a href="https://travis-ci.org/prettier/prettier">
<img alt="Travis" src="https://img.shields.io/travis/prettier/prettier.svg?style=flat-square">
<img alt="Travis" src="https://img.shields.io/travis/prettier/prettier/master.svg?style=flat-square">
</a>
<a href="https://codecov.io/gh/prettier/prettier">
<img alt="Codecov" src="https://img.shields.io/codecov/c/github/prettier/prettier.svg?style=flat-square">

View File

@ -22,7 +22,7 @@ declare function group(doc: Doc, opts?: GroupOpts): 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 with break. This only matters for "hard" breaks, i.e. newlines that are printed no matter what and can be statically analyzed.
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.
For example, an array will try to fit on one line:
@ -193,13 +193,49 @@ declare function indent(doc: Doc): Doc;
Increase the level of indentation.
### dedent
```ts
declare function dedent(doc: Doc): Doc;
```
Decrease the level of indentation. (Each `align` is considered one level of indentation.)
### align
```ts
declare function align(n: number, doc: Doc): Doc;
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. When using tabs, it's going to print spaces. You should prefer using `indent` whenever possible.
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.
For example:
* `useTabs`
* `tabWidth: 2`
* `<indent><align 2><indent><align 2>` -> `<tab><tab><tab><2 space>`
* `<indent><align 4><indent><align 2>` -> `<tab><tab><tab><2 space>`
* `tabWidth: 4`
* `<indent><align 2><indent><align 2>` -> `<tab><tab><tab><2 space>`
* `<indent><align 4><indent><align 2>` -> `<tab><tab><tab><2 space>`
### markAsRoot
```ts
declare function markAsRoot(doc: Doc): Doc;
```
This marks the current indentation as root for `dedentToRoot` and `literalline`s.
#### dedentToRoot
```ts
declare function dedentToRoot(doc: Doc): Doc;
```
This will dedent the current indentation to the root marked by `markAsRoot`.
### cursor

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

View File

@ -17,11 +17,9 @@ Vim users can simply install either [sbdchd](https://github.com/sbdchd)/[neoform
## Visual Studio Code
Can be installed using the extension sidebar. Search for `Prettier - Code formatter`.
`prettier-vscode` can be installed using the extension sidebar. Search for `Prettier - Code formatter`. It can also be installed using `ext install prettier-vscode` in the command palette. [Check its repository for configuration and shortcuts](https://github.com/prettier/prettier-vscode).
Can also be installed using `ext install prettier-vscode`.
[Check its repository for configuration and shortcuts](https://github.com/prettier/prettier-vscode)
If you'd like to toggle the formatter on and off, install [`vscode-status-bar-format-toggle`](https://marketplace.visualstudio.com/items?itemName=tombonnike.vscode-status-bar-format-toggle).
## Visual Studio

View File

@ -57,3 +57,9 @@ There are a few rules that this disables that you may want to turn back on as lo
"extends": ["plugin:prettier/recommended"]
}
```
Remember to install both `eslint-plugin-prettier` and `eslint-config-prettier`:
```bash
yarn add --dev eslint-plugin-prettier eslint-config-prettier
```

View File

@ -7,7 +7,7 @@ Prettier offers an escape hatch to ignore a block of code or prevent entire file
## Ignoring Files
To exclude files from formatting, add entries to a `.prettierignore` file in the project root or set the `--ignore-path` [CLI](cli.md) option.
To exclude files from formatting, add entries to a `.prettierignore` file in the project root or set the [`--ignore-path` CLI option](cli.md#ignore-path).
## JavaScript
@ -69,3 +69,24 @@ matrix(
<!-- prettier-ignore -->
Do not format this
```
### Range Ignore
_available in v1.12.0+_
This type of ignore is only allowed to be used in top-level and aimed to disable formatting for auto-generated content, e.g. [`all-contributors`](https://github.com/kentcdodds/all-contributors), [`markdown-toc`](https://github.com/jonschlinkert/markdown-toc), etc.
<!-- TODO: remove this ignore when upgraded to v1.12 -->
<!-- prettier-ignore -->
```markdown
<!-- prettier-ignore-start -->
<!-- SOMETHING AUTO-GENERATED BY TOOLS - START -->
| MY | AWESOME | AUTO-GENERATED | TABLE |
|-|-|-|-|
| a | b | c | d |
<!-- SOMETHING AUTO-GENERATED BY TOOLS - END -->
<!-- prettier-ignore-end -->
```

View File

@ -46,9 +46,9 @@ Prettier enforces a consistent code **style** (i.e. code formatting that won't a
If you want to learn more, these two conference talks are great introductions:
[![](https://cloud.githubusercontent.com/assets/197597/24886367/dda8a6f0-1e08-11e7-865b-22492450f10f.png)](https://www.youtube.com/watch?v=hkfBvpEfWdA)
[![A Prettier Printer by James Long on React Conf 2017](/docs/assets/youtube-cover/a-prettier-printer-by-james-long-on-react-conf-2017.png)](https://www.youtube.com/watch?v=hkfBvpEfWdA)
[![](https://cloud.githubusercontent.com/assets/197597/24886368/ddacd6f8-1e08-11e7-806a-9febd23cbf47.png)](https://www.youtube.com/watch?v=0Q4kUNx85_4")
[![JavaScript Code Formatting by Christopher Chedeau on React London 2017](/docs/assets/youtube-cover/javascript-code-formatting-by-christopher-chedeau-on-react-london-2017.png)](https://www.youtube.com/watch?v=0Q4kUNx85_4)
#### Footnotes

View File

@ -7,11 +7,7 @@ Install with `yarn`:
```bash
yarn add prettier --dev --exact
```
You can install it globally if you like:
```bash
# or globally
yarn global add prettier
```

28
docs/option-philosophy.md Normal file
View File

@ -0,0 +1,28 @@
---
id: option-philosophy
title: Option Philosophy
---
Prettier is not a kitchen-sink code formatter that attempts to print your code in any way you wish. It is _opinionated._ Quoting the [Why Prettier?](why-prettier.md) page:
> By far the biggest reason for adopting Prettier is to stop all the on-going debates over styles.
The more options Prettier has, the further from the above goal it gets. **The debates over styles just turn into debates over which Prettier options to use.**
The issue about [resisting adding configuration](https://github.com/prettier/prettier/issues/40) has more 👍s than any option request issue.
So why does Prettier have options at all?
Well, had Prettier been created around the same time as JavaScript itself was born it could have made choices that the community would have picked up (which is the case for [elm-format](https://github.com/avh4/elm-format/)). But JavaScript is far older than Prettier so the community has had time to start their holy wars about tabs vs spaces, single vs double quotes, indentation levels, trailing commas and semicolons, so Prettier more or less has to support those.
Then there's a bunch of interesting cases.
* `--trailing-comma es5` was added to make it easier to use trailing commas in most environments without having to transpile (trailing function commas were added in ES2017).
* `--prose-wrap` is important to support all quirky markdown renderers in the wild.
* `--arrow-parens` was added after [huge demand](https://github.com/prettier/prettier/issues/812). Prettier has to strike a balance between ideal goals and listening to the community.
* `--jsx-bracket-same-line` was needed for a big company with a huge code base (Facebook), which backed the project when it got started, to be able to [adopt Prettier at all](https://github.com/prettier/prettier/pull/661#issuecomment-295770645).
Finally, perhaps the most interesting of them all is `--bracket-spacing`.
The truth is that not even [Prettier's creator knows exactly why it exists](https://github.com/prettier/prettier/issues/715#issuecomment-281096495). It was added super early on without much thought. It now serves as an example of the types of options we should avoid.
Remember, it is easy to _add_ features to a program, but hard to remove them.

View File

@ -1,50 +1,59 @@
---
id: plugins
title: Plugins
title: Plugins (Beta)
---
# IN DEVELOPMENT
## IN BETA
> The plugin API is unreleased and the API may change!
> The plugin API is in a **beta** state as of Prettier 1.10 and the API may change in the next release!
Plugins are ways of adding new languages to Prettier. Prettier's own implementations of all languages are expressed using the plugin API. The core `prettier` package contains JavaScript and other web-focussed languages built in. For additional languages you'll need to install a plugin.
## Using Plugins
There are three ways to add plugins to Prettier:
Plugins are automatically loaded if you have them installed in your `package.json`. Prettier plugin package names must start with `@prettier/plugin-` or `prettier-plugin-` to be registered.
* Via the CLI.
* Via the API.
* With a configuration file.
If the plugin is unable to be found automatically, you can load them with:
### Configuration File (Recommended)
* The [CLI](./cli.md), via the `--plugin` flag:
In your [configuration file](./configuration.md), add the `plugins` property:
```bash
prettier --write main.foo --plugin=./foo-plugin
```
```json
{
"plugins": ["prettier-python"]
}
```
> Tip: You can pass multiple `--plugin` flags.
### CLI
* Or the [API](./api.md), via the `plugins` field:
With the [CLI](./cli.md), pass the `--plugin` flag:
```bash
prettier --write main.py --plugin prettier-python
```
> Tip: You can pass multiple `--plugin` flags.
```js
prettier.format("code", {
parser: "foo",
plugins: ["./foo-plugin"]
});
```
## Official Plugins
* [`prettier-python`](https://github.com/prettier/prettier-python)
* [`prettier-php`](https://github.com/prettier/prettier-php)
* [`@prettier/plugin-python`](https://github.com/prettier/plugin-python)
* [`@prettier/plugin-php`](https://github.com/prettier/plugin-php)
* [`@prettier/plugin-swift`](https://github.com/prettier/plugin-swift)
## Community Plugins
* [`prettier-plugin-elm`](https://github.com/gicentre/prettier-plugin-elm) by [**@giCentre**](https://github.com/gicentre)
* [`prettier-plugin-java`](https://github.com/thorbenvh8/prettier-java) by [**@thorbenvh8**](https://github.com/thorbenvh8)
* [`prettier-plugin-pg`](https://github.com/benjie/prettier-plugin-pg) by [**@benjie**](https://github.com/benjie)
* [`prettier-plugin-ruby`](https://github.com/iamsolankiamit/prettier-ruby) by [**@iamsolankiamit**](https://github.com/iamsolankiamit)
## Developing Plugins
Prettier plugins are regular JavaScript modules with three exports, `languages`, `parsers` and `printers`.
Prettier plugins are regular JavaScript modules with five exports:
* `languages`
* `parsers`
* `printers`
* `options`
* `defaultOptions`
### `languages`
@ -68,14 +77,17 @@ export const languages = [
Parsers convert code as a string into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree).
The key must match the name in the `parsers` array from `languages`. The value contains a parse function and an AST format name.
The key must match the name in the `parsers` array from `languages`. The value contains a parse function, an AST format name, and two location extraction functions (`locStart` and `locEnd`).
```js
export const parsers = {
"dance-parse": {
parse,
// The name of the AST that
astFormat: "dance-ast"
astFormat: "dance-ast",
hasPragma,
locStart,
locEnd
}
};
```
@ -86,6 +98,18 @@ The signature of the `parse` function is:
function parse(text: string, parsers: object, options: object): AST;
```
The location extraction functions (`locStart` and `locEnd`) return the starting and ending locations of a given AST node:
```ts
function locStart(node: object): number;
```
The pragma detection function (`hasPragma`) should return if the text contains the pragma comment.
```ts
function hasPragma(text: string): boolean;
```
### `printers`
Printers convert ASTs into a Prettier intermediate representation, also known as a Doc.
@ -96,12 +120,13 @@ The key must match the `astFormat` that the parser produces. The value contains
export const printers = {
"dance-ast": {
print,
embed
embed,
insertPragma
}
};
```
Printing is a recursive process of coverting an AST node (represented by a path to that node) into a doc. The doc is constructed using the [builder commands](https://github.com/prettier/prettier/blob/master/commands.md):
Printing is a recursive process of converting an AST node (represented by a path to that node) into a doc. The doc is constructed using the [builder commands](https://github.com/prettier/prettier/blob/master/commands.md):
```js
const { concat, join, line, ifBreak, group } = require("prettier").doc.builders;
@ -139,6 +164,51 @@ function embed(
If you don't want to switch to a different parser, simply return `null` or `undefined`.
A plugin can implement how a pragma comment is inserted in the resulting code when the `--insert-pragma` option is used, in the `insertPragma` function. Its signature is:
```ts
function insertPragma(text: string): string;
```
### `options`
`options` is an object containing the custom options your plugin supports.
Example:
```js
options: {
openingBraceNewLine: {
type: "boolean",
category: "Global",
default: true,
description: "Move open brace for code blocks onto new line."
}
}
```
### `defaultOptions`
If your plugin requires different default values for some of Prettier's core options, you can specify them in `defaultOptions`:
```
defaultOptions: {
tabWidth: 4
}
```
### Utility functions
A `util` module from Prettier core is considered a private API and is not meant to be consumed by plugins. Instead, the `util-shared` module provides the following limited set of utility functions for plugins:
```ts
makeString(rawContent: string, enclosingQuote: string, unescapeUnnecessarEscapes: boolean): string;
getNextNonSpaceNonCommentCharacterIndex(text: string, node: object, options: object): number;
isNextLineEmptyAfterIndex(text: string, index: number): boolean;
isNextLineEmpty(text: string, node: object, options: object): boolean;
mapDoc(doc: object, callback: function): void;
```
## Testing Plugins
Since plugins can be resolved using relative paths, when working on one you can do:
@ -152,4 +222,4 @@ prettier.format(code, {
});
```
This will resolve a plugin relative to the current working direcrory.
This will resolve a plugin relative to the current working directory.

View File

@ -7,6 +7,8 @@ You can use Prettier with a pre-commit tool. This can re-format your files that
## Option 1. [lint-staged](https://github.com/okonet/lint-staged)
**Use Case:** Useful for when you need to use other tools on top of Prettier (e.g. ESLint)
Install it along with [husky](https://github.com/typicode/husky):
```bash
@ -30,31 +32,31 @@ and add this config to your `package.json`:
See https://github.com/okonet/lint-staged#configuration for more details about how you can configure lint-staged.
## Option 2. [pre-commit](https://github.com/observing/pre-commit) (JS version)
## Option 2. [pretty-quick](https://github.com/azz/pretty-quick)
Install the package:
**Use Case:** Great for when you want an entire file formatting on your changed/staged files.
Install it along with [husky](https://github.com/typicode/husky):
```bash
yarn add pre-commit --dev
yarn add pretty-quick husky --dev
```
and add this config to your `package.json`:
<!-- prettier-ignore -->
```json
{
"scripts": {
"prettier": "prettier \"*/**/*.js\" --ignore-path ./.prettierignore --write && git add . && git status"
},
"pre-commit": [
"prettier"
]
"precommit": "pretty-quick --staged"
}
}
```
Find more info from [here](https://github.com/observing/pre-commit).
Find more info from [here](https://github.com/azz/pretty-quick).
## Option 3. [pre-commit](https://github.com/pre-commit/pre-commit) (Python version)
## Option 3. [pre-commit](https://github.com/pre-commit/pre-commit)
**Use Case:** Great when working with multi-language projects.
Copy the following config into your `.pre-commit-config.yaml` file:
@ -65,9 +67,33 @@ Copy the following config into your `.pre-commit-config.yaml` file:
- id: prettier
```
Find more info from [here](http://pre-commit.com).
Find more info from [here](https://pre-commit.com).
## Option 4. bash script
## Option 4. [precise-commits](https://github.com/JamesHenry/precise-commits)
**Use Case:** Great for when you want an partial file formatting on your changed/staged files.
Install it along with [husky](https://github.com/typicode/husky):
```bash
yarn add precise-commits husky --dev
```
and add this config to your `package.json`:
```json
{
"scripts": {
"precommit": "precise-commits"
}
}
```
**Note:** This is currently the only tool that will format only staged lines rather than the entire file. See more information [here](https://github.com/JamesHenry/precise-commits#why-precise-commits)
Read more about this tool [here](https://github.com/JamesHenry/precise-commits#2-precommit-hook).
## Option 5. bash script
Alternately you can save this script as `.git/hooks/pre-commit` and give it execute permission:
@ -84,3 +110,12 @@ echo "$jsfiles" | xargs git add
exit 0
```
If git is reporting that your prettified files are still modified after committing, you may need to add a post-commit script to update git's index as described in [this issue](https://github.com/prettier/prettier/issues/2978#issuecomment-334408427).
Add something like the following to `.git/hooks/post-commit`:
```bash
#!/bin/sh
git update-index -g
```

View File

@ -3,78 +3,146 @@ id: rationale
title: Rationale
---
Prettier is an opinionated code formatter. This document gives a rationale behind those opinions.
Prettier is an opinionated code formatter. This document explains some of its choices.
## What prettier is concerned about
### Consistency
Prettier exists for one purpose: to enforce consistency across your entire project. Not only do we output code with consistent whitespace, prettier will lay out code according to a wrapping algorithm based on a maximum line width. That means that long expressions will be broken up across lines, removing the need for manual layout from the programmer which inevitably leads to inconsistency.
## What Prettier is concerned about
### Correctness
The first requirement of prettier is to output valid JavaScript and code that has the exact same behavior as before formatting. Please report any JavaScript code where prettier fails to follow these correctness rules — that's a bug which needs to be fixed!
### Whitespace: indentation and line breaks
This is the core of prettier. The formatting rules are going to be explained in a later section.
The first requirement of Prettier is to output valid code that has the exact same behavior as before formatting. Please report any code where Prettier fails to follow these correctness rules — that's a bug which needs to be fixed!
### Strings
Prettier enforces double quotes by default, but has a setting for enforcing single quotes instead. There are two exceptions:
Double or single quotes? Prettier chooses the one which results in the fewest number of escapes. `"It's gettin' better!"`, not `'It\'s gettin\' better!'`. In case of a tie, Prettier defaults to double quotes (but that can be changed via the [singleQuote](options.html#quotes) option).
* The number of escaped quotes are minimized. For example, if you have a string with a single quote inside, it will be enclosed in double quotes regardless of the quote setting: `"that's a double quote"`, not `'that\'s a double quote'`.
* JSX always uses double quotes. JSX takes its roots from HTML, where the dominant use of quotes for attributes is double quotes. Browser developer tools also follow this convention by always displaying HTML with double quotes, even if the source code uses single quotes.
JSX always uses double quotes. JSX takes its roots from HTML, where the dominant use of quotes for attributes is double quotes. Browser developer tools also follow this convention by always displaying HTML with double quotes, even if the source code uses single quotes.
Prettier maintains the way your string is escaped. For example, `"🙂"` won't be formatted into `"\uD83D\uDE42"` and vice versa.
### Parentheses
Prettier outputs the minimum number of parentheses required to ensure that the behavior of the formatted code stays unchanged. This may lead to code that feels ambiguous. If that's the case, you are encouraged to extract the ambiguous parts into variables.
### Empty lines
It turns out that empty lines are very hard to automatically generate. The approach that prettier takes is to preserve empty lines the way they were in the original source code. The only constraint is that prettier disallows several empty lines in a row. They are collapsed to a single one.
It turns out that empty lines are very hard to automatically generate. The approach that Prettier takes is to preserve empty lines the way they were in the original source code. There are two additional rules:
* Prettier collapses multiple blank lines into a single blank line.
* Empty lines at the start and end of blocks (and whole files) are removed. (Files always end with a single newline, though.)
### Multi-line objects
It is tempting to collapse an object to a single line if it fits, but there are times when it is better for sibling/cousin keys to stay vertically aligned—see [object lists], [nested configs], [stylesheets], and [keyed methods]. To avoid unfavorable collapsing, prettier simply formats any object as multi-line if it appears as such in the original source code. This is the same strategy used by [elm-format] for multi-line records.
By default, Prettiers printing algorithm prints expressions on a single line if they fit. Objects are used for a lot of different things in JavaScript, though, and sometimes it really helps readability if they stay multiline. See [object lists], [nested configs], [stylesheets] and [keyed methods], for example. We haven't been able to find a good rule for all those cases, so Prettier instead keeps objects multiline if there's a newline anywhere inside it in the original source code. A consequence of this is that long singleline objects are automatically expanded, but short multiline objects are never collapsed.
[object lists]: https://github.com/prettier/prettier/issues/74#issue-199965534
[nested configs]: https://github.com/prettier/prettier/issues/88#issuecomment-275448346
[stylesheets]: https://github.com/prettier/prettier/issues/74#issuecomment-275262094
[keyed methods]: https://github.com/prettier/prettier/pull/495#issuecomment-275745434
[elm-format]: https://github.com/prettier/prettier/issues/74#issuecomment-275621526
## What prettier is _not_ concerned about
### Semicolons
Here are a few examples of things that are out of scope for prettier:
This is about using the `--no-semi` option.
* Turning single/double quotes into template literals or vice versa.
* Adding/removing `{}` and `return` where they are optional.
* Turning `?:` into `if then else`.
Consider this piece of code:
<!--
### Semi-colons
<!-- prettier-ignore -->
```js
if (shouldAddLines) {
[-1, 1].forEach(delta => addLine(delta * 20))
}
```
...TBD...
While the above code works just fine without semicolons, Prettier actually turns it into:
## Formatting rules
<!-- prettier-ignore -->
```js
if (shouldAddLines) {
;[-1, 1].forEach(delta => addLine(delta * 20))
}
```
... TBD ...
This is to help you avoid mistakes. Imagine adding this line:
```diff
if (shouldAddLines) {
+ console.log('Do we even get here??')
[-1, 1].forEach(delta => addLine(delta * 20))
}
```
### Function calls
Oops! The above actually means:
<!-- prettier-ignore -->
```js
if (shouldAddLines) {
console.log('Do we even get here??')[-1, 1].forEach(delta => addLine(delta * 20))
}
```
### Method calls
With a semicolon in front of that `[` such issues never happen. It makes the line independent of other lines so you can move and add lines without having to think about ASI rules.
This practice is also common in [standard] which uses a semicolon-free style.
[standard]: https://standardjs.com/rules.html#semicolons
### Imports
Prettier can break long `import` statements across several lines:
```js
import {
CollectionDashboard,
DashboardPlaceholder
} from "../components/collections/collection-dashboard/main";
```
The following example doesn't fit within the print width, but Prettier prints it in a single line anyway:
```js
import { CollectionDashboard } from "../components/collections/collection-dashboard/main";
```
This might be unexpected by some, but we do it this way since it was a common request to keep `import`s with single elements in a single line. The same applies for `require` calls.
### JSX
Prettier prints things a little differently compared to other JS when JSX is involved:
### Boolean expressions
```jsx
function greet(user) {
return user
? `Welcome back, ${user.name}!`
: "Greetings, traveler! Sign up today!";
}
function Greet({ user }) {
return (
<div>
{user ? (
<p>Welcome back, {user.name}!</p>
) : (
<p>Greetings, traveler! Sign up today!</p>
)}
</div>
);
}
```
### String concatenation
-->
There are two reasons.
First off, lots of people already wrapped their JSX in parentheses, especially in `return` statements. Prettier follows this common style.
Secondly, [the alternate formatting makes it easier to edit the JSX](https://github.com/prettier/prettier/issues/2208). It is easy to leave a semicolon behind. As opposed to normal JS, a leftover semicolon in JSX can end up as plain text showing on your page.
```jsx
<div>
<p>Greetings, traveler! Sign up today!</p>; {/* <-- Oops! */}
</div>
```
## What Prettier is _not_ concerned about
Prettier only _prints_ code. It does not transform it. This is to limit the scope of Prettier. Let's focus on the printing and do it really well!
Here are a few examples of things that are out of scope for Prettier:
* Turning single- or double-quoted strings into template literals or vice versa.
* Adding/removing `{}` and `return` where they are optional.
* Turning `?:` into `if`-`else` statements.
* Sorting and hoisting `import`s. (Sorting is unsafe because of side effects, which would violate the [correctness](#correctness) goal.)

View File

@ -34,3 +34,4 @@ title: Related Projects
* [`prettier-github`](https://github.com/jgierer12/prettier-github) formats code in GitHub comments
* [`rollup-plugin-prettier`](https://github.com/mjeanroy/rollup-plugin-prettier) allows you to use Prettier with Rollup
* [`markdown-magic-prettier`](https://github.com/camacho/markdown-magic-prettier) allows you to use Prettier to format JS [codeblocks](https://help.github.com/articles/creating-and-highlighting-code-blocks/) in Markdown files via [Markdown Magic](https://github.com/DavidWells/markdown-magic)
* [`pretty-quick`](https://github.com/azz/pretty-quick) formats your changed files with Prettier

View File

@ -77,9 +77,9 @@ When installed via vim-plug, a default prettier executable is installed inside v
vim-prettier executable resolution:
1. Traverse parents and search for Prettier installation inside `node_modules`
2. Look for a global prettier installation
3. Use locally installed vim-prettier prettier executable
1. Traverse parents and search for Prettier installation inside `node_modules`
2. Look for a global prettier installation
3. Use locally installed vim-prettier prettier executable
### vim-prettier - Usage

View File

@ -1,60 +1,56 @@
---
id: webstorm
title: Webstorm Setup
title: WebStorm Setup
---
## SetUp
## WebStorm 2018.1 and above
### With ESLint Integration
Use the `Reformat with Prettier` action (`Alt-Shift-Cmd-P` on macOS or `Alt-Shift-Ctrl-P` on Windows and Linux) to format the selected code, a file, or a whole directory.
If you are using the ESLint integration for prettier via [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier) all you need to do is simply add a hotkey for `eslint --fix`. To do this go to _File | Settings | Keymap_ for Windows and Linux _WebStorm | Preferences | Keymap_, type `Fix ESLint Problems` in search box and add a keyboard shortcut.
Don't forget to install `prettier` first.
See [this documentation](https://www.jetbrains.com/help/webstorm/configuring-keyboard-shortcuts.html) about configuring keyboard shortcuts.
To use Prettier in IntelliJ IDEA, PhpStorm, PyCharm, and other JetBrains IDEs, please install this [plugin](https://plugins.jetbrains.com/plugin/10456-prettier).
### Standalone
For older IDE versions, please follow the instructions below.
#### Configure External Tool
## Running Prettier on save using File Watcher
https://blog.jetbrains.com/webstorm/2016/08/using-external-tools/
To automatically format your files using `prettier` on save, you can use a file watcher.
Go to _File | Settings | Tools | External Tools_ for Windows and Linux or _WebStorm | Preferences | Tools | External Tools_ for OS X and click **+** to add a new tool. Lets name it **Prettier**.
Go to _Preferences | Tools | File Watchers_ and click **+** to add a new watcher. Lets name it **Prettier**.
* **Program** set `prettier`
* **File Type**: _JavaScript_ (or _Any_ if you want to run `prettier` on all files)
* **Scope**: _Project Files_
* **Program**: full path to `.bin/prettier` or `.bin\prettier.cmd` in the project's `node_module` folder
* **Arguments**: `--write [other options] $FilePathRelativeToProjectRoot$`
* **Output paths to refresh**: `$FilePathRelativeToProjectRoot$`
* **Working directory**: `$ProjectFileDir$`
* **Auto-save edited files to trigger the watcher**: Uncheck to reformat on Save only.
> If on the other hand you have `prettier` installed locally, replace the **Program** with `$ProjectFileDir$/node_modules/.bin/prettier` (on OS X and Linux) or `$ProjectFileDir$\node_modules\.bin\prettier.cmd` (on Windows).
![Example](/docs/assets/webstorm/file-watcher-prettier.png)
* **Parameters** set `--write [other opts] $FilePathRelativeToProjectRoot$`
* **Working directory** set `$ProjectFileDir$`
## WebStorm 2017.3 or earlier
![Example](/docs/assets/webstorm/with-prettier.png)
### Using Prettier with ESLint
##### Process directories
If you are using ESLint with [eslint-plugin-prettier](https://github.com/prettier/eslint-plugin-prettier), use the `Fix ESLint Problems` action to reformat the currect file find it using _Find Action_ (`Cmd/Ctrl-Shift-A`) or [add a keyboard shortcut](https://www.jetbrains.com/help/webstorm/configuring-keyboard-shortcuts.html) to it in _Preferences | Kymap_ and then use it.
* Clone the External tool created above and name it `Prettier Directories`
* **Parameters** set `--write [other opts] $FileDirRelativeToProjectRoot$/**/{*.js,*.jsx}`
Make sure that the ESLint integration is enabled in _Preferences | Languages & Frameworks | JavaScript | Code Quality Tools | ESLint_.
#### Usage
### Using Prettier as External Tool
* Cmd-Shift-A on OS X or Ctrl+Shift+A on Windows and Linux
* Type: 'prettier' and hit enter
Go to _Preferences | Tools | External Tools_ and click **+** to add a new tool. Lets name it **Prettier**.
#### Configure Keymap
* **Program**: `prettier` (if it's installed globally)
* **Parameters**: `--write [other options] $FilePathRelativeToProjectRoot$`
* **Working directory**: `$ProjectFileDir$`
Now when you setup **External Tool** I guess you want to add hotkey for it. Go to _File | Settings | Keymap_ for Windows and Linux _WebStorm | Preferences | Keymap_ and type external tool name in search box.
> If Prettier is installed locally in your project, replace the **Program** with `$ProjectFileDir$/node_modules/.bin/prettier` (on macOS and Linux) or `$ProjectFileDir$\node_modules\.bin\prettier.cmd` (on Windows).
See [this documentation](https://www.jetbrains.com/help/webstorm/configuring-keyboard-shortcuts.html) about configuring keyboard shortcuts.
![Example](/docs/assets/webstorm/external-tool-prettier.png)
## Using File Watcher
Press `Cmd/Ctrl-Shift-A` (_Find Action_), search for _Prettier_, and then hit `Enter`.
To automatically format using `prettier` on save, you can use a file watcher.
It will run `prettier` for the current file.
Go to _File | Settings | Tools | File Watchers_ for Windows and Linux or _WebStorm | Preferences | Tools | File Watchers_ for OS X and click **+** to add a new tool. Lets name it **Prettier**.
* **File Type**: JavaScript
* **Scope**: Current File
* **Program** set `prettier` (if you have `prettier` installed locally, see ["Configure External Tool"](#configure-external-tool) above)
* **Arguments** set `--write [other opts] $FilePath$`
* **Working directory** set `$ProjectFileDir$`
* **Immediate file synchronization**: Uncheck to reformat on Save only (otherwise code will jump around while you type).
![Example](/docs/assets/webstorm/prettier-file-watcher.png)
You can [add a keyboard shortcut](https://www.jetbrains.com/help/webstorm/configuring-keyboard-shortcuts.html) to run this External tool configuration in _Preferences | Keymap_.

View File

@ -7,6 +7,8 @@ title: Why Prettier?
By far the biggest reason for adopting Prettier is to stop all the on-going debates over styles. It is generally accepted that having a common style guide is valuable for a project and team but getting there is a very painful and unrewarding process. People get very emotional around particular ways of writing code and nobody likes spending time writing and receiving nits.
So why choose the "Prettier style guide" over any other random style guide? Because Prettier is the only "style guide" that is fully automatic. Even if Prettier does not format all code 100% the way you'd like, it's worth the "sacrifice" given the unique benefits of Prettier, don't you think?
* “We want to free mental threads and end discussions around style. While sometimes fruitful, these discussions are for the most part wasteful.”
* “Literally had an engineer go through a huge effort of cleaning up all of our code because we were debating ternary style for the longest time and were inconsistent about it. It was dumb, but it was a weird on-going "great debate" that wasted lots of little back and forth bits. It's far easier for us all to agree now: just run Prettier, and go with that style.”
* “Getting tired telling people how to style their product code.”

View File

@ -1,10 +1,9 @@
"use strict";
const docblock = require("jest-docblock");
const version = require("./package.json").version;
const util = require("./src/common/util");
const privateUtil = require("./src/common/util");
const sharedUtil = require("./src/common/util-shared");
const getSupportInfo = require("./src/common/support").getSupportInfo;
const comments = require("./src/main/comments");
@ -37,11 +36,6 @@ function attachComments(text, ast, opts) {
return astComments;
}
function hasPragma(text) {
const pragmas = Object.keys(docblock.parse(docblock.extract(text)));
return pragmas.indexOf("prettier") !== -1 || pragmas.indexOf("format") !== -1;
}
function ensureAllCommentsPrinted(astComments) {
if (!astComments) {
return;
@ -68,7 +62,9 @@ function ensureAllCommentsPrinted(astComments) {
}
function formatWithCursor(text, opts, addAlignmentSize) {
if (opts.requirePragma && !hasPragma(text)) {
const selectedParser = parser.resolveParser(opts);
const hasPragma = !selectedParser.hasPragma || selectedParser.hasPragma(text);
if (opts.requirePragma && !hasPragma) {
return { formatted: text };
}
@ -80,24 +76,19 @@ function formatWithCursor(text, opts, addAlignmentSize) {
if (
opts.insertPragma &&
!hasPragma(text) &&
opts.printer.insertPragma &&
!hasPragma &&
opts.rangeStart === 0 &&
opts.rangeEnd === Infinity
) {
const parsedDocblock = docblock.parseWithComments(docblock.extract(text));
const pragmas = Object.assign({ format: "" }, parsedDocblock.pragmas);
const newDocblock = docblock.print({
pragmas,
comments: parsedDocblock.comments.replace(/^(\s+?\r?\n)+/, "") // remove leading newlines
});
const strippedText = docblock.strip(text);
const separatingNewlines = strippedText.startsWith("\n") ? "\n" : "\n\n";
text = newDocblock + separatingNewlines + strippedText;
text = opts.printer.insertPragma(text);
}
addAlignmentSize = addAlignmentSize || 0;
const ast = parser.parse(text, opts);
const result = parser.parse(text, opts);
const ast = result.ast;
text = result.text;
const formattedRangeOnly = formatRange(text, opts, ast);
if (formattedRangeOnly) {
@ -109,7 +100,7 @@ function formatWithCursor(text, opts, addAlignmentSize) {
const cursorNodeAndParents = findNodeAtOffset(ast, opts.cursorOffset, opts);
const cursorNode = cursorNodeAndParents.node;
if (cursorNode) {
cursorOffset = opts.cursorOffset - util.locStart(cursorNode);
cursorOffset = opts.cursorOffset - opts.locStart(cursorNode);
opts.cursorNode = cursorNode;
}
}
@ -143,7 +134,7 @@ function format(text, opts, addAlignmentSize) {
return formatWithCursor(text, opts, addAlignmentSize).formatted;
}
function findSiblingAncestors(startNodeAndParents, endNodeAndParents) {
function findSiblingAncestors(startNodeAndParents, endNodeAndParents, opts) {
let resultStartNode = startNodeAndParents.node;
let resultEndNode = endNodeAndParents.node;
@ -158,7 +149,7 @@ function findSiblingAncestors(startNodeAndParents, endNodeAndParents) {
if (
endParent.type !== "Program" &&
endParent.type !== "File" &&
util.locStart(endParent) >= util.locStart(startNodeAndParents.node)
opts.locStart(endParent) >= opts.locStart(startNodeAndParents.node)
) {
resultEndNode = endParent;
} else {
@ -170,7 +161,7 @@ function findSiblingAncestors(startNodeAndParents, endNodeAndParents) {
if (
startParent.type !== "Program" &&
startParent.type !== "File" &&
util.locEnd(startParent) <= util.locEnd(endNodeAndParents.node)
opts.locEnd(startParent) <= opts.locEnd(endNodeAndParents.node)
) {
resultStartNode = startParent;
} else {
@ -187,8 +178,8 @@ function findSiblingAncestors(startNodeAndParents, endNodeAndParents) {
function findNodeAtOffset(node, offset, options, predicate, parentNodes) {
predicate = predicate || (() => true);
parentNodes = parentNodes || [];
const start = util.locStart(node);
const end = util.locEnd(node);
const start = options.locStart(node, options.locStart);
const end = options.locEnd(node, options.locEnd);
if (start <= offset && offset <= end) {
for (const childNode of comments.getSortedChildNodes(
node,
@ -248,10 +239,10 @@ function isSourceElement(opts, node) {
"ExportNamedDeclaration", // Module
"ExportAllDeclaration", // Module
"TypeAlias", // Flow
"InterfaceDeclaration", // Flow, Typescript
"TypeAliasDeclaration", // Typescript
"ExportAssignment", // Typescript
"ExportDeclaration" // Typescript
"InterfaceDeclaration", // Flow, TypeScript
"TypeAliasDeclaration", // TypeScript
"ExportAssignment", // TypeScript
"ExportDeclaration" // TypeScript
];
const jsonSourceElements = [
"ObjectExpression",
@ -333,12 +324,19 @@ function calculateRange(text, opts, ast) {
const siblingAncestors = findSiblingAncestors(
startNodeAndParents,
endNodeAndParents
endNodeAndParents,
opts
);
const startNode = siblingAncestors.startNode;
const endNode = siblingAncestors.endNode;
const rangeStart = Math.min(util.locStart(startNode), util.locStart(endNode));
const rangeEnd = Math.max(util.locEnd(startNode), util.locEnd(endNode));
const rangeStart = Math.min(
opts.locStart(startNode, opts.locStart),
opts.locStart(endNode, opts.locStart)
);
const rangeEnd = Math.max(
opts.locEnd(startNode, opts.locEnd),
opts.locEnd(endNode, opts.locEnd)
);
return {
rangeStart: rangeStart,
@ -365,7 +363,10 @@ function formatRange(text, opts, ast) {
);
const indentString = text.slice(rangeStart2, rangeStart);
const alignmentSize = util.getAlignmentSize(indentString, opts.tabWidth);
const alignmentSize = privateUtil.getAlignmentSize(
indentString,
opts.tabWidth
);
const rangeFormatted = format(
rangeString,
@ -411,6 +412,8 @@ module.exports = {
version,
util: sharedUtil,
/* istanbul ignore next */
__debug: {
parse: function(text, opts) {
@ -432,7 +435,9 @@ module.exports = {
},
printToDoc: function(text, opts) {
opts = normalizeOptions(opts);
const ast = parser.parse(text, opts);
const result = parser.parse(text, opts);
const ast = result.ast;
text = result.text;
attachComments(text, ast, opts);
const doc = printAstToDoc(ast, opts);
return doc;

View File

@ -14,5 +14,12 @@ module.exports = {
"<rootDir>/src/clean-ast.js",
"<rootDir>/src/deprecated.js"
],
moduleNameMapper: {
// Jest wires `fs` to `graceful-fs`, which causes a memory leak when
// `graceful-fs` does `require('fs')`.
// Ref: https://github.com/facebook/jest/issues/2179#issuecomment-355231418
// If this is removed, see also rollup.bin.config.js and rollup.index.config.js.
"graceful-fs": "<rootDir>/tests_config/fs.js"
},
transform: {}
};

View File

@ -1,6 +1,6 @@
{
"name": "prettier",
"version": "1.9.2",
"version": "1.11.1",
"description": "Prettier is an opinionated code formatter",
"bin": {
"prettier": "./bin/prettier.js"
@ -14,7 +14,7 @@
"node": ">=4"
},
"dependencies": {
"@babel/code-frame": "7.0.0-beta.35",
"@babel/code-frame": "7.0.0-beta.40",
"@glimmer/syntax": "0.30.3",
"babylon": "7.0.0-beta.34",
"camelcase": "4.1.0",
@ -24,36 +24,39 @@
"dashify": "0.2.2",
"dedent": "0.7.0",
"diff": "3.2.0",
"editorconfig": "0.14.2",
"editorconfig": "0.15.0",
"editorconfig-to-prettier": "0.0.6",
"emoji-regex": "6.5.1",
"escape-string-regexp": "1.0.5",
"esutils": "2.0.2",
"find-project-root": "1.1.1",
"flow-parser": "0.59.0",
"flow-parser": "0.64.0",
"get-stream": "3.0.0",
"globby": "6.1.0",
"graphql": "0.10.5",
"graphql": "0.13.2",
"gray-matter": "3.1.1",
"html-tag-names": "1.1.2",
"ignore": "3.3.7",
"jest-docblock": "21.3.0-beta.11",
"jest-validate": "21.1.0",
"jest-docblock": "22.2.2",
"json-stable-stringify": "1.0.1",
"leven": "2.1.0",
"mem": "1.1.0",
"minimatch": "3.0.4",
"minimist": "1.2.0",
"parse5": "3.0.3",
"postcss-less": "1.1.3",
"postcss-less": "1.1.5",
"postcss-media-query-parser": "0.2.3",
"postcss-scss": "1.0.2",
"postcss-scss": "1.0.5",
"postcss-selector-parser": "2.2.3",
"postcss-values-parser": "1.3.1",
"postcss-values-parser": "1.5.0",
"read-pkg-up": "3.0.0",
"remark-frontmatter": "1.1.0",
"remark-parse": "4.0.0",
"remark-parse": "5.0.0",
"resolve": "1.5.0",
"semver": "5.4.1",
"string-width": "2.1.1",
"typescript": "2.7.0-insiders.20171214",
"typescript-eslint-parser": "11.0.0",
"typescript": "2.8.0-rc",
"typescript-eslint-parser": "14.0.0",
"unicode-regex": "1.0.1",
"unified": "6.1.6"
},
@ -62,15 +65,15 @@
"babel-preset-es2015": "6.24.1",
"codecov": "2.2.0",
"cross-env": "5.0.5",
"eslint": "4.1.1",
"eslint": "4.18.2",
"eslint-config-prettier": "2.9.0",
"eslint-friendly-formatter": "3.0.0",
"eslint-plugin-import": "2.6.1",
"eslint-plugin-prettier": "2.4.0",
"eslint-plugin-react": "7.1.0",
"eslint-plugin-import": "2.9.0",
"eslint-plugin-prettier": "2.6.0",
"eslint-plugin-react": "7.7.0",
"jest": "21.1.0",
"mkdirp": "0.5.1",
"prettier": "1.9.2",
"prettier": "1.11.1",
"prettylint": "1.0.0",
"rimraf": "2.6.2",
"rollup": "0.47.6",
@ -80,18 +83,19 @@
"rollup-plugin-node-globals": "1.1.0",
"rollup-plugin-node-resolve": "2.0.0",
"rollup-plugin-replace": "1.2.1",
"shelljs": "0.7.8",
"shelljs": "0.8.1",
"snapshot-diff": "0.2.2",
"strip-ansi": "4.0.0",
"sw-toolbox": "3.6.0",
"uglify-es": "3.0.28",
"tempy": "0.2.1",
"uglify-es": "3.3.9",
"webpack": "2.6.1"
},
"scripts": {
"prepublishOnly": "echo \"Error: must publish from dist/\" && exit 1",
"prepare-release": "yarn && yarn build && yarn test:dist",
"test": "jest",
"test:dist": "cross-env NODE_ENV=production yarn test",
"test:dist": "node ./scripts/test-dist.js",
"test-integration": "jest tests_integration",
"lint": "cross-env EFF_NO_LINK_RULES=true eslint . --format node_modules/eslint-friendly-formatter",
"lint-docs": "prettylint {.,docs,website,website/blog}/*.md",

View File

@ -2,6 +2,7 @@
"use strict";
const fs = require("fs");
const path = require("path");
const shell = require("shelljs");
const parsers = require("./parsers");
@ -39,6 +40,13 @@ shell.exec(
`node_modules/babel-cli/bin/babel.js ${docs}/index.js --out-file ${docs}/index.js --presets=es2015`
);
// wrap content with IIFE to avoid `assign to readonly` error on Safari
(function(filename) {
const content = fs.readFileSync(filename, "utf8");
const wrapped = `"use strict";(function(){${content}}());`;
fs.writeFileSync(filename, wrapped);
})(`${docs}/index.js`);
shell.exec(
`rollup -c scripts/build/rollup.docs.config.js --environment filepath:parser-babylon.js -i ${prettierPath}/parser-babylon.js`
);
@ -62,6 +70,16 @@ shell.cp("node_modules/sw-toolbox/sw-toolbox.js", `${docs}/sw-toolbox.js`);
shell.cd("website");
shell.echo("Building website...");
shell.exec("yarn install");
shell.echo("Copy prettier-animated-logo CSS file to docs");
shell.cp(
path.join(
rootDir,
"website/node_modules/@sandhose/prettier-animated-logo/dist/wide.css"
),
`${docs}/prettier-animated-logo.css`
);
shell.exec("yarn build");
shell.echo();

View File

@ -26,7 +26,7 @@ shell.rm("-Rf", "dist/");
shell.exec("rollup -c scripts/build/rollup.index.config.js");
shell.exec("rollup -c scripts/build/rollup.bin.config.js");
shell.chmod("+x", "./dist/bin/prettier.js");
shell.chmod("+x", "./dist/bin-prettier.js");
shell.exec("rollup -c scripts/build/rollup.third-party.config.js");
@ -63,7 +63,7 @@ shell.sed(
/eval\("require"\)/,
"require",
"dist/index.js",
"dist/bin/prettier.js"
"dist/bin-prettier.js"
);
shell.echo("Update ISSUE_TEMPLATE.md");
@ -86,11 +86,13 @@ pipe(newIssueTemplate).to(".github/ISSUE_TEMPLATE.md");
shell.echo("Copy package.json");
const pkgWithoutDependencies = Object.assign({}, pkg);
pkgWithoutDependencies.bin = "./bin-prettier.js";
delete pkgWithoutDependencies.dependencies;
pkgWithoutDependencies.scripts = {
prepublishOnly:
"node -e \"assert.equal(require('.').version, require('..').version)\""
};
pkgWithoutDependencies.files = ["*.js"];
pipe(JSON.stringify(pkgWithoutDependencies, null, 2)).to("dist/package.json");
shell.echo("Copy README.md");

View File

@ -7,13 +7,20 @@ import * as path from "path";
export default Object.assign(baseConfig, {
entry: "bin/prettier.js",
dest: "dist/bin/prettier.js",
dest: "dist/bin-prettier.js",
format: "cjs",
banner: "#!/usr/bin/env node",
plugins: [
replace({ "#!/usr/bin/env node": "" }),
replace({
"#!/usr/bin/env node": "",
// See comment in jest.config.js
"require('graceful-fs')": "require('fs')"
}),
json(),
resolve({ preferBuiltins: true }),
resolve({
preferBuiltins: true,
extensions: [".js", ".json"]
}),
commonjs()
],
external: [
@ -27,6 +34,6 @@ export default Object.assign(baseConfig, {
path.resolve("src/common/third-party.js")
],
paths: {
[path.resolve("src/common/third-party.js")]: "../third-party"
[path.resolve("src/common/third-party.js")]: "./third-party"
}
});

View File

@ -17,10 +17,15 @@ export default Object.assign(baseConfig, {
format: "cjs",
plugins: [
replace({
"process.env.NODE_ENV": JSON.stringify("production")
"process.env.NODE_ENV": JSON.stringify("production"),
// See comment in jest.config.js
"require('graceful-fs')": "require('fs')"
}),
json(),
resolve({ preferBuiltins: true }),
resolve({
preferBuiltins: true,
extensions: [".js", ".json"]
}),
commonjs()
],
external,

View File

@ -24,7 +24,7 @@ export default Object.assign(baseConfig, {
// by its value before bundling.
parser.endsWith("flow")
? replace({
"require(s8)": 'require("fs")',
"require(tf)": 'require("fs")',
include: "node_modules/flow-parser/flow_parser.js"
})
: {},

102
scripts/generate-schema.js Executable file
View File

@ -0,0 +1,102 @@
#!/usr/bin/env node
"use strict";
const prettier = require("..");
console.log(
prettier.format(
JSON.stringify(generateSchema(prettier.getSupportInfo().options)),
{ parser: "json" }
)
);
function generateSchema(options) {
return {
$schema: "http://json-schema.org/draft-04/schema#",
title: "Schema for .prettierrc",
type: "object",
definitions: {
optionsDefinition: {
type: "object",
properties: options.reduce(
(props, option) =>
Object.assign(props, { [option.name]: optionToSchema(option) }),
{}
)
},
overridesDefinition: {
type: "object",
properties: {
overrides: {
type: "array",
description:
"Provide a list of patterns to override prettier configuration.",
items: {
type: "object",
required: ["files"],
properties: {
files: {
description: "Include these files in this override.",
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
]
},
excludeFiles: {
description: "Exclude these files from this override.",
oneOf: [
{ type: "string" },
{ type: "array", items: { type: "string" } }
]
},
options: {
type: "object",
description: "The options to apply for this override.",
$ref: "#/definitions/optionsDefinition"
}
},
additionalProperties: false
}
}
}
}
},
allOf: [
{ $ref: "#/definitions/optionsDefinition" },
{ $ref: "#/definitions/overridesDefinition" }
]
};
}
function optionToSchema(option) {
return Object.assign(
{
description: option.description,
default: option.default
},
option.type === "choice"
? { oneOf: option.choices.map(choiceToSchema) }
: { type: optionTypeToSchemaType(option.type) }
);
}
function optionTypeToSchemaType(optionType) {
switch (optionType) {
case "int":
return "integer";
case "array":
case "boolean":
return optionType;
case "choice":
throw new Error(
"Please use `oneOf` instead of `enum` for better description support."
);
default:
return "string";
}
}
function choiceToSchema(choice) {
return { enum: [choice.value], description: choice.description };
}

33
scripts/test-dist.js Normal file
View File

@ -0,0 +1,33 @@
#!/usr/bin/env node
"use strict";
const path = require("path");
const shell = require("shelljs");
const tempy = require("tempy");
shell.config.fatal = true;
const rootDir = path.join(__dirname, "..");
const distDir = path.join(rootDir, "dist");
const file = shell.exec("npm pack", { cwd: distDir }).stdout.trim();
const tarPath = path.join(distDir, file);
const tmpDir = tempy.directory();
shell.config.silent = true;
shell.exec("npm init -y", { cwd: tmpDir });
shell.exec(`npm install "${tarPath}"`, { cwd: tmpDir });
shell.config.silent = false;
const code = shell.exec("yarn test --color --runInBand", {
cwd: rootDir,
env: Object.assign({}, process.env, {
NODE_ENV: "production",
AST_COMPARE: "1",
PRETTIER_DIR: path.join(tmpDir, "node_modules/prettier")
}),
shell: true
}).code;
process.exit(code);

View File

@ -1,6 +1,5 @@
"use strict";
const camelCase = require("camelcase");
const dedent = require("dedent");
const CATEGORY_CONFIG = "Config";
@ -64,46 +63,18 @@ const categoryOrder = [
* // If the option has a value that is an exception to the regular value
* // constraints, indicate that value here (or use a function for more
* // flexibility).
* exception?: any | ((value: any) => boolean);
* exception?: ((value: any) => boolean);
*
* // Indicate that the option is deprecated. Use a string to add an extra
* // message to --help for the option, for example to suggest a replacement
* // option.
* deprecated?: true | string;
*
* // Custom function to get the value for the option. Useful for handling
* // deprecated options.
* // --parser example: (value, argv) => argv["flow-parser"] ? "flow" : value
* getter?: (value: any, argv: any) => any;
* }
* }
*
* Note: The options below are sorted alphabetically.
*/
const detailedOptions = normalizeDetailedOptions({
"arrow-parens": {
type: "choice",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Include parentheses around a sole arrow function parameter.",
choices: [
{
value: "avoid",
description: "Omit parens when possible. Example: `x => x`"
},
{
value: "always",
description: "Always include parens. Example: `(x) => x`"
}
]
},
"bracket-spacing": {
type: "boolean",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Print spaces between brackets.",
oppositeDescription: "Do not print spaces between brackets."
},
const options = {
color: {
// The supports-color package (a sub sub dependency) looks directly at
// `process.argv` for `--no-color` and such-like options. The reason it is
@ -145,16 +116,6 @@ const detailedOptions = normalizeDetailedOptions({
description:
"Define in which order config files and CLI options should be evaluated."
},
"cursor-offset": {
type: "int",
category: CATEGORY_EDITOR,
exception: -1,
forwardToApi: true,
description: dedent`
Print (to stderr) where a cursor at the given position would move to after formatting.
This option cannot be used with --range-start and --range-end.
`
},
"debug-check": {
type: "boolean"
},
@ -175,12 +136,6 @@ const detailedOptions = normalizeDetailedOptions({
description:
"Find and print the path to a configuration file for the given input file."
},
"flow-parser": {
// Deprecated in 0.0.10
type: "boolean",
category: CATEGORY_FORMAT,
deprecated: "Use `--parser flow` instead."
},
help: {
type: "flag",
alias: "h",
@ -195,19 +150,6 @@ const detailedOptions = normalizeDetailedOptions({
default: ".prettierignore",
description: "Path to a file with patterns describing files to ignore."
},
"insert-pragma": {
type: "boolean",
forwardToApi: true,
description: dedent`
Insert @format pragma into file's first docblock comment.
`
},
"jsx-bracket-same-line": {
type: "boolean",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Put > on the last line instead of at a new line."
},
"list-different": {
type: "boolean",
category: CATEGORY_OUTPUT,
@ -221,145 +163,14 @@ const detailedOptions = normalizeDetailedOptions({
default: "log",
choices: ["silent", "error", "warn", "log", "debug"]
},
parser: {
type: "choice",
category: CATEGORY_FORMAT,
forwardToApi: true,
exception: value => typeof value === "string", // Allow path to a parser module.
choices: [
"flow",
"babylon",
"typescript",
"css",
{ value: "postcss", deprecated: true, redirect: "css" },
"less",
"scss",
"json",
// "glimmer",
"graphql",
"markdown",
"vue"
],
description: "Which parser to use.",
getter: (value, argv) => (argv["flow-parser"] ? "flow" : value)
},
plugin: {
type: "path",
category: CATEGORY_CONFIG,
description:
"Add a plugin. Multiple plugins can be passed as separate `--plugin`s.",
forwardToApi: "plugins",
array: true
},
"print-width": {
type: "int",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "The line length where Prettier will try wrap."
},
"prose-wrap": {
type: "choice",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "How to wrap prose. (markdown)",
choices: [
{
value: "always",
description: "Wrap prose if it exceeds the print width."
},
{ value: "never", description: "Do not wrap prose." },
{ value: "preserve", description: "Wrap prose as-is." },
{ value: false, deprecated: true, redirect: "never" }
]
},
"range-end": {
type: "int",
category: CATEGORY_EDITOR,
forwardToApi: true,
exception: Infinity,
description: dedent`
Format code ending at a given character offset (exclusive).
The range will extend forwards to the end of the selected statement.
This option cannot be used with --cursor-offset.
`
},
"range-start": {
type: "int",
category: CATEGORY_EDITOR,
forwardToApi: true,
description: dedent`
Format code starting at a given character offset.
The range will extend backwards to the start of the first line containing the selected statement.
This option cannot be used with --cursor-offset.
`
},
"require-pragma": {
type: "boolean",
forwardToApi: true,
description: dedent`
Require either '@prettier' or '@format' to be present in the file's first docblock comment
in order for it to be formatted.
`
},
semi: {
type: "boolean",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Print semicolons.",
oppositeDescription:
"Do not print semicolons, except at the beginning of lines which may need them."
},
"single-quote": {
type: "boolean",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Use single quotes instead of double quotes."
},
stdin: {
type: "boolean",
description: "Force reading input from stdin."
},
"stdin-filepath": {
type: "path",
forwardToApi: "filepath",
description: "Path to the file to pretend that stdin comes from."
},
"support-info": {
type: "boolean",
description: "Print support information as JSON."
},
"tab-width": {
type: "int",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Number of spaces per indentation level."
},
"trailing-comma": {
type: "choice",
category: CATEGORY_FORMAT,
forwardToApi: true,
choices: [
{ value: "none", description: "No trailing commas." },
{
value: "es5",
description:
"Trailing commas where valid in ES5 (objects, arrays, etc.)"
},
{
value: "all",
description:
"Trailing commas wherever possible (including function arguments)."
},
{ value: "", deprecated: true, redirect: "es5" }
],
description: "Print trailing commas wherever possible when multi-line."
},
"use-tabs": {
type: "boolean",
category: CATEGORY_FORMAT,
forwardToApi: true,
description: "Indent with tabs instead of spaces."
},
version: {
type: "boolean",
alias: "v",
@ -375,29 +186,6 @@ const detailedOptions = normalizeDetailedOptions({
category: CATEGORY_OUTPUT,
description: "Edit files in-place. (Beware!)"
}
});
const minimistOptions = {
boolean: detailedOptions
.filter(option => option.type === "boolean")
.map(option => option.name),
string: detailedOptions
.filter(option => option.type !== "boolean")
.map(option => option.name),
default: detailedOptions
.filter(option => option.default !== undefined)
.reduce(
(current, option) =>
Object.assign({ [option.name]: option.default }, current),
{}
),
alias: detailedOptions
.filter(option => option.alias !== undefined)
.reduce(
(current, option) =>
Object.assign({ [option.name]: option.alias }, current),
{}
)
};
const usageSummary = dedent`
@ -407,52 +195,13 @@ const usageSummary = dedent`
Stdin is read if it is piped to Prettier and no files are given.
`;
function normalizeDetailedOptions(rawDetailedOptions) {
const names = Object.keys(rawDetailedOptions).sort();
const normalized = names.map(name => {
const option = rawDetailedOptions[name];
return Object.assign({}, option, {
name,
category: option.category || CATEGORY_OTHER,
forwardToApi:
option.forwardToApi &&
(typeof option.forwardToApi === "string"
? option.forwardToApi
: camelCase(name)),
choices:
option.choices &&
option.choices.map(choice =>
Object.assign(
{ description: "", deprecated: false },
typeof choice === "object" ? choice : { value: choice }
)
),
getter: option.getter || (value => value)
});
});
return normalized;
}
const detailedOptionMap = detailedOptions.reduce(
(current, option) => Object.assign(current, { [option.name]: option }),
{}
);
const apiDetailedOptionMap = detailedOptions.reduce(
(current, option) =>
option.forwardToApi && option.forwardToApi !== option.name
? Object.assign(current, { [option.forwardToApi]: option })
: current,
{}
);
module.exports = {
CATEGORY_CONFIG,
CATEGORY_EDITOR,
CATEGORY_FORMAT,
CATEGORY_OTHER,
CATEGORY_OUTPUT,
categoryOrder,
minimistOptions,
detailedOptions,
detailedOptionMap,
apiDetailedOptionMap,
options,
usageSummary
};

View File

@ -1,62 +1,69 @@
"use strict";
const minimist = require("minimist");
const prettier = eval("require")("../../index");
const constant = require("./constant");
const prettier = require("../../index");
const stringify = require("json-stable-stringify");
const util = require("./util");
const validator = require("./validator");
const logger = require("./logger");
function run(args) {
const rawArgv = minimist(args, constant.minimistOptions);
const context = util.createContext(args);
process.env[logger.ENV_LOG_LEVEL] =
rawArgv["loglevel"] || constant.detailedOptionMap["loglevel"].default;
try {
util.initContext(context);
const argv = util.normalizeConfig("cli", rawArgv);
context.logger.debug(`normalized argv: ${JSON.stringify(context.argv)}`);
logger.debug(`normalized argv: ${JSON.stringify(argv)}`);
if (context.argv["write"] && context.argv["debug-check"]) {
context.logger.error("Cannot use --write and --debug-check together.");
process.exit(1);
}
argv.__args = args;
argv.__filePatterns = argv["_"];
if (context.argv["find-config-path"] && context.filePatterns.length) {
context.logger.error("Cannot use --find-config-path with multiple files");
process.exit(1);
}
validator.validateArgv(argv);
if (context.argv["version"]) {
context.logger.log(prettier.version);
process.exit(0);
}
if (argv["version"]) {
logger.log(prettier.version);
process.exit(0);
}
if (context.argv["help"] !== undefined) {
context.logger.log(
typeof context.argv["help"] === "string" && context.argv["help"] !== ""
? util.createDetailedUsage(context, context.argv["help"])
: util.createUsage(context)
);
process.exit(0);
}
if (argv["help"] !== undefined) {
logger.log(
typeof argv["help"] === "string" && argv["help"] !== ""
? util.createDetailedUsage(argv["help"])
: util.createUsage()
);
process.exit(0);
}
if (context.argv["support-info"]) {
context.logger.log(
prettier.format(stringify(prettier.getSupportInfo()), {
parser: "json"
})
);
process.exit(0);
}
if (argv["support-info"]) {
logger.log(
prettier.format(JSON.stringify(prettier.getSupportInfo()), {
parser: "json"
})
);
process.exit(0);
}
const hasFilePatterns = context.filePatterns.length !== 0;
const useStdin =
context.argv["stdin"] || (!hasFilePatterns && !process.stdin.isTTY);
const hasFilePatterns = argv.__filePatterns.length !== 0;
const useStdin = argv["stdin"] || (!hasFilePatterns && !process.stdin.isTTY);
if (argv["find-config-path"]) {
util.logResolvedConfigPathOrDie(argv["find-config-path"]);
} else if (useStdin) {
util.formatStdin(argv);
} else if (hasFilePatterns) {
util.formatFiles(argv);
} else {
logger.log(util.createUsage());
if (context.argv["find-config-path"]) {
util.logResolvedConfigPathOrDie(
context,
context.argv["find-config-path"]
);
} else if (useStdin) {
util.formatStdin(context);
} else if (hasFilePatterns) {
util.formatFiles(context);
} else {
context.logger.log(util.createUsage(context));
process.exit(1);
}
} catch (error) {
context.logger.error(error.message);
process.exit(1);
}
}

View File

@ -1,57 +0,0 @@
"use strict";
const ENV_LOG_LEVEL = "PRETTIER_LOG_LEVEL";
const chalk = require("chalk");
const warn = createLogger("warn", "yellow");
const error = createLogger("error", "red");
const debug = createLogger("debug", "blue");
const log = createLogger("log");
function createLogger(loggerName, color) {
const prefix = color ? `[${chalk[color](loggerName)}] ` : "";
return function(message, opts) {
opts = Object.assign({ newline: true }, opts);
if (shouldLog(loggerName)) {
const stream = process[loggerName === "log" ? "stdout" : "stderr"];
stream.write(message.replace(/^/gm, prefix) + (opts.newline ? "\n" : ""));
}
};
}
function shouldLog(loggerName) {
const logLevel = process.env[ENV_LOG_LEVEL];
switch (logLevel) {
case "silent":
return false;
default:
return true;
case "debug":
if (loggerName === "debug") {
return true;
}
// fall through
case "log":
if (loggerName === "log") {
return true;
}
// fall through
case "warn":
if (loggerName === "warn") {
return true;
}
// fall through
case "error":
return loggerName === "error";
}
}
module.exports = {
warn,
error,
debug,
log,
ENV_LOG_LEVEL
};

35
src/cli/minimist.js Normal file
View File

@ -0,0 +1,35 @@
"use strict";
const minimist = require("minimist");
const PLACEHOLDER = null;
/**
* unspecified boolean flag without default value is parsed as `undefined` instead of `false`
*/
module.exports = function(args, options) {
const boolean = options.boolean || [];
const defaults = options.default || {};
const booleanWithoutDefault = boolean.filter(key => !(key in defaults));
const newDefaults = Object.assign(
{},
defaults,
booleanWithoutDefault.reduce(
(reduced, key) => Object.assign(reduced, { [key]: PLACEHOLDER }),
{}
)
);
const parsed = minimist(
args,
Object.assign({}, options, { default: newDefaults })
);
return Object.keys(parsed).reduce((reduced, key) => {
if (parsed[key] !== PLACEHOLDER) {
reduced[key] = parsed[key];
}
return reduced;
}, {});
};

File diff suppressed because it is too large Load Diff

View File

@ -1,55 +0,0 @@
"use strict";
const camelCase = require("camelcase");
const logger = require("./logger");
function validateArgv(argv) {
if (argv["write"] && argv["debug-check"]) {
logger.error("Cannot use --write and --debug-check together.");
process.exit(1);
}
if (argv["find-config-path"] && argv.__filePatterns.length) {
logger.error("Cannot use --find-config-path with multiple files");
process.exit(1);
}
}
function getOptionName(type, option) {
return type === "cli" ? `--${option.name}` : camelCase(option.name);
}
function validateIntOption(type, value, option) {
if (!/^\d+$/.test(value) || (type === "api" && typeof value !== "number")) {
const optionName = getOptionName(type, option);
throw new Error(
`Invalid ${optionName} value.\n` +
`Expected an integer, but received: ${JSON.stringify(value)}`
);
}
}
function validateChoiceOption(type, value, option) {
if (!option.choices.some(choice => choice.value === value)) {
const optionName = getOptionName(type, option);
throw new Error(
`Invalid option for ${optionName}.\n` +
`Expected ${getJoinedChoices()}, but received: ${JSON.stringify(value)}`
);
}
function getJoinedChoices() {
const choices = option.choices
.filter(choice => !choice.deprecated)
.map(choice => `"${choice.value}"`);
const head = choices.slice(0, -2);
const tail = choices.slice(-2);
return head.concat(tail.join(" or ")).join(", ");
}
}
module.exports = {
validateArgv,
validateIntOption,
validateChoiceOption
};

View File

@ -1,8 +1,6 @@
"use strict";
const assert = require("assert");
const util = require("../common/util");
const startsWithNoLookaheadToken = util.startsWithNoLookaheadToken;
function FastPath(value) {
assert.ok(this instanceof FastPath);
@ -131,541 +129,4 @@ FastPath.prototype.map = function map(callback /*, name1, name2, ... */) {
return result;
};
FastPath.prototype.needsParens = function(options) {
const parent = this.getParentNode();
if (!parent) {
return false;
}
const name = this.getName();
const node = this.getNode();
// If the value of this path is some child of a Node and not a Node
// itself, then it doesn't need parentheses. Only Node objects (in
// fact, only Expression nodes) need parentheses.
if (this.getValue() !== node) {
return false;
}
// Only statements don't need parentheses.
if (isStatement(node)) {
return false;
}
// Closure compiler requires that type casted expressions to be surrounded by
// parentheses.
if (util.hasClosureCompilerTypeCastComment(options.originalText, node)) {
return true;
}
// Identifiers never need parentheses.
if (node.type === "Identifier") {
return false;
}
if (parent.type === "ParenthesizedExpression") {
return false;
}
// Add parens around the extends clause of a class. It is needed for almost
// all expressions.
if (
(parent.type === "ClassDeclaration" || parent.type === "ClassExpression") &&
parent.superClass === node &&
(node.type === "ArrowFunctionExpression" ||
node.type === "AssignmentExpression" ||
node.type === "AwaitExpression" ||
node.type === "BinaryExpression" ||
node.type === "ConditionalExpression" ||
node.type === "LogicalExpression" ||
node.type === "NewExpression" ||
node.type === "ObjectExpression" ||
node.type === "ParenthesizedExpression" ||
node.type === "SequenceExpression" ||
node.type === "TaggedTemplateExpression" ||
node.type === "UnaryExpression" ||
node.type === "UpdateExpression" ||
node.type === "YieldExpression")
) {
return true;
}
if (
(parent.type === "ArrowFunctionExpression" &&
parent.body === node &&
node.type !== "SequenceExpression" && // these have parens added anyway
startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ false)) ||
(parent.type === "ExpressionStatement" &&
startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ true))
) {
return true;
}
switch (node.type) {
case "CallExpression": {
let firstParentNotMemberExpression = parent;
let i = 0;
while (
firstParentNotMemberExpression &&
firstParentNotMemberExpression.type === "MemberExpression"
) {
firstParentNotMemberExpression = this.getParentNode(++i);
}
if (
firstParentNotMemberExpression.type === "NewExpression" &&
firstParentNotMemberExpression.callee === this.getParentNode(i - 1)
) {
return true;
}
return false;
}
case "SpreadElement":
case "SpreadProperty":
return (
parent.type === "MemberExpression" &&
name === "object" &&
parent.object === node
);
case "UpdateExpression":
if (parent.type === "UnaryExpression") {
return (
node.prefix &&
((node.operator === "++" && parent.operator === "+") ||
(node.operator === "--" && parent.operator === "-"))
);
}
// else fallthrough
case "UnaryExpression":
switch (parent.type) {
case "UnaryExpression":
return (
node.operator === parent.operator &&
(node.operator === "+" || node.operator === "-")
);
case "MemberExpression":
return name === "object" && parent.object === node;
case "TaggedTemplateExpression":
return true;
case "NewExpression":
case "CallExpression":
return name === "callee" && parent.callee === node;
case "BinaryExpression":
return parent.operator === "**" && name === "left";
default:
return false;
}
case "BinaryExpression": {
if (parent.type === "UpdateExpression") {
return true;
}
const isLeftOfAForStatement = node => {
let i = 0;
while (node) {
const parent = this.getParentNode(i++);
if (!parent) {
return false;
}
if (parent.type === "ForStatement" && parent.init === node) {
return true;
}
node = parent;
}
return false;
};
if (node.operator === "in" && isLeftOfAForStatement(node)) {
return true;
}
}
// fallthrough
case "TSTypeAssertionExpression":
case "TSAsExpression":
case "LogicalExpression":
switch (parent.type) {
case "ConditionalExpression":
return node.type === "TSAsExpression";
case "CallExpression":
case "NewExpression":
return name === "callee" && parent.callee === node;
case "ClassDeclaration":
case "TSAbstractClassDeclaration":
return name === "superClass" && parent.superClass === node;
case "TSTypeAssertionExpression":
case "TaggedTemplateExpression":
case "UnaryExpression":
case "SpreadElement":
case "SpreadProperty":
case "ExperimentalSpreadProperty":
case "BindExpression":
case "AwaitExpression":
case "TSAsExpression":
case "TSNonNullExpression":
return true;
case "MemberExpression":
return name === "object" && parent.object === node;
case "AssignmentExpression":
return (
parent.left === node &&
(node.type === "TSTypeAssertionExpression" ||
node.type === "TSAsExpression")
);
case "Decorator":
return (
parent.expression === node &&
(node.type === "TSTypeAssertionExpression" ||
node.type === "TSAsExpression")
);
case "BinaryExpression":
case "LogicalExpression": {
if (!node.operator && node.type !== "TSTypeAssertionExpression") {
return true;
}
const po = parent.operator;
const pp = util.getPrecedence(po);
const no = node.operator;
const np = util.getPrecedence(no);
if (pp > np) {
return true;
}
if ((po === "||" || po === "??") && no === "&&") {
return true;
}
if (pp === np && name === "right") {
assert.strictEqual(parent.right, node);
return true;
}
if (pp === np && !util.shouldFlatten(po, no)) {
return true;
}
// Add parenthesis when working with binary operators
// It's not stricly needed but helps with code understanding
if (util.isBitwiseOperator(po)) {
return true;
}
return false;
}
default:
return false;
}
case "TSParenthesizedType": {
const grandParent = this.getParentNode(1);
if (
(parent.type === "TSTypeParameter" ||
parent.type === "TypeParameter" ||
parent.type === "VariableDeclarator" ||
parent.type === "TSTypeAnnotation" ||
parent.type === "GenericTypeAnnotation" ||
parent.type === "TSTypeReference") &&
(node.typeAnnotation.type === "TSTypeAnnotation" &&
node.typeAnnotation.typeAnnotation.type !== "TSFunctionType" &&
grandParent.type !== "TSTypeOperator")
) {
return false;
}
// Delegate to inner TSParenthesizedType
if (node.typeAnnotation.type === "TSParenthesizedType") {
return false;
}
return true;
}
case "SequenceExpression":
switch (parent.type) {
case "ReturnStatement":
return false;
case "ForStatement":
// Although parentheses wouldn't hurt around sequence
// expressions in the head of for loops, traditional style
// dictates that e.g. i++, j++ should not be wrapped with
// parentheses.
return false;
case "ExpressionStatement":
return name !== "expression";
case "ArrowFunctionExpression":
// We do need parentheses, but SequenceExpressions are handled
// specially when printing bodies of arrow functions.
return name !== "body";
default:
// Otherwise err on the side of overparenthesization, adding
// explicit exceptions above if this proves overzealous.
return true;
}
case "YieldExpression":
if (
parent.type === "UnaryExpression" ||
parent.type === "AwaitExpression" ||
parent.type === "TSAsExpression" ||
parent.type === "TSNonNullExpression"
) {
return true;
}
// else fallthrough
case "AwaitExpression":
switch (parent.type) {
case "TaggedTemplateExpression":
case "BinaryExpression":
case "LogicalExpression":
case "SpreadElement":
case "SpreadProperty":
case "TSAsExpression":
case "TSNonNullExpression":
return true;
case "MemberExpression":
return parent.object === node;
case "NewExpression":
case "CallExpression":
return parent.callee === node;
case "ConditionalExpression":
return parent.test === node;
default:
return false;
}
case "ArrayTypeAnnotation":
return parent.type === "NullableTypeAnnotation";
case "IntersectionTypeAnnotation":
case "UnionTypeAnnotation":
return (
parent.type === "ArrayTypeAnnotation" ||
parent.type === "NullableTypeAnnotation" ||
parent.type === "IntersectionTypeAnnotation" ||
parent.type === "UnionTypeAnnotation"
);
case "NullableTypeAnnotation":
return parent.type === "ArrayTypeAnnotation";
case "FunctionTypeAnnotation":
return (
parent.type === "UnionTypeAnnotation" ||
parent.type === "IntersectionTypeAnnotation" ||
parent.type === "ArrayTypeAnnotation"
);
case "StringLiteral":
case "NumericLiteral":
case "Literal":
if (
typeof node.value === "string" &&
parent.type === "ExpressionStatement" &&
// TypeScript workaround for eslint/typescript-eslint-parser#267
// See corresponding workaround in printer.js case: "Literal"
((options.parser !== "typescript" && !parent.directive) ||
(options.parser === "typescript" &&
options.originalText.substr(util.locStart(node) - 1, 1) === "("))
) {
// To avoid becoming a directive
const grandParent = this.getParentNode(1);
return (
grandParent.type === "Program" ||
grandParent.type === "BlockStatement"
);
}
return (
parent.type === "MemberExpression" &&
typeof node.value === "number" &&
name === "object" &&
parent.object === node
);
case "AssignmentExpression": {
const grandParent = this.getParentNode(1);
if (parent.type === "ArrowFunctionExpression" && parent.body === node) {
return true;
} else if (
parent.type === "ClassProperty" &&
parent.key === node &&
parent.computed
) {
return false;
} else if (
parent.type === "TSPropertySignature" &&
parent.name === node
) {
return false;
} else if (
parent.type === "ForStatement" &&
(parent.init === node || parent.update === node)
) {
return false;
} else if (parent.type === "ExpressionStatement") {
return node.left.type === "ObjectPattern";
} else if (parent.type === "TSPropertySignature" && parent.key === node) {
return false;
} else if (parent.type === "AssignmentExpression") {
return false;
} else if (
parent.type === "SequenceExpression" &&
grandParent &&
grandParent.type === "ForStatement" &&
(grandParent.init === parent || grandParent.update === parent)
) {
return false;
}
return true;
}
case "ConditionalExpression":
switch (parent.type) {
case "TaggedTemplateExpression":
case "UnaryExpression":
case "SpreadElement":
case "SpreadProperty":
case "BinaryExpression":
case "LogicalExpression":
case "ExportDefaultDeclaration":
case "AwaitExpression":
case "JSXSpreadAttribute":
case "TSTypeAssertionExpression":
case "TSAsExpression":
case "TSNonNullExpression":
return true;
case "NewExpression":
case "CallExpression":
return name === "callee" && parent.callee === node;
case "ConditionalExpression":
return name === "test" && parent.test === node;
case "MemberExpression":
return name === "object" && parent.object === node;
default:
return false;
}
case "FunctionExpression":
switch (parent.type) {
case "CallExpression":
return name === "callee"; // Not strictly necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses.
case "TaggedTemplateExpression":
return true; // This is basically a kind of IIFE.
case "ExportDefaultDeclaration":
return true;
default:
return false;
}
case "ArrowFunctionExpression":
switch (parent.type) {
case "CallExpression":
return name === "callee";
case "NewExpression":
return name === "callee";
case "MemberExpression":
return name === "object";
case "TSAsExpression":
case "BindExpression":
case "TaggedTemplateExpression":
case "UnaryExpression":
case "LogicalExpression":
case "BinaryExpression":
case "AwaitExpression":
case "TSTypeAssertionExpression":
return true;
case "ConditionalExpression":
return name === "test";
default:
return false;
}
case "ClassExpression":
return parent.type === "ExportDefaultDeclaration";
}
return false;
};
function isStatement(node) {
return (
node.type === "BlockStatement" ||
node.type === "BreakStatement" ||
node.type === "ClassBody" ||
node.type === "ClassDeclaration" ||
node.type === "ClassMethod" ||
node.type === "ClassProperty" ||
node.type === "ClassPrivateProperty" ||
node.type === "ContinueStatement" ||
node.type === "DebuggerStatement" ||
node.type === "DeclareClass" ||
node.type === "DeclareExportAllDeclaration" ||
node.type === "DeclareExportDeclaration" ||
node.type === "DeclareFunction" ||
node.type === "DeclareInterface" ||
node.type === "DeclareModule" ||
node.type === "DeclareModuleExports" ||
node.type === "DeclareVariable" ||
node.type === "DoWhileStatement" ||
node.type === "ExportAllDeclaration" ||
node.type === "ExportDefaultDeclaration" ||
node.type === "ExportNamedDeclaration" ||
node.type === "ExpressionStatement" ||
node.type === "ForAwaitStatement" ||
node.type === "ForInStatement" ||
node.type === "ForOfStatement" ||
node.type === "ForStatement" ||
node.type === "FunctionDeclaration" ||
node.type === "IfStatement" ||
node.type === "ImportDeclaration" ||
node.type === "InterfaceDeclaration" ||
node.type === "LabeledStatement" ||
node.type === "MethodDefinition" ||
node.type === "ReturnStatement" ||
node.type === "SwitchStatement" ||
node.type === "ThrowStatement" ||
node.type === "TryStatement" ||
node.type === "TSAbstractClassDeclaration" ||
node.type === "TSEnumDeclaration" ||
node.type === "TSImportEqualsDeclaration" ||
node.type === "TSInterfaceDeclaration" ||
node.type === "TSModuleDeclaration" ||
node.type === "TSNamespaceExportDeclaration" ||
node.type === "TypeAlias" ||
node.type === "VariableDeclaration" ||
node.type === "WhileStatement" ||
node.type === "WithStatement"
);
}
module.exports = FastPath;

View File

@ -1,9 +1,10 @@
"use strict";
const resolve = require("resolve");
const readPkgUp = require("read-pkg-up");
function loadPlugins(options) {
options = Object.assign({ plugins: [] }, options);
function loadPlugins(plugins) {
plugins = plugins || [];
const internalPlugins = [
require("../language-js"),
@ -13,20 +14,47 @@ function loadPlugins(options) {
require("../language-markdown"),
require("../language-html"),
require("../language-vue")
].filter(plugin => {
return options.plugins.indexOf(plugin) < 0;
});
];
const externalPlugins = options.plugins.map(plugin => {
if (typeof plugin !== "string") {
return plugin;
const externalPlugins = plugins
.concat(
getPluginsFromPackage(
readPkgUp.sync({
normalize: false
}).pkg
)
)
.map(plugin => {
if (typeof plugin !== "string") {
return plugin;
}
const pluginPath = resolve.sync(plugin, { basedir: process.cwd() });
return Object.assign({ name: plugin }, eval("require")(pluginPath));
});
return deduplicate(internalPlugins.concat(externalPlugins));
}
function getPluginsFromPackage(pkg) {
if (!pkg) {
return [];
}
const deps = Object.assign({}, pkg.dependencies, pkg.devDependencies);
return Object.keys(deps).filter(
dep =>
dep.startsWith("prettier-plugin-") || dep.startsWith("@prettier/plugin-")
);
}
function deduplicate(items) {
const uniqItems = [];
for (const item of items) {
if (uniqItems.indexOf(item) < 0) {
uniqItems.push(item);
}
const pluginPath = resolve.sync(plugin, { basedir: process.cwd() });
return eval("require")(pluginPath);
});
return internalPlugins.concat(externalPlugins);
}
return uniqItems;
}
module.exports = loadPlugins;

View File

@ -5,6 +5,7 @@ const dedent = require("dedent");
const semver = require("semver");
const currentVersion = require("../../package.json").version;
const loadPlugins = require("./load-plugins");
const cliConstant = require("../cli/constant");
const CATEGORY_GLOBAL = "Global";
const CATEGORY_SPECIAL = "Special";
@ -14,6 +15,7 @@ const CATEGORY_SPECIAL = "Special";
* @property {string} since - available since version
* @property {string} category
* @property {'int' | 'boolean' | 'choice' | 'path'} type
* @property {boolean} array - indicate it's an array of the specified type
* @property {boolean?} deprecated - deprecated since version
* @property {OptionRedirectInfo?} redirect - redirect deprecated option
* @property {string} description
@ -21,9 +23,10 @@ const CATEGORY_SPECIAL = "Special";
* @property {OptionValueInfo} default
* @property {OptionRangeInfo?} range - for type int
* @property {OptionChoiceInfo?} choices - for type choice
* @property {(value: any) => boolean} exception
*
* @typedef {number | boolean | string} OptionValue
* @typedef {OptionValue | Array<{ since: string, value: OptionValue}>} OptionValueInfo
* @typedef {OptionValue | [{ value: OptionValue[] }] | Array<{ since: string, value: OptionValue}>} OptionValueInfo
*
* @typedef {Object} OptionRedirectInfo
* @property {string} option
@ -40,6 +43,10 @@ const CATEGORY_SPECIAL = "Special";
* @property {string?} since - undefined if available since the first version of the option
* @property {string?} deprecated - deprecated since version
* @property {OptionValueInfo?} redirect - redirect deprecated value
*
* @property {string?} cliName
* @property {string?} cliCategory
* @property {string?} cliDescription
*/
/** @type {{ [name: string]: OptionInfo } */
const supportOptions = {
@ -52,7 +59,8 @@ const supportOptions = {
description: dedent`
Print (to stderr) where a cursor at the given position would move to after formatting.
This option cannot be used with --range-start and --range-end.
`
`,
cliCategory: cliConstant.CATEGORY_EDITOR
},
filepath: {
since: "1.4.0",
@ -60,14 +68,18 @@ const supportOptions = {
type: "path",
default: undefined,
description:
"Specify the input filepath. This will be used to do parser inference."
"Specify the input filepath. This will be used to do parser inference.",
cliName: "stdin-filepath",
cliCategory: cliConstant.CATEGORY_OTHER,
cliDescription: "Path to the file to pretend that stdin comes from."
},
insertPragma: {
since: "1.8.0",
category: CATEGORY_SPECIAL,
type: "boolean",
default: false,
description: "Insert @format pragma into file's first docblock comment."
description: "Insert @format pragma into file's first docblock comment.",
cliCategory: cliConstant.CATEGORY_OTHER
},
parser: {
since: "0.0.10",
@ -75,9 +87,11 @@ const supportOptions = {
type: "choice",
default: "babylon",
description: "Which parser to use.",
exception: value =>
typeof value === "string" || typeof value === "function",
choices: [
{ value: "babylon", description: "JavaScript" },
{ value: "flow", description: "Flow" },
{ value: "babylon", description: "JavaScript" },
{ value: "typescript", since: "1.4.0", description: "TypeScript" },
{ value: "css", since: "1.7.1", description: "CSS" },
{
@ -91,9 +105,22 @@ const supportOptions = {
{ value: "scss", since: "1.7.1", description: "SCSS" },
{ value: "json", since: "1.5.0", description: "JSON" },
{ value: "graphql", since: "1.5.0", description: "GraphQL" },
{ value: "markdown", since: "1.8.0", description: "Markdown" }
{ value: "markdown", since: "1.8.0", description: "Markdown" },
{ value: "vue", since: "1.10.0", description: "Vue" }
]
},
plugins: {
since: "1.10.0",
type: "path",
array: true,
default: [{ value: [] }],
category: CATEGORY_GLOBAL,
description:
"Add a plugin. Multiple plugins can be passed as separate `--plugin`s.",
exception: value => typeof value === "string" || typeof value === "object",
cliName: "plugin",
cliCategory: cliConstant.CATEGORY_CONFIG
},
printWidth: {
since: "0.0.0",
category: CATEGORY_GLOBAL,
@ -112,7 +139,8 @@ const supportOptions = {
Format code ending at a given character offset (exclusive).
The range will extend forwards to the end of the selected statement.
This option cannot be used with --cursor-offset.
`
`,
cliCategory: cliConstant.CATEGORY_EDITOR
},
rangeStart: {
since: "1.4.0",
@ -124,7 +152,8 @@ const supportOptions = {
Format code starting at a given character offset.
The range will extend backwards to the start of the first line containing the selected statement.
This option cannot be used with --cursor-offset.
`
`,
cliCategory: cliConstant.CATEGORY_EDITOR
},
requirePragma: {
since: "1.7.0",
@ -134,7 +163,8 @@ const supportOptions = {
description: dedent`
Require either '@prettier' or '@format' to be present in the file's first docblock comment
in order for it to be formatted.
`
`,
cliCategory: cliConstant.CATEGORY_OTHER
},
tabWidth: {
type: "int",
@ -150,7 +180,8 @@ const supportOptions = {
default: false,
deprecated: "0.0.10",
description: "Use flow parser.",
redirect: { option: "parser", value: "flow" }
redirect: { option: "parser", value: "flow" },
cliName: "flow-parser"
},
useTabs: {
since: "1.0.0",
@ -162,32 +193,31 @@ const supportOptions = {
};
function getSupportInfo(version, opts) {
opts = opts || {};
opts = Object.assign(
{
plugins: [],
pluginsLoaded: false,
showUnreleased: false,
showDeprecated: false,
showInternal: false
},
opts
);
if (!version) {
version = currentVersion;
}
const plugins = loadPlugins();
const plugins = opts.pluginsLoaded ? opts.plugins : loadPlugins(opts.plugins);
const options = util
.arrayify(
Object.assign(
plugins
.reduce(
(currentPrinters, plugin) =>
currentPrinters.concat(
Object.keys(plugin.printers).map(
printerName => plugin.printers[printerName]
)
),
[]
)
.reduce(
(currentOptions, printer) =>
Object.assign(currentOptions, printer.options),
{}
),
plugins.reduce(
(currentOptions, plugin) =>
Object.assign(currentOptions, plugin.options),
{}
),
supportOptions
),
"name"
@ -196,15 +226,19 @@ function getSupportInfo(version, opts) {
.filter(filterSince)
.filter(filterDeprecated)
.map(mapDeprecated)
.map(mapInternal)
.map(option => {
const newOption = Object.assign({}, option);
if (Array.isArray(newOption.default)) {
newOption.default = newOption.default
.filter(filterSince)
.sort((info1, info2) =>
semver.compare(info2.since, info1.since)
)[0].value;
newOption.default =
newOption.default.length === 1
? newOption.default[0].value
: newOption.default
.filter(filterSince)
.sort((info1, info2) =>
semver.compare(info2.since, info1.since)
)[0].value;
}
if (Array.isArray(newOption.choices)) {
@ -215,13 +249,28 @@ function getSupportInfo(version, opts) {
}
return newOption;
})
.map(option => {
const filteredPlugins = plugins.filter(
plugin => plugin.defaultOptions && plugin.defaultOptions[option.name]
);
const pluginDefaults = filteredPlugins.reduce((reduced, plugin) => {
reduced[plugin.name] = plugin.defaultOptions[option.name];
return reduced;
}, {});
return Object.assign(option, { pluginDefaults });
});
const usePostCssParser = semver.lt(version, "1.7.1");
const languages = plugins
.reduce((all, plugin) => all.concat(plugin.languages), [])
.filter(language => language.since && semver.gte(version, language.since))
.filter(
language =>
language.since
? semver.gte(version, language.since)
: language.since !== null
)
.map(language => {
// Prevent breaking changes
if (language.name === "Markdown") {
@ -268,6 +317,16 @@ function getSupportInfo(version, opts) {
delete newObject.redirect;
return newObject;
}
function mapInternal(object) {
if (opts.showInternal) {
return object;
}
const newObject = Object.assign({}, object);
delete newObject.cliName;
delete newObject.cliCategory;
delete newObject.cliDescription;
return newObject;
}
}
module.exports = {

26
src/common/util-shared.js Normal file
View File

@ -0,0 +1,26 @@
"use strict";
const util = require("./util");
function isNextLineEmpty(text, node, options) {
return util.isNextLineEmpty(text, node, options.locEnd);
}
function getNextNonSpaceNonCommentCharacterIndex(text, node, options) {
return util.getNextNonSpaceNonCommentCharacterIndex(
text,
node,
options.locEnd
);
}
module.exports = {
isNextLineEmpty,
isNextLineEmptyAfterIndex: util.isNextLineEmptyAfterIndex,
getNextNonSpaceNonCommentCharacterIndex,
mapDoc: util.mapDoc,
makeString: util.makeString,
addLeadingComment: util.addLeadingComment,
addDanglingComment: util.addDanglingComment,
addTrailingComment: util.addTrailingComment
};

View File

@ -187,7 +187,7 @@ function hasNewlineInRange(text, start, end) {
}
// Note: this function doesn't ignore leading comments unlike isNextLineEmpty
function isPreviousLineEmpty(text, node) {
function isPreviousLineEmpty(text, node, locStart) {
let idx = locStart(node) - 1;
idx = skipSpaces(text, idx, { backwards: true });
idx = skipNewline(text, idx, { backwards: true });
@ -211,11 +211,11 @@ function isNextLineEmptyAfterIndex(text, index) {
return hasNewline(text, idx);
}
function isNextLineEmpty(text, node) {
function isNextLineEmpty(text, node, locEnd) {
return isNextLineEmptyAfterIndex(text, locEnd(node));
}
function getNextNonSpaceNonCommentCharacterIndex(text, node) {
function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) {
let oldIdx = null;
let idx = locEnd(node);
while (idx !== oldIdx) {
@ -228,8 +228,10 @@ function getNextNonSpaceNonCommentCharacterIndex(text, node) {
return idx;
}
function getNextNonSpaceNonCommentCharacter(text, node) {
return text.charAt(getNextNonSpaceNonCommentCharacterIndex(text, node));
function getNextNonSpaceNonCommentCharacter(text, node, locEnd) {
return text.charAt(
getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd)
);
}
function hasSpaces(text, index, opts) {
@ -238,65 +240,6 @@ function hasSpaces(text, index, opts) {
return idx !== index;
}
function locStart(node) {
// Handle nodes with decorators. They should start at the first decorator
if (
node.declaration &&
node.declaration.decorators &&
node.declaration.decorators.length > 0
) {
return locStart(node.declaration.decorators[0]);
}
if (node.decorators && node.decorators.length > 0) {
return locStart(node.decorators[0]);
}
if (node.__location) {
return node.__location.startOffset;
}
if (node.range) {
return node.range[0];
}
if (typeof node.start === "number") {
return node.start;
}
if (node.source) {
return lineColumnToIndex(node.source.start, node.source.input.css) - 1;
}
if (node.loc) {
return node.loc.start;
}
}
function locEnd(node) {
const endNode = node.nodes && getLast(node.nodes);
if (endNode && node.source && !node.source.end) {
node = endNode;
}
let loc;
if (node.range) {
loc = node.range[1];
} else if (typeof node.end === "number") {
loc = node.end;
} else if (node.source) {
loc = lineColumnToIndex(node.source.end, node.source.input.css);
}
if (node.__location) {
return node.__location.endOffset;
}
if (node.typeAnnotation) {
return Math.max(loc, locEnd(node.typeAnnotation));
}
if (node.loc && !loc) {
return node.loc.end;
}
return loc;
}
// Super inefficient, needs to be cached.
function lineColumnToIndex(lineColumn, text) {
let index = 0;
@ -476,7 +419,7 @@ function isBlockComment(comment) {
return comment.type === "Block" || comment.type === "CommentBlock";
}
function hasClosureCompilerTypeCastComment(text, node) {
function hasClosureCompilerTypeCastComment(text, node, locEnd) {
// https://github.com/google/closure-compiler/wiki/Annotating-Types#type-casts
// Syntax example: var x = /** @type {string} */ (fruit);
return (
@ -486,7 +429,7 @@ function hasClosureCompilerTypeCastComment(text, node) {
comment.leading &&
isBlockComment(comment) &&
comment.value.match(/^\*\s*@type\s*{[^}]+}\s*$/) &&
getNextNonSpaceNonCommentCharacter(text, comment) === "("
getNextNonSpaceNonCommentCharacter(text, comment, locEnd) === "("
)
);
}
@ -813,6 +756,37 @@ function arrayify(object, keyName) {
);
}
function addCommentHelper(node, comment) {
const comments = node.comments || (node.comments = []);
comments.push(comment);
comment.printed = false;
// For some reason, TypeScript parses `// x` inside of JSXText as a comment
// We already "print" it via the raw text, we don't need to re-print it as a
// comment
if (node.type === "JSXText") {
comment.printed = true;
}
}
function addLeadingComment(node, comment) {
comment.leading = true;
comment.trailing = false;
addCommentHelper(node, comment);
}
function addDanglingComment(node, comment) {
comment.leading = false;
comment.trailing = false;
addCommentHelper(node, comment);
}
function addTrailingComment(node, comment) {
comment.leading = false;
comment.trailing = true;
addCommentHelper(node, comment);
}
module.exports = {
arrayify,
punctuationRegex,
@ -839,8 +813,6 @@ module.exports = {
hasNewline,
hasNewlineInRange,
hasSpaces,
locStart,
locEnd,
setLocStart,
setLocEnd,
startsWithNoLookaheadToken,
@ -852,5 +824,10 @@ module.exports = {
printString,
printNumber,
hasIgnoreComment,
hasNodeIgnoreComment
hasNodeIgnoreComment,
lineColumnToIndex,
makeString,
addLeadingComment,
addDanglingComment,
addTrailingComment
};

View File

@ -9,7 +9,7 @@ const findProjectRoot = require("find-project-root");
const maybeParse = (filePath, config, parse) => {
const root = findProjectRoot(path.dirname(path.resolve(filePath)));
return filePath && !config && parse(filePath, { root });
return filePath && parse(filePath, { root });
};
const editorconfigAsyncNoCache = (filePath, config) => {

View File

@ -56,6 +56,18 @@ function group(contents, opts) {
};
}
function dedentToRoot(contents) {
return align(-Infinity, contents);
}
function markAsRoot(contents) {
return align({ type: "root" }, contents);
}
function dedent(contents) {
return align(-1, contents);
}
function conditionalGroup(states, opts) {
return group(
states[0],
@ -149,5 +161,8 @@ module.exports = {
ifBreak,
indent,
align,
addAlignmentToDoc
addAlignmentToDoc,
markAsRoot,
dedentToRoot,
dedent
};

View File

@ -68,7 +68,17 @@ function printDoc(doc) {
}
if (doc.type === "align") {
return "align(" + doc.n + ", " + printDoc(doc.contents) + ")";
return doc.n === -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") {

View File

@ -10,33 +10,103 @@ const MODE_BREAK = 1;
const MODE_FLAT = 2;
function rootIndent() {
return {
length: 0,
value: ""
};
return { value: "", length: 0, queue: [] };
}
function makeIndent(ind, options) {
return {
length: ind.length + options.tabWidth,
value: ind.value + (options.useTabs ? "\t" : " ".repeat(options.tabWidth))
};
return generateInd(ind, { type: "indent" }, options);
}
function makeAlign(ind, n, options) {
return n === -Infinity
? rootIndent()
: typeof n === "string"
? {
length: ind.length + n.length,
value: ind.value + n
? ind.root || rootIndent()
: n < 0
? generateInd(ind, { type: "dedent" }, options)
: !n
? ind
: n.type === "root"
? Object.assign({}, ind, { root: ind })
: typeof n === "string"
? generateInd(ind, { type: "stringAlign", n }, options)
: generateInd(ind, { type: "numberAlign", n }, options);
}
function generateInd(ind, newPart, options) {
const queue =
newPart.type === "dedent"
? ind.queue.slice(0, -1)
: ind.queue.concat(newPart);
let value = "";
let length = 0;
let lastTabs = 0;
let lastSpaces = 0;
for (const part of queue) {
switch (part.type) {
case "indent":
flush();
if (options.useTabs) {
addTabs(1);
} else {
addSpaces(options.tabWidth);
}
: options.useTabs && n > 0
? makeIndent(ind, options)
: {
length: ind.length + n,
value: ind.value + " ".repeat(n)
};
break;
case "stringAlign":
flush();
value += part.n;
length += part.n.length;
break;
case "numberAlign":
lastTabs += 1;
lastSpaces += part.n;
break;
/* istanbul ignore next */
default:
throw new Error(`Unexpected type '${part.type}'`);
}
}
flushSpaces();
return Object.assign({}, ind, { value, length, queue });
function addTabs(count) {
value += "\t".repeat(count);
length += options.tabWidth * count;
}
function addSpaces(count) {
value += " ".repeat(count);
length += count;
}
function flush() {
if (options.useTabs) {
flushTabs();
} else {
flushSpaces();
}
}
function flushTabs() {
if (lastTabs > 0) {
addTabs(lastTabs);
}
resetLast();
}
function flushSpaces() {
if (lastSpaces > 0) {
addSpaces(lastSpaces);
}
resetLast();
}
function resetLast() {
lastTabs = 0;
lastSpaces = 0;
}
}
function fits(next, restCommands, width, options, mustBeFlat) {
@ -380,8 +450,13 @@ function printDocToString(doc, options) {
}
if (doc.literal) {
out.push(newLine);
pos = 0;
if (ind.root) {
out.push(newLine, ind.root.value);
pos = ind.root.length;
} else {
out.push(newLine);
pos = 0;
}
} else {
if (out.length > 0) {
// Trim whitespace at the end of line

View File

@ -1,5 +1,7 @@
"use strict";
const htmlTagNames = require("html-tag-names");
function clean(ast, newObj) {
if (
ast.type === "media-query" ||
@ -22,7 +24,11 @@ function clean(ast, newObj) {
}
if (
(ast.type === "value-word" && ast.isColor && ast.isHex) ||
(ast.type === "value-word" &&
((ast.isColor && ast.isHex) ||
["initial", "inherit", "unset", "revert"].indexOf(
newObj.value.replace().toLowerCase()
) !== -1)) ||
ast.type === "media-feature" ||
ast.type === "selector-root-invalid" ||
ast.type === "selector-pseudo"
@ -61,9 +67,23 @@ function clean(ast, newObj) {
newObj.importPath = cleanCSSStrings(newObj.importPath);
}
if (ast.type === "selector-attribute" && newObj.value) {
newObj.value = newObj.value.replace(/^['"]|['"]$/g, "");
delete newObj.quoted;
if (ast.type === "selector-attribute") {
newObj.attribute = newObj.attribute.trim();
if (newObj.namespace) {
if (typeof newObj.namespace === "string") {
newObj.namespace = newObj.namespace.trim();
if (newObj.namespace.length === 0) {
newObj.namespace = true;
}
}
}
if (newObj.value) {
newObj.value = newObj.value.trim().replace(/^['"]|['"]$/g, "");
delete newObj.quoted;
}
}
if (
@ -84,6 +104,24 @@ function clean(ast, newObj) {
}
);
}
if (ast.type === "media-url") {
newObj.value = newObj.value
.replace(/^url\(\s+/gi, "url(")
.replace(/\s+\)$/gi, ")");
}
if (ast.type === "selector-tag") {
const lowercasedValue = ast.value.toLowerCase();
if (htmlTagNames.indexOf(lowercasedValue) !== -1) {
newObj.value = lowercasedValue;
}
if (["from", "to"].indexOf(lowercasedValue) !== -1) {
newObj.value = lowercasedValue;
}
}
}
function cleanCSSStrings(value) {

View File

@ -1,6 +1,11 @@
"use strict";
const printer = require("./printer-postcss");
const options = require("./options");
const privateUtil = require("../common/util");
const lineColumnToIndex = privateUtil.lineColumnToIndex;
const getLast = privateUtil.getLast;
// Based on:
// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
@ -51,7 +56,23 @@ const postcss = {
get parse() {
return eval("require")("./parser-postcss");
},
astFormat: "postcss"
astFormat: "postcss",
locEnd: function(node) {
const endNode = node.nodes && getLast(node.nodes);
if (endNode && node.source && !node.source.end) {
node = endNode;
}
if (node.source) {
return lineColumnToIndex(node.source.end, node.source.input.css);
}
return null;
},
locStart: function(node) {
if (node.source) {
return lineColumnToIndex(node.source.start, node.source.input.css) - 1;
}
return null;
}
};
// TODO: switch these to just `postcss` and use `language` instead.
@ -67,6 +88,7 @@ const printers = {
module.exports = {
languages,
options,
parsers,
printers
};

View File

@ -1,8 +1,19 @@
"use strict";
const createError = require("../common/parser-create-error");
const grayMatter = require("gray-matter");
function parseSelector(selector) {
// If there's a comment inside of a selector, the parser tries to parse
// the content of the comment as selectors which turns it into complete
// garbage. Better to print the whole selector as-is and not try to parse
// and reformat it.
if (selector.match(/\/\/|\/\*/)) {
return {
type: "selector-comment",
value: selector.replace(/^ +/, "").replace(/ +$/, "")
};
}
const selectorParser = require("postcss-selector-parser");
let result;
selectorParser(result_ => {
@ -28,6 +39,23 @@ function parseValueNodes(nodes) {
for (let i = 0; i < nodes.length; ++i) {
const node = nodes[i];
const isUnquotedDataURLCall =
node.type === "func" &&
node.value === "url" &&
node.group &&
node.group.groups &&
node.group.groups[0] &&
node.group.groups[0].groups &&
node.group.groups[0].groups.length > 2 &&
node.group.groups[0].groups[0].type === "word" &&
node.group.groups[0].groups[0].value === "data" &&
node.group.groups[0].groups[1].type === "colon" &&
node.group.groups[0].groups[1].value === ":";
if (isUnquotedDataURLCall) {
node.group.groups = [stringifyGroup(node)];
}
if (node.type === "paren" && node.value === "(") {
parenGroup = {
open: node,
@ -75,6 +103,31 @@ function parseValueNodes(nodes) {
return rootParenGroup;
}
function stringifyGroup(node) {
if (node.group) {
return stringifyGroup(node.group);
}
if (node.groups) {
return node.groups.reduce((previousValue, currentValue, index) => {
return (
previousValue +
stringifyGroup(currentValue) +
(currentValue.type === "comma_group" && index !== node.groups.length - 1
? ","
: "")
);
}, "");
}
const before = node.raws && node.raws.before ? node.raws.before : "";
const value = node.value ? node.value : "";
const unit = node.unit ? node.unit : "";
const after = node.raws && node.raws.after ? node.raws.after : "";
return before + value + unit + after;
}
function flattenGroups(node) {
if (
node.type === "paren_group" &&
@ -151,19 +204,33 @@ function parseMediaQuery(value) {
return addTypePrefix(result, "media-");
}
const DEFAULT_SCSS_DIRECTIVE = /(\s*?)(!default).*$/;
const GLOBAL_SCSS_DIRECTIVE = /(\s*?)(!global).*$/;
function parseNestedCSS(node) {
if (node && typeof node === "object") {
delete node.parent;
for (const key in node) {
parseNestedCSS(node[key]);
}
if (typeof node.selector === "string") {
const selector = node.raws.selector
if (typeof node.selector === "string" && node.selector.trim().length > 0) {
let selector = node.raws.selector
? node.raws.selector.raw
: node.selector;
if (node.raws.between && node.raws.between.trim()) {
selector += node.raws.between;
}
if (selector.startsWith("@") && selector.endsWith(":")) {
return node;
}
try {
node.selector = parseSelector(selector);
node.raws.selector = selector;
} catch (e) {
// 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:
@ -176,25 +243,171 @@ function parseNestedCSS(node) {
value: selector
};
}
return node;
}
if (node.type && typeof node.value === "string") {
if (
node.type &&
node.type !== "css-comment-yaml" &&
typeof node.value === "string" &&
node.value.trim().length > 0
) {
try {
node.value = parseValue(node.value);
let value = node.raws.value ? node.raws.value.raw : node.value;
const defaultSCSSDirectiveIndex = value.match(DEFAULT_SCSS_DIRECTIVE);
if (defaultSCSSDirectiveIndex) {
value = value.substring(0, defaultSCSSDirectiveIndex.index);
node.scssDefault = true;
if (defaultSCSSDirectiveIndex[0].trim() !== "!default") {
node.raws.scssDefault = defaultSCSSDirectiveIndex[0];
}
}
const globalSCSSDirectiveIndex = value.match(GLOBAL_SCSS_DIRECTIVE);
if (globalSCSSDirectiveIndex) {
value = value.substring(0, globalSCSSDirectiveIndex.index);
node.scssGlobal = true;
if (globalSCSSDirectiveIndex[0].trim() !== "!global") {
node.raws.scssGlobal = globalSCSSDirectiveIndex[0];
}
}
if (value.startsWith("progid:")) {
return node;
}
node.value = parseValue(value);
} catch (e) {
throw createError(
"(postcss-values-parser) " + e.toString(),
node.source
);
}
return node;
}
if (node.type === "css-atrule" && typeof node.params === "string") {
node.params = parseMediaQuery(node.params);
let params =
node.raws.params && node.raws.params.raw
? node.raws.params.raw
: node.params;
if (node.raws.afterName.trim()) {
params = node.raws.afterName + params;
}
if (node.raws.between.trim()) {
params = params + node.raws.between;
}
params = params.trim();
if (params.length === 0) {
return node;
}
const name = node.name;
const lowercasedName = node.name.toLowerCase();
if (name === "warn" || name === "error") {
node.params = {
type: "media-unknown",
value: params
};
return node;
}
if (name === "extend" || name === "nest") {
node.selector = parseSelector(params);
delete node.params;
return node;
}
if (name === "at-root") {
if (/^\(\s*(without|with)\s*:[\s\S]+\)$/.test(params)) {
node.params = parseMediaQuery(params);
} else {
node.selector = parseSelector(params);
delete node.params;
}
return node;
}
if (
[
"if",
"else",
"for",
"each",
"while",
"debug",
"mixin",
"include",
"function",
"return",
"define-mixin",
"add-mixin"
].indexOf(name) !== -1
) {
// Remove unnecessary spaces in SCSS variable arguments
params = params.replace(/(\$\S+?)\s+?\.\.\./, "$1...");
// Remove unnecessary spaces before SCSS control, mixin and function directives
params = params.replace(/^(?!if)(\S+)\s+\(/, "$1(");
node.value = parseValue(params);
delete node.params;
return node;
}
if (name === "custom-selector") {
const customSelector = params.match(/:--\S+?\s+/)[0].trim();
node.customSelector = customSelector;
node.selector = parseSelector(params.substring(customSelector.length));
delete node.params;
return node;
}
if (
["namespace", "import", "media", "supports", "custom-media"].indexOf(
lowercasedName
) !== -1
) {
if (params.includes("#{")) {
// Workaround for media at rule with scss interpolation
return {
type: "media-unknown",
value: params
};
}
node.params = parseMediaQuery(params);
return node;
}
node.params = params;
return node;
}
}
return node;
}
function parseWithParser(parser, text) {
function parseWithParser(parser, text, frontMatter) {
let result;
try {
result = parser.parse(text);
@ -204,6 +417,14 @@ function parseWithParser(parser, text) {
}
throw createError("(postcss) " + e.name + " " + e.reason, { start: e });
}
if (Object.keys(frontMatter.data).length > 0) {
result.nodes.unshift({
type: "comment-yaml",
value: grayMatter.stringify("", frontMatter.data).replace(/\s$/, "")
});
}
const prefixedResult = addTypePrefix(result, "css-");
const parsedResult = parseNestedCSS(prefixedResult);
return parsedResult;
@ -237,15 +458,22 @@ function parse(text, parsers, opts) {
? opts.parser === "scss"
: IS_POSSIBLY_SCSS.test(text);
const frontMatter = grayMatter(text);
const normalizedText = frontMatter.content;
try {
return parseWithParser(requireParser(isSCSS), text);
return parseWithParser(requireParser(isSCSS), normalizedText, frontMatter);
} catch (originalError) {
if (hasExplicitParserChoice) {
throw originalError;
}
try {
return parseWithParser(requireParser(!isSCSS), text);
return parseWithParser(
requireParser(!isSCSS),
normalizedText,
frontMatter
);
} catch (_secondError) {
throw originalError;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
"use strict";
const printer = require("./printer-graphql");
const options = require("./options");
// Based on:
// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
@ -23,7 +24,19 @@ const parsers = {
get parse() {
return eval("require")("./parser-graphql");
},
astFormat: "graphql"
astFormat: "graphql",
locStart: function(node) {
if (typeof node.start === "number") {
return node.start;
}
return node.loc && node.loc.start;
},
locEnd: function(node) {
if (typeof node.end === "number") {
return node.end;
}
return node.loc && node.loc.end;
}
}
};
@ -33,6 +46,7 @@ const printers = {
module.exports = {
languages,
options,
parsers,
printers
};

View File

@ -34,11 +34,19 @@ function removeTokens(node) {
return node;
}
function fallbackParser(parse, source) {
try {
return parse(source, { allowLegacySDLImplementsInterfaces: false });
} catch (_) {
return parse(source, { allowLegacySDLImplementsInterfaces: true });
}
}
function parse(text /*, parsers, opts*/) {
// Inline the require to avoid loading all the JS if we don't use it
const parser = require("graphql/language");
try {
const ast = parser.parse(text);
const ast = fallbackParser(parser.parse, text);
ast.comments = parseComments(ast);
removeTokens(ast);
return ast;

View File

@ -9,9 +9,8 @@ const softline = docBuilders.softline;
const group = docBuilders.group;
const indent = docBuilders.indent;
const ifBreak = docBuilders.ifBreak;
const printerOptions = require("./options");
const util = require("../common/util");
const privateUtil = require("../common/util");
const sharedUtil = require("../common/util-shared");
function genericPrint(path, options, print) {
const n = path.getValue();
@ -31,9 +30,11 @@ function genericPrint(path, options, print) {
]);
}
case "OperationDefinition": {
const hasOperation = options.originalText[options.locStart(n)] !== "{";
const hasName = !!n.name;
return concat([
n.name === null ? "" : n.operation,
n.name ? concat([" ", path.call(print, "name")]) : "",
hasOperation ? n.operation : "",
hasOperation && hasName ? concat([" ", path.call(print, "name")]) : "",
n.variableDefinitions && n.variableDefinitions.length
? group(
concat([
@ -53,7 +54,7 @@ function genericPrint(path, options, print) {
)
: "",
printDirectives(path, print, n),
n.selectionSet ? (n.name === null ? "" : " ") : "",
n.selectionSet ? (!hasOperation && !hasName ? "" : " ") : "",
path.call(print, "selectionSet")
]);
}
@ -123,6 +124,15 @@ function genericPrint(path, options, print) {
return n.value;
}
case "StringValue": {
if (n.block) {
return concat([
'"""',
hardline,
join(hardline, n.value.replace(/"""/g, "\\$&").split("\n")),
hardline,
'"""'
]);
}
return concat(['"', n.value.replace(/["\\]/g, "\\$&"), '"']);
}
case "IntValue":
@ -231,36 +241,55 @@ function genericPrint(path, options, print) {
return concat(["extend ", path.call(print, "definition")]);
}
case "ObjectTypeExtension":
case "ObjectTypeDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
n.kind === "ObjectTypeExtension" ? "extend " : "",
"type ",
path.call(print, "name"),
n.interfaces.length > 0
? concat([" implements ", join(", ", path.map(print, "interfaces"))])
? concat([
" implements ",
join(
determineInterfaceSeparator(
options.originalText.substr(
options.locStart(n),
options.locEnd(n)
)
),
path.map(print, "interfaces")
)
])
: "",
printDirectives(path, print, n),
" {",
n.fields.length > 0
? indent(
concat([
hardline,
join(
? concat([
" {",
indent(
concat([
hardline,
path.call(
fieldsPath => printSequence(fieldsPath, options, print),
"fields"
join(
hardline,
path.call(
fieldsPath => printSequence(fieldsPath, options, print),
"fields"
)
)
)
])
)
: "",
hardline,
"}"
])
),
hardline,
"}"
])
: ""
]);
}
case "FieldDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
path.call(print, "name"),
n.arguments.length > 0
? group(
@ -291,6 +320,8 @@ function genericPrint(path, options, print) {
case "DirectiveDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
"directive ",
"@",
path.call(print, "name"),
@ -319,33 +350,42 @@ function genericPrint(path, options, print) {
]);
}
case "EnumTypeExtension":
case "EnumTypeDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
n.kind === "EnumTypeExtension" ? "extend " : "",
"enum ",
path.call(print, "name"),
printDirectives(path, print, n),
" {",
n.values.length > 0
? indent(
concat([
hardline,
join(
? concat([
" {",
indent(
concat([
hardline,
path.call(
valuesPath => printSequence(valuesPath, options, print),
"values"
join(
hardline,
path.call(
valuesPath => printSequence(valuesPath, options, print),
"values"
)
)
)
])
)
: "",
hardline,
"}"
])
),
hardline,
"}"
])
: ""
]);
}
case "EnumValueDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
path.call(print, "name"),
printDirectives(path, print, n)
]);
@ -353,6 +393,8 @@ function genericPrint(path, options, print) {
case "InputValueDefinition": {
return concat([
path.call(print, "description"),
n.description ? (n.description.block ? hardline : line) : "",
path.call(print, "name"),
": ",
path.call(print, "type"),
@ -361,28 +403,34 @@ function genericPrint(path, options, print) {
]);
}
case "InputObjectTypeExtension":
case "InputObjectTypeDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
n.kind === "InputObjectTypeExtension" ? "extend " : "",
"input ",
path.call(print, "name"),
printDirectives(path, print, n),
" {",
n.fields.length > 0
? indent(
concat([
hardline,
join(
? concat([
" {",
indent(
concat([
hardline,
path.call(
fieldsPath => printSequence(fieldsPath, options, print),
"fields"
join(
hardline,
path.call(
fieldsPath => printSequence(fieldsPath, options, print),
"fields"
)
)
)
])
)
: "",
hardline,
"}"
])
),
hardline,
"}"
])
: ""
]);
}
@ -418,28 +466,35 @@ function genericPrint(path, options, print) {
]);
}
case "InterfaceTypeExtension":
case "InterfaceTypeDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
n.kind === "InterfaceTypeExtension" ? "extend " : "",
"interface ",
path.call(print, "name"),
printDirectives(path, print, n),
" {",
n.fields.length > 0
? indent(
concat([
hardline,
join(
? concat([
" {",
indent(
concat([
hardline,
path.call(
fieldsPath => printSequence(fieldsPath, options, print),
"fields"
join(
hardline,
path.call(
fieldsPath => printSequence(fieldsPath, options, print),
"fields"
)
)
)
])
)
: "",
hardline,
"}"
])
),
hardline,
"}"
])
: ""
]);
}
@ -463,25 +518,42 @@ function genericPrint(path, options, print) {
]);
}
case "UnionTypeExtension":
case "UnionTypeDefinition": {
return group(
concat([
"union ",
path.call(print, "name"),
" =",
ifBreak("", " "),
indent(
path.call(print, "description"),
n.description ? hardline : "",
group(
concat([
ifBreak(concat([line, " "])),
join(concat([line, "| "]), path.map(print, "types"))
n.kind === "UnionTypeExtension" ? "extend " : "",
"union ",
path.call(print, "name"),
printDirectives(path, print, n),
n.types.length > 0
? concat([
" =",
ifBreak("", " "),
indent(
concat([
ifBreak(concat([line, " "])),
join(concat([line, "| "]), path.map(print, "types"))
])
)
])
: ""
])
)
])
);
}
case "ScalarTypeExtension":
case "ScalarTypeDefinition": {
return concat([
path.call(print, "description"),
n.description ? hardline : "",
n.kind === "ScalarTypeExtension" ? "extend " : "",
"scalar ",
path.call(print, "name"),
printDirectives(path, print, n)
@ -530,7 +602,11 @@ function printSequence(sequencePath, options, print) {
const printed = print(path);
if (
util.isNextLineEmpty(options.originalText, path.getValue()) &&
sharedUtil.isNextLineEmpty(
options.originalText,
path.getValue(),
options
) &&
i < count - 1
) {
return concat([printed, hardline]);
@ -555,10 +631,21 @@ function printComment(commentPath) {
}
}
function determineInterfaceSeparator(originalSource) {
const start = originalSource.indexOf("implements");
if (start === -1) {
throw new Error("Must implement interfaces: " + originalSource);
}
let end = originalSource.indexOf("{");
if (end === -1) {
end = originalSource.length;
}
return originalSource.substr(start, end).includes("&") ? " & " : ", ";
}
module.exports = {
options: printerOptions,
print: genericPrint,
hasPrettierIgnore: util.hasIgnoreComment,
hasPrettierIgnore: privateUtil.hasIgnoreComment,
printComment,
canAttachComment
};

View File

@ -13,7 +13,8 @@ const languages = [
extensions: [".handlebars", ".hbs"],
tm_scope: "text.html.handlebars",
ace_mode: "handlebars",
language_id: 155
language_id: 155,
since: null // unreleased
}
];
@ -22,7 +23,13 @@ const parsers = {
get parse() {
return eval("require")("./parser-glimmer");
},
astFormat: "glimmer"
astFormat: "glimmer",
locEnd: function(node) {
return node.loc && node.loc.end;
},
locStart: function(node) {
return node.loc && node.loc.start;
}
}
};

View File

@ -96,11 +96,16 @@ function print(path, options, print) {
}
case "BlockStatement": {
const pp = path.getParentNode(1);
const isElseIf = pp && pp.inverse && pp.inverse.body[0] === n;
const isElseIf =
pp &&
pp.inverse &&
pp.inverse.body[0] === n &&
pp.inverse.body[0].path.parts[0] === "if";
const hasElseIf =
n.inverse &&
n.inverse.body[0] &&
n.inverse.body[0].type === "BlockStatement";
n.inverse.body[0].type === "BlockStatement" &&
n.inverse.body[0].path.parts[0] === "if";
const indentElse = hasElseIf ? a => a : indent;
if (n.inverse) {
return concat([
@ -114,6 +119,11 @@ function print(path, options, print) {
: "",
isElseIf ? "" : concat([hardline, printCloseBlock(path, print)])
]);
} else if (isElseIf) {
return concat([
concat(["{{else ", printPathParams(path, print), "}}"]),
indent(concat([hardline, path.call(print, "program")]))
]);
}
/**
* I want this boolean to be: if params are going to cause a break,

View File

@ -1,6 +1,6 @@
"use strict";
const util = require("../common/util");
const privateUtil = require("../common/util");
const doc = require("../doc");
const docUtils = doc.utils;
const docBuilders = doc.builders;
@ -66,7 +66,7 @@ function embed(path, print, textToDoc, options) {
return concat([
node.key,
'="',
util.hasNewlineInRange(node.value, 0, node.value.length)
privateUtil.hasNewlineInRange(node.value, 0, node.value.length)
? doc
: docUtils.removeLines(doc),
'"'
@ -87,7 +87,10 @@ function parseJavaScriptExpression(text, parsers) {
}
function getText(options, node) {
return options.originalText.slice(util.locStart(node), util.locEnd(node));
return options.originalText.slice(
options.locStart(node),
options.locEnd(node)
);
}
module.exports = embed;

View File

@ -8,7 +8,7 @@ const printer = require("./printer-htmlparser2");
const languages = [
{
name: "HTML",
since: undefined, // unreleased
since: null, // unreleased
parsers: ["parse5"],
group: "HTML",
tmScope: "text.html.basic",
@ -27,7 +27,13 @@ const parsers = {
get parse() {
return eval("require")("./parser-parse5");
},
astFormat: "htmlparser2"
astFormat: "htmlparser2",
locEnd: function(node) {
return node.__location && node.__location.endOffset;
},
locStart: function(node) {
return node.__location && node.__location.startOffset;
}
}
};

View File

@ -1,26 +1,17 @@
"use strict";
// const createError = require("./parser-create-error");
function parse(text /*, parsers, opts*/) {
// Inline the require to avoid loading all the JS if we don't use it
const parse5 = require("parse5");
try {
const isFragment = !/^\s*<(!doctype|html|head|body)/i.test(text);
const isFragment = !/^\s*<(!doctype|html|head|body|!--)/i.test(text);
const ast = (isFragment ? parse5.parseFragment : parse5.parse)(text, {
treeAdapter: parse5.treeAdapters.htmlparser2,
locationInfo: true
});
return extendAst(ast);
} catch (error) {
// throw createError(error.message, {
// start: {
// line: error.locations[0].line,
// column: error.locations[0].column
// }
// } else {
throw error;
// }
}
}

View File

@ -1,7 +1,7 @@
"use strict";
const embed = require("./embed");
const util = require("../common/util");
const privateUtil = require("../common/util");
const docBuilders = require("../doc").builders;
const concat = docBuilders.concat;
const join = docBuilders.join;
@ -10,7 +10,6 @@ const line = docBuilders.line;
const softline = docBuilders.softline;
const group = docBuilders.group;
const indent = docBuilders.indent;
// const ifBreak = docBuilders.ifBreak;
// http://w3c.github.io/html/single-page.html#void-elements
const voidTags = {
@ -56,10 +55,10 @@ function genericPrint(path, options, print) {
const selfClose = voidTags[n.name] ? ">" : " />";
const children = printChildren(path, print);
const hasNewline = util.hasNewlineInRange(
const hasNewline = privateUtil.hasNewlineInRange(
options.originalText,
util.locStart(n),
util.locEnd(n)
options.locStart(n),
options.locEnd(n)
);
return group(
@ -118,5 +117,5 @@ function printChildren(path, print) {
module.exports = {
print: genericPrint,
embed,
hasPrettierIgnore: util.hasIgnoreComment
hasPrettierIgnore: privateUtil.hasIgnoreComment
};

707
src/language-js/comments.js Normal file
View File

@ -0,0 +1,707 @@
"use strict";
const privateUtil = require("../common/util");
const sharedUtil = require("../common/util-shared");
const addLeadingComment = sharedUtil.addLeadingComment;
const addTrailingComment = sharedUtil.addTrailingComment;
const addDanglingComment = sharedUtil.addDanglingComment;
function handleOwnLineComment(comment, text, options, ast, isLastComment) {
const precedingNode = comment.precedingNode;
const enclosingNode = comment.enclosingNode;
const followingNode = comment.followingNode;
if (
handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleTryStatementComments(enclosingNode, followingNode, comment) ||
handleClassComments(enclosingNode, precedingNode, followingNode, comment) ||
handleImportSpecifierComments(enclosingNode, comment) ||
handleForComments(enclosingNode, precedingNode, comment) ||
handleUnionTypeComments(
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleImportDeclarationComments(
text,
enclosingNode,
precedingNode,
comment,
options
) ||
handleAssignmentPatternComments(enclosingNode, comment) ||
handleMethodNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
)
) {
return true;
}
return false;
}
function handleEndOfLineComment(comment, text, options, ast, isLastComment) {
const precedingNode = comment.precedingNode;
const enclosingNode = comment.enclosingNode;
const followingNode = comment.followingNode;
if (
handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleConditionalExpressionComments(
enclosingNode,
precedingNode,
followingNode,
comment,
text,
options
) ||
handleImportSpecifierComments(enclosingNode, comment) ||
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleClassComments(enclosingNode, precedingNode, followingNode, comment) ||
handleLabeledStatementComments(enclosingNode, comment) ||
handleCallExpressionComments(precedingNode, enclosingNode, comment) ||
handlePropertyComments(enclosingNode, comment) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleTypeAliasComments(enclosingNode, followingNode, comment) ||
handleVariableDeclaratorComments(enclosingNode, followingNode, comment)
) {
return true;
}
return false;
}
function handleRemainingComment(comment, text, options, ast, isLastComment) {
const precedingNode = comment.precedingNode;
const enclosingNode = comment.enclosingNode;
const followingNode = comment.followingNode;
if (
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) ||
handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) ||
handleCommentInEmptyParens(text, enclosingNode, comment, options) ||
handleMethodNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleCommentAfterArrowParams(text, enclosingNode, comment, options) ||
handleFunctionNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) ||
handleBreakAndContinueStatementComments(enclosingNode, comment)
) {
return true;
}
return false;
}
function addBlockStatementFirstComment(node, comment) {
const body = node.body.filter(n => n.type !== "EmptyStatement");
if (body.length === 0) {
addDanglingComment(node, comment);
} else {
addLeadingComment(body[0], comment);
}
}
function addBlockOrNotComment(node, comment) {
if (node.type === "BlockStatement") {
addBlockStatementFirstComment(node, comment);
} else {
addLeadingComment(node, comment);
}
}
// There are often comments before the else clause of if statements like
//
// if (1) { ... }
// // comment
// else { ... }
//
// They are being attached as leading comments of the BlockExpression which
// is not well printed. What we want is to instead move the comment inside
// of the block and make it leadingComment of the first element of the block
// or dangling comment of the block if there is nothing inside
//
// if (1) { ... }
// else {
// // comment
// ...
// }
function handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) {
if (
!enclosingNode ||
enclosingNode.type !== "IfStatement" ||
!followingNode
) {
return false;
}
// We unfortunately have no way using the AST or location of nodes to know
// if the comment is positioned before the condition parenthesis:
// if (a /* comment */) {}
// The only workaround I found is to look at the next character to see if
// it is a ).
const nextCharacter = privateUtil.getNextNonSpaceNonCommentCharacter(
text,
comment,
options.locEnd
);
if (nextCharacter === ")") {
addTrailingComment(precedingNode, comment);
return true;
}
// Comments before `else`:
// - treat as trailing comments of the consequent, if it's a BlockStatement
// - treat as a dangling comment otherwise
if (
precedingNode === enclosingNode.consequent &&
followingNode === enclosingNode.alternate
) {
if (precedingNode.type === "BlockStatement") {
addTrailingComment(precedingNode, comment);
} else {
addDanglingComment(enclosingNode, comment);
}
return true;
}
if (followingNode.type === "BlockStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
if (followingNode.type === "IfStatement") {
addBlockOrNotComment(followingNode.consequent, comment);
return true;
}
// For comments positioned after the condition parenthesis in an if statement
// before the consequent with or without brackets on, such as
// if (a) /* comment */ {} or if (a) /* comment */ true,
// we look at the next character to see if it is a { or if the following node
// is the consequent for the if statement
if (nextCharacter === "{" || enclosingNode.consequent === followingNode) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
// Same as IfStatement but for TryStatement
function handleTryStatementComments(enclosingNode, followingNode, comment) {
if (
!enclosingNode ||
enclosingNode.type !== "TryStatement" ||
!followingNode
) {
return false;
}
if (followingNode.type === "BlockStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
if (followingNode.type === "TryStatement") {
addBlockOrNotComment(followingNode.finalizer, comment);
return true;
}
if (followingNode.type === "CatchClause") {
addBlockOrNotComment(followingNode.body, comment);
return true;
}
return false;
}
function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
if (
enclosingNode &&
enclosingNode.type === "MemberExpression" &&
followingNode &&
followingNode.type === "Identifier"
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleConditionalExpressionComments(
enclosingNode,
precedingNode,
followingNode,
comment,
text,
options
) {
const isSameLineAsPrecedingNode =
precedingNode &&
!privateUtil.hasNewlineInRange(
text,
options.locEnd(precedingNode),
options.locStart(comment)
);
if (
(!precedingNode || !isSameLineAsPrecedingNode) &&
enclosingNode &&
enclosingNode.type === "ConditionalExpression" &&
followingNode
) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
function handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ObjectProperty" ||
enclosingNode.type === "Property") &&
enclosingNode.shorthand &&
enclosingNode.key === precedingNode &&
enclosingNode.value.type === "AssignmentPattern"
) {
addTrailingComment(enclosingNode.value.left, comment);
return true;
}
return false;
}
function handleClassComments(
enclosingNode,
precedingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "ClassDeclaration" ||
enclosingNode.type === "ClassExpression") &&
(enclosingNode.decorators && enclosingNode.decorators.length > 0) &&
!(followingNode && followingNode.type === "Decorator")
) {
if (!enclosingNode.decorators || enclosingNode.decorators.length === 0) {
addLeadingComment(enclosingNode, comment);
} else {
addTrailingComment(
enclosingNode.decorators[enclosingNode.decorators.length - 1],
comment
);
}
return true;
}
return false;
}
function handleMethodNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) {
// This is only needed for estree parsers (flow, typescript) to attach
// after a method name:
// obj = { fn /*comment*/() {} };
if (
enclosingNode &&
precedingNode &&
(enclosingNode.type === "Property" ||
enclosingNode.type === "MethodDefinition") &&
precedingNode.type === "Identifier" &&
enclosingNode.key === precedingNode &&
// special Property case: { key: /*comment*/(value) };
// comment should be attached to value instead of key
privateUtil.getNextNonSpaceNonCommentCharacter(
text,
precedingNode,
options.locEnd
) !== ":"
) {
addTrailingComment(precedingNode, comment);
return true;
}
// Print comments between decorators and class methods as a trailing comment
// on the decorator node instead of the method node
if (
precedingNode &&
enclosingNode &&
precedingNode.type === "Decorator" &&
(enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "ClassProperty" ||
enclosingNode.type === "TSAbstractClassProperty" ||
enclosingNode.type === "TSAbstractMethodDefinition" ||
enclosingNode.type === "MethodDefinition")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleFunctionNameComments(
text,
enclosingNode,
precedingNode,
comment,
options
) {
if (
privateUtil.getNextNonSpaceNonCommentCharacter(
text,
comment,
options.locEnd
) !== "("
) {
return false;
}
if (
precedingNode &&
enclosingNode &&
(enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "FunctionExpression" ||
enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "MethodDefinition" ||
enclosingNode.type === "ObjectMethod")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleCommentAfterArrowParams(text, enclosingNode, comment, options) {
if (!(enclosingNode && enclosingNode.type === "ArrowFunctionExpression")) {
return false;
}
const index = sharedUtil.getNextNonSpaceNonCommentCharacterIndex(
text,
comment,
options
);
if (text.substr(index, 2) === "=>") {
addDanglingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleCommentInEmptyParens(text, enclosingNode, comment, options) {
if (
privateUtil.getNextNonSpaceNonCommentCharacter(
text,
comment,
options.locEnd
) !== ")"
) {
return false;
}
// Only add dangling comments to fix the case when no params are present,
// i.e. a function without any argument.
if (
enclosingNode &&
(((enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "FunctionExpression" ||
(enclosingNode.type === "ArrowFunctionExpression" &&
(enclosingNode.body.type !== "CallExpression" ||
enclosingNode.body.arguments.length === 0)) ||
enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "ObjectMethod") &&
enclosingNode.params.length === 0) ||
(enclosingNode.type === "CallExpression" &&
enclosingNode.arguments.length === 0))
) {
addDanglingComment(enclosingNode, comment);
return true;
}
if (
enclosingNode &&
(enclosingNode.type === "MethodDefinition" &&
enclosingNode.value.params.length === 0)
) {
addDanglingComment(enclosingNode.value, comment);
return true;
}
return false;
}
function handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment,
options
) {
// Type definitions functions
if (
precedingNode &&
precedingNode.type === "FunctionTypeParam" &&
enclosingNode &&
enclosingNode.type === "FunctionTypeAnnotation" &&
followingNode &&
followingNode.type !== "FunctionTypeParam"
) {
addTrailingComment(precedingNode, comment);
return true;
}
// Real functions
if (
precedingNode &&
(precedingNode.type === "Identifier" ||
precedingNode.type === "AssignmentPattern") &&
enclosingNode &&
(enclosingNode.type === "ArrowFunctionExpression" ||
enclosingNode.type === "FunctionExpression" ||
enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "ObjectMethod" ||
enclosingNode.type === "ClassMethod") &&
privateUtil.getNextNonSpaceNonCommentCharacter(
text,
comment,
options.locEnd
) === ")"
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleImportSpecifierComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "ImportSpecifier") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleLabeledStatementComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "LabeledStatement") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleBreakAndContinueStatementComments(enclosingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ContinueStatement" ||
enclosingNode.type === "BreakStatement") &&
!enclosingNode.label
) {
addTrailingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
if (
enclosingNode &&
enclosingNode.type === "CallExpression" &&
precedingNode &&
enclosingNode.callee === precedingNode &&
enclosingNode.arguments.length > 0
) {
addLeadingComment(enclosingNode.arguments[0], comment);
return true;
}
return false;
}
function handleUnionTypeComments(
precedingNode,
enclosingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "UnionTypeAnnotation" ||
enclosingNode.type === "TSUnionType")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handlePropertyComments(enclosingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "Property" ||
enclosingNode.type === "ObjectProperty")
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleOnlyComments(enclosingNode, ast, comment, isLastComment) {
// With Flow the enclosingNode is undefined so use the AST instead.
if (ast && ast.body && ast.body.length === 0) {
if (isLastComment) {
addDanglingComment(ast, comment);
} else {
addLeadingComment(ast, comment);
}
return true;
} else if (
enclosingNode &&
enclosingNode.type === "Program" &&
enclosingNode.body.length === 0 &&
enclosingNode.directives &&
enclosingNode.directives.length === 0
) {
if (isLastComment) {
addDanglingComment(enclosingNode, comment);
} else {
addLeadingComment(enclosingNode, comment);
}
return true;
}
return false;
}
function handleForComments(enclosingNode, precedingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ForInStatement" ||
enclosingNode.type === "ForOfStatement")
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleImportDeclarationComments(
text,
enclosingNode,
precedingNode,
comment,
options
) {
if (
precedingNode &&
enclosingNode &&
enclosingNode.type === "ImportDeclaration" &&
privateUtil.hasNewline(text, options.locEnd(comment))
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleAssignmentPatternComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "AssignmentPattern") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleTypeAliasComments(enclosingNode, followingNode, comment) {
if (enclosingNode && enclosingNode.type === "TypeAlias") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleVariableDeclaratorComments(
enclosingNode,
followingNode,
comment
) {
if (
enclosingNode &&
enclosingNode.type === "VariableDeclarator" &&
followingNode &&
(followingNode.type === "ObjectExpression" ||
followingNode.type === "ArrayExpression")
) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
module.exports = {
handleOwnLineComment,
handleEndOfLineComment,
handleRemainingComment
};

View File

@ -8,7 +8,9 @@ const indent = docBuilders.indent;
const join = docBuilders.join;
const hardline = docBuilders.hardline;
const softline = docBuilders.softline;
const literalline = docBuilders.literalline;
const concat = docBuilders.concat;
const dedentToRoot = docBuilders.dedentToRoot;
function embed(path, print, textToDoc /*, options */) {
const node = path.getValue();
@ -24,7 +26,16 @@ function embed(path, print, textToDoc /*, options */) {
if (isCss) {
// Get full template literal with expressions replaced by placeholders
const rawQuasis = node.quasis.map(q => q.value.raw);
const text = rawQuasis.join("@prettier-placeholder");
let placeholderID = 0;
const text = rawQuasis.reduce((prevVal, currVal, idx) => {
return idx == 0
? currVal
: prevVal +
"@prettier-placeholder-" +
placeholderID++ +
"-id" +
currVal;
}, "");
const doc = textToDoc(text, { parser: "css" });
return transformCssDoc(doc, path, print);
}
@ -66,7 +77,14 @@ function embed(path, print, textToDoc /*, options */) {
const templateElement = node.quasis[i];
const isFirst = i === 0;
const isLast = i === numQuasis - 1;
const text = templateElement.value.raw;
const text = templateElement.value.cooked;
// Bail out if any of the quasis have an invalid escape sequence
// (which would make the `cooked` value be `null` or `undefined`)
if (typeof text !== "string") {
return null;
}
const lines = text.split("\n");
const numLines = lines.length;
const expressionDoc = expressionDocs[i];
@ -96,13 +114,17 @@ function embed(path, print, textToDoc /*, options */) {
doc = docUtils.stripTrailingHardline(
textToDoc(text, { parser: "graphql" })
);
} catch (_error) {
} catch (error) {
if (process.env.PRETTIER_DEBUG) {
throw error;
}
// Bail if any part fails to parse.
return null;
}
}
if (doc) {
doc = escapeBackticks(doc);
if (!isFirst && startsWithBlankLine) {
parts.push("");
}
@ -143,21 +165,20 @@ function embed(path, print, textToDoc /*, options */) {
(parentParent.tag.name === "md" ||
parentParent.tag.name === "markdown")))
) {
const doc = textToDoc(
// leading whitespaces matter in markdown
dedent(parent.quasis[0].value.cooked),
{
parser: "markdown",
__inJsTemplate: true
}
);
const text = parent.quasis[0].value.cooked;
const indentation = getIndentation(text);
const hasIndent = indentation !== "";
return concat([
indent(
concat([
softline,
docUtils.stripTrailingHardline(escapeBackticks(doc))
])
),
hasIndent
? indent(
concat([
softline,
printMarkdown(
text.replace(new RegExp(`^${indentation}`, "gm"), "")
)
])
)
: concat([literalline, dedentToRoot(printMarkdown(text))]),
softline
]);
}
@ -165,12 +186,16 @@ function embed(path, print, textToDoc /*, options */) {
break;
}
}
function printMarkdown(text) {
const doc = textToDoc(text, { parser: "markdown", __inJsTemplate: true });
return docUtils.stripTrailingHardline(escapeBackticks(doc));
}
}
function dedent(str) {
const firstMatchedIndent = str.match(/\n^( *)/m);
const spaces = firstMatchedIndent === null ? 0 : firstMatchedIndent[1].length;
return str.replace(new RegExp(`^ {${spaces}}`, "gm"), "").trim();
function getIndentation(str) {
const firstMatchedIndent = str.match(/^([^\S\n]*)\S/m);
return firstMatchedIndent === null ? "" : firstMatchedIndent[1];
}
function escapeBackticks(doc) {
@ -228,6 +253,7 @@ function replacePlaceholders(quasisDoc, expressionDocs) {
}
const expressions = expressionDocs.slice();
let replaceCounter = 0;
const newDoc = docUtils.mapDoc(quasisDoc, doc => {
if (!doc || !doc.parts || !doc.parts.length) {
return doc;
@ -256,12 +282,16 @@ function replacePlaceholders(quasisDoc, expressionDocs) {
if (atPlaceholderIndex > -1) {
const placeholder = parts[atPlaceholderIndex];
const rest = parts.slice(atPlaceholderIndex + 1);
const placeholderMatch = placeholder.match(
/@prettier-placeholder-(.+)-id(.*)/
);
const placeholderID = placeholderMatch[1];
// When the expression has a suffix appended, like:
// animation: linear ${time}s ease-out;
const suffix = placeholder.slice("@prettier-placeholder".length);
const suffix = placeholderMatch[2];
const expression = expressions[placeholderID];
const expression = expressions.shift();
replaceCounter++;
parts = parts
.slice(0, atPlaceholderIndex)
.concat(["${", expression, "}" + suffix])
@ -272,7 +302,7 @@ function replacePlaceholders(quasisDoc, expressionDocs) {
});
});
return expressions.length === 0 ? newDoc : null;
return expressions.length === replaceCounter ? newDoc : null;
}
function printGraphqlComments(lines) {

View File

@ -1,10 +1,68 @@
"use strict";
const printer = require("./printer-estree");
const hasPragma = require("./pragma").hasPragma;
const options = require("./options");
const privateUtil = require("../common/util");
// Based on:
// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
const locStart = function(node) {
// Handle nodes with decorators. They should start at the first decorator
if (
node.declaration &&
node.declaration.decorators &&
node.declaration.decorators.length > 0
) {
return locStart(node.declaration.decorators[0]);
}
if (node.decorators && node.decorators.length > 0) {
return locStart(node.decorators[0]);
}
if (node.__location) {
return node.__location.startOffset;
}
if (node.range) {
return node.range[0];
}
if (typeof node.start === "number") {
return node.start;
}
if (node.loc) {
return node.loc.start;
}
return null;
};
const locEnd = function(node) {
const endNode = node.nodes && privateUtil.getLast(node.nodes);
if (endNode && node.source && !node.source.end) {
node = endNode;
}
let loc;
if (node.range) {
loc = node.range[1];
} else if (typeof node.end === "number") {
loc = node.end;
}
if (node.__location) {
return node.__location.endOffset;
}
if (node.typeAnnotation) {
return Math.max(loc, locEnd(node.typeAnnotation));
}
if (node.loc && !loc) {
return node.loc.end;
}
return loc;
};
const languages = [
{
name: "JavaScript",
@ -103,24 +161,37 @@ const typescript = {
get parse() {
return eval("require")("./parser-typescript");
},
astFormat: "estree"
astFormat: "estree",
hasPragma,
locStart,
locEnd
};
const babylon = {
get parse() {
return eval("require")("./parser-babylon");
},
astFormat: "estree"
astFormat: "estree",
hasPragma,
locStart,
locEnd
};
const parsers = {
babylon,
json: babylon,
json: Object.assign({}, babylon, {
hasPragma() {
return false;
}
}),
flow: {
get parse() {
return eval("require")("./parser-flow");
},
astFormat: "estree"
astFormat: "estree",
hasPragma,
locStart,
locEnd
},
"typescript-eslint": typescript,
// TODO: Delete this in 2.0
@ -133,6 +204,9 @@ const printers = {
module.exports = {
languages,
options,
parsers,
printers
printers,
locStart,
locEnd
};

View File

@ -0,0 +1,560 @@
"use strict";
const assert = require("assert");
const util = require("../common/util");
function needsParens(path, options) {
const parent = path.getParentNode();
if (!parent) {
return false;
}
const name = path.getName();
const node = path.getNode();
// If the value of this path is some child of a Node and not a Node
// itself, then it doesn't need parentheses. Only Node objects (in
// fact, only Expression nodes) need parentheses.
if (path.getValue() !== node) {
return false;
}
// Only statements don't need parentheses.
if (isStatement(node)) {
return false;
}
// Closure compiler requires that type casted expressions to be surrounded by
// parentheses.
if (
util.hasClosureCompilerTypeCastComment(
options.originalText,
node,
options.locEnd
)
) {
return true;
}
// Identifiers never need parentheses.
if (node.type === "Identifier") {
return false;
}
if (parent.type === "ParenthesizedExpression") {
return false;
}
// Add parens around the extends clause of a class. It is needed for almost
// all expressions.
if (
(parent.type === "ClassDeclaration" || parent.type === "ClassExpression") &&
parent.superClass === node &&
(node.type === "ArrowFunctionExpression" ||
node.type === "AssignmentExpression" ||
node.type === "AwaitExpression" ||
node.type === "BinaryExpression" ||
node.type === "ConditionalExpression" ||
node.type === "LogicalExpression" ||
node.type === "NewExpression" ||
node.type === "ObjectExpression" ||
node.type === "ParenthesizedExpression" ||
node.type === "SequenceExpression" ||
node.type === "TaggedTemplateExpression" ||
node.type === "UnaryExpression" ||
node.type === "UpdateExpression" ||
node.type === "YieldExpression")
) {
return true;
}
if (
(parent.type === "ArrowFunctionExpression" &&
parent.body === node &&
node.type !== "SequenceExpression" && // these have parens added anyway
util.startsWithNoLookaheadToken(
node,
/* forbidFunctionAndClass */ false
)) ||
(parent.type === "ExpressionStatement" &&
util.startsWithNoLookaheadToken(node, /* forbidFunctionAndClass */ true))
) {
return true;
}
switch (node.type) {
case "CallExpression": {
let firstParentNotMemberExpression = parent;
let i = 0;
while (
firstParentNotMemberExpression &&
firstParentNotMemberExpression.type === "MemberExpression"
) {
firstParentNotMemberExpression = path.getParentNode(++i);
}
if (
firstParentNotMemberExpression.type === "NewExpression" &&
firstParentNotMemberExpression.callee === path.getParentNode(i - 1)
) {
return true;
}
return false;
}
case "SpreadElement":
case "SpreadProperty":
return (
parent.type === "MemberExpression" &&
name === "object" &&
parent.object === node
);
case "UpdateExpression":
if (parent.type === "UnaryExpression") {
return (
node.prefix &&
((node.operator === "++" && parent.operator === "+") ||
(node.operator === "--" && parent.operator === "-"))
);
}
// else fallthrough
case "UnaryExpression":
switch (parent.type) {
case "UnaryExpression":
return (
node.operator === parent.operator &&
(node.operator === "+" || node.operator === "-")
);
case "MemberExpression":
return name === "object" && parent.object === node;
case "TaggedTemplateExpression":
return true;
case "NewExpression":
case "CallExpression":
return name === "callee" && parent.callee === node;
case "BinaryExpression":
return parent.operator === "**" && name === "left";
case "TSNonNullExpression":
return true;
default:
return false;
}
case "BinaryExpression": {
if (parent.type === "UpdateExpression") {
return true;
}
const isLeftOfAForStatement = node => {
let i = 0;
while (node) {
const parent = path.getParentNode(i++);
if (!parent) {
return false;
}
if (parent.type === "ForStatement" && parent.init === node) {
return true;
}
node = parent;
}
return false;
};
if (node.operator === "in" && isLeftOfAForStatement(node)) {
return true;
}
}
// fallthrough
case "TSTypeAssertionExpression":
case "TSAsExpression":
case "LogicalExpression":
switch (parent.type) {
case "ConditionalExpression":
return node.type === "TSAsExpression";
case "CallExpression":
case "NewExpression":
return name === "callee" && parent.callee === node;
case "ClassDeclaration":
case "TSAbstractClassDeclaration":
return name === "superClass" && parent.superClass === node;
case "TSTypeAssertionExpression":
case "TaggedTemplateExpression":
case "UnaryExpression":
case "SpreadElement":
case "SpreadProperty":
case "ExperimentalSpreadProperty":
case "BindExpression":
case "AwaitExpression":
case "TSAsExpression":
case "TSNonNullExpression":
case "UpdateExpression":
return true;
case "MemberExpression":
return name === "object" && parent.object === node;
case "AssignmentExpression":
return (
parent.left === node &&
(node.type === "TSTypeAssertionExpression" ||
node.type === "TSAsExpression")
);
case "Decorator":
return (
parent.expression === node &&
(node.type === "TSTypeAssertionExpression" ||
node.type === "TSAsExpression")
);
case "BinaryExpression":
case "LogicalExpression": {
if (!node.operator && node.type !== "TSTypeAssertionExpression") {
return true;
}
const po = parent.operator;
const pp = util.getPrecedence(po);
const no = node.operator;
const np = util.getPrecedence(no);
if (pp > np) {
return true;
}
if ((po === "||" || po === "??") && no === "&&") {
return true;
}
if (pp === np && name === "right") {
assert.strictEqual(parent.right, node);
return true;
}
if (pp === np && !util.shouldFlatten(po, no)) {
return true;
}
// Add parenthesis when working with binary operators
// It's not stricly needed but helps with code understanding
if (util.isBitwiseOperator(po)) {
return true;
}
return false;
}
default:
return false;
}
case "TSParenthesizedType": {
const grandParent = path.getParentNode(1);
if (
(parent.type === "TSTypeParameter" ||
parent.type === "TypeParameter" ||
parent.type === "VariableDeclarator" ||
parent.type === "TSTypeAnnotation" ||
parent.type === "GenericTypeAnnotation" ||
parent.type === "TSTypeReference") &&
(node.typeAnnotation.type === "TSTypeAnnotation" &&
node.typeAnnotation.typeAnnotation.type !== "TSFunctionType" &&
grandParent.type !== "TSTypeOperator")
) {
return false;
}
// Delegate to inner TSParenthesizedType
if (node.typeAnnotation.type === "TSParenthesizedType") {
return false;
}
return true;
}
case "SequenceExpression":
switch (parent.type) {
case "ReturnStatement":
return false;
case "ForStatement":
// Although parentheses wouldn't hurt around sequence
// expressions in the head of for loops, traditional style
// dictates that e.g. i++, j++ should not be wrapped with
// parentheses.
return false;
case "ExpressionStatement":
return name !== "expression";
case "ArrowFunctionExpression":
// We do need parentheses, but SequenceExpressions are handled
// specially when printing bodies of arrow functions.
return name !== "body";
default:
// Otherwise err on the side of overparenthesization, adding
// explicit exceptions above if this proves overzealous.
return true;
}
case "YieldExpression":
if (
parent.type === "UnaryExpression" ||
parent.type === "AwaitExpression" ||
parent.type === "TSAsExpression" ||
parent.type === "TSNonNullExpression"
) {
return true;
}
// else fallthrough
case "AwaitExpression":
switch (parent.type) {
case "TaggedTemplateExpression":
case "BinaryExpression":
case "LogicalExpression":
case "SpreadElement":
case "SpreadProperty":
case "ExperimentalSpreadProperty":
case "TSAsExpression":
case "TSNonNullExpression":
return true;
case "MemberExpression":
return parent.object === node;
case "NewExpression":
case "CallExpression":
return parent.callee === node;
case "ConditionalExpression":
return parent.test === node;
default:
return false;
}
case "ArrayTypeAnnotation":
return parent.type === "NullableTypeAnnotation";
case "IntersectionTypeAnnotation":
case "UnionTypeAnnotation":
return (
parent.type === "ArrayTypeAnnotation" ||
parent.type === "NullableTypeAnnotation" ||
parent.type === "IntersectionTypeAnnotation" ||
parent.type === "UnionTypeAnnotation"
);
case "NullableTypeAnnotation":
return parent.type === "ArrayTypeAnnotation";
case "FunctionTypeAnnotation":
return (
parent.type === "UnionTypeAnnotation" ||
parent.type === "IntersectionTypeAnnotation" ||
parent.type === "ArrayTypeAnnotation"
);
case "StringLiteral":
case "NumericLiteral":
case "Literal":
if (
typeof node.value === "string" &&
parent.type === "ExpressionStatement" &&
// TypeScript workaround for eslint/typescript-eslint-parser#267
// See corresponding workaround in printer.js case: "Literal"
((options.parser !== "typescript" && !parent.directive) ||
(options.parser === "typescript" &&
options.originalText.substr(options.locStart(node) - 1, 1) === "("))
) {
// To avoid becoming a directive
const grandParent = path.getParentNode(1);
return (
grandParent.type === "Program" ||
grandParent.type === "BlockStatement"
);
}
return (
parent.type === "MemberExpression" &&
typeof node.value === "number" &&
name === "object" &&
parent.object === node
);
case "AssignmentExpression": {
const grandParent = path.getParentNode(1);
if (parent.type === "ArrowFunctionExpression" && parent.body === node) {
return true;
} else if (
parent.type === "ClassProperty" &&
parent.key === node &&
parent.computed
) {
return false;
} else if (
parent.type === "TSPropertySignature" &&
parent.name === node
) {
return false;
} else if (
parent.type === "ForStatement" &&
(parent.init === node || parent.update === node)
) {
return false;
} else if (parent.type === "ExpressionStatement") {
return node.left.type === "ObjectPattern";
} else if (parent.type === "TSPropertySignature" && parent.key === node) {
return false;
} else if (parent.type === "AssignmentExpression") {
return false;
} else if (
parent.type === "SequenceExpression" &&
grandParent &&
grandParent.type === "ForStatement" &&
(grandParent.init === parent || grandParent.update === parent)
) {
return false;
}
return true;
}
case "ConditionalExpression":
switch (parent.type) {
case "TaggedTemplateExpression":
case "UnaryExpression":
case "SpreadElement":
case "SpreadProperty":
case "ExperimentalSpreadProperty":
case "BinaryExpression":
case "LogicalExpression":
case "ExportDefaultDeclaration":
case "AwaitExpression":
case "JSXSpreadAttribute":
case "TSTypeAssertionExpression":
case "TypeCastExpression":
case "TSAsExpression":
case "TSNonNullExpression":
return true;
case "NewExpression":
case "CallExpression":
return name === "callee" && parent.callee === node;
case "ConditionalExpression":
return name === "test" && parent.test === node;
case "MemberExpression":
return name === "object" && parent.object === node;
default:
return false;
}
case "FunctionExpression":
switch (parent.type) {
case "CallExpression":
return name === "callee"; // Not strictly necessary, but it's clearer to the reader if IIFEs are wrapped in parentheses.
case "TaggedTemplateExpression":
return true; // This is basically a kind of IIFE.
case "ExportDefaultDeclaration":
return true;
default:
return false;
}
case "ArrowFunctionExpression":
switch (parent.type) {
case "CallExpression":
return name === "callee";
case "NewExpression":
return name === "callee";
case "MemberExpression":
return name === "object";
case "TSAsExpression":
case "BindExpression":
case "TaggedTemplateExpression":
case "UnaryExpression":
case "LogicalExpression":
case "BinaryExpression":
case "AwaitExpression":
case "TSTypeAssertionExpression":
return true;
case "ConditionalExpression":
return name === "test";
default:
return false;
}
case "ClassExpression":
return parent.type === "ExportDefaultDeclaration";
}
return false;
}
function isStatement(node) {
return (
node.type === "BlockStatement" ||
node.type === "BreakStatement" ||
node.type === "ClassBody" ||
node.type === "ClassDeclaration" ||
node.type === "ClassMethod" ||
node.type === "ClassProperty" ||
node.type === "ClassPrivateProperty" ||
node.type === "ContinueStatement" ||
node.type === "DebuggerStatement" ||
node.type === "DeclareClass" ||
node.type === "DeclareExportAllDeclaration" ||
node.type === "DeclareExportDeclaration" ||
node.type === "DeclareFunction" ||
node.type === "DeclareInterface" ||
node.type === "DeclareModule" ||
node.type === "DeclareModuleExports" ||
node.type === "DeclareVariable" ||
node.type === "DoWhileStatement" ||
node.type === "ExportAllDeclaration" ||
node.type === "ExportDefaultDeclaration" ||
node.type === "ExportNamedDeclaration" ||
node.type === "ExpressionStatement" ||
node.type === "ForAwaitStatement" ||
node.type === "ForInStatement" ||
node.type === "ForOfStatement" ||
node.type === "ForStatement" ||
node.type === "FunctionDeclaration" ||
node.type === "IfStatement" ||
node.type === "ImportDeclaration" ||
node.type === "InterfaceDeclaration" ||
node.type === "LabeledStatement" ||
node.type === "MethodDefinition" ||
node.type === "ReturnStatement" ||
node.type === "SwitchStatement" ||
node.type === "ThrowStatement" ||
node.type === "TryStatement" ||
node.type === "TSAbstractClassDeclaration" ||
node.type === "TSEnumDeclaration" ||
node.type === "TSImportEqualsDeclaration" ||
node.type === "TSInterfaceDeclaration" ||
node.type === "TSModuleDeclaration" ||
node.type === "TSNamespaceExportDeclaration" ||
node.type === "TypeAlias" ||
node.type === "VariableDeclaration" ||
node.type === "WhileStatement" ||
node.type === "WithStatement"
);
}
module.exports = needsParens;

25
src/language-js/pragma.js Normal file
View File

@ -0,0 +1,25 @@
"use strict";
const docblock = require("jest-docblock");
function hasPragma(text) {
const pragmas = Object.keys(docblock.parse(docblock.extract(text)));
return pragmas.indexOf("prettier") !== -1 || pragmas.indexOf("format") !== -1;
}
function insertPragma(text) {
const parsedDocblock = docblock.parseWithComments(docblock.extract(text));
const pragmas = Object.assign({ format: "" }, parsedDocblock.pragmas);
const newDocblock = docblock.print({
pragmas,
comments: parsedDocblock.comments.replace(/^(\s+?\r?\n)+/, "") // remove leading newlines
});
const strippedText = docblock.strip(text);
const separatingNewlines = strippedText.startsWith("\n") ? "\n" : "\n\n";
return newDocblock + separatingNewlines + strippedText;
}
module.exports = {
hasPragma,
insertPragma
};

File diff suppressed because it is too large Load Diff

View File

@ -5,33 +5,42 @@ const support = require("../common/support");
const doc = require("../doc");
const docBuilders = doc.builders;
const hardline = docBuilders.hardline;
const literalline = docBuilders.literalline;
const concat = docBuilders.concat;
const markAsRoot = docBuilders.markAsRoot;
function embed(path, print, textToDoc, options) {
const node = path.getValue();
if (node.type === "code") {
const parser = getParserName(node.lang);
// only look for the first string so as to support [markdown-preview-enhanced](https://shd101wyy.github.io/markdown-preview-enhanced/#/code-chunk)
const lang = node.lang.split(/\s/, 1)[0];
const parser = getParserName(lang);
if (parser) {
const styleUnit = options.__inJsTemplate ? "~" : "`";
const style = styleUnit.repeat(
Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1)
);
const doc = textToDoc(node.value, { parser });
return concat([
style,
node.lang,
hardline,
replaceNewlinesWithHardlines(doc),
style
]);
return markAsRoot(
concat([
style,
node.lang,
hardline,
replaceNewlinesWithLiterallines(doc),
style
])
);
}
}
return null;
function getParserName(lang) {
const supportInfo = support.getSupportInfo(undefined, options);
const supportInfo = support.getSupportInfo(null, {
plugins: options.plugins,
pluginsLoaded: true
});
const language = supportInfo.languages.find(
language =>
language.name.toLowerCase() === lang ||
@ -45,7 +54,7 @@ function embed(path, print, textToDoc, options) {
return null;
}
function replaceNewlinesWithHardlines(doc) {
function replaceNewlinesWithLiterallines(doc) {
return util.mapDoc(
doc,
currentDoc =>
@ -53,7 +62,7 @@ function embed(path, print, textToDoc, options) {
? concat(
currentDoc
.split(/(\n)/g)
.map((v, i) => (i % 2 === 0 ? v : hardline))
.map((v, i) => (i % 2 === 0 ? v : literalline))
)
: currentDoc
);

View File

@ -1,6 +1,8 @@
"use strict";
const printer = require("./printer-markdown");
const options = require("./options");
const pragma = require("./pragma");
// Based on:
// https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
@ -37,7 +39,10 @@ const remark = {
get parse() {
return eval("require")("./parser-markdown");
},
astFormat: "mdast"
astFormat: "mdast",
hasPragma: pragma.hasPragma,
locStart: node => node.position.start.offset,
locEnd: node => node.position.end.offset
};
const parsers = {
@ -52,6 +57,7 @@ const printers = {
module.exports = {
languages,
options,
parsers,
printers
};

View File

@ -0,0 +1,38 @@
"use strict";
const pragmas = ["format", "prettier"];
function startWithPragma(text) {
const pragma = `@(${pragmas.join("|")})`;
const regex = new RegExp(
[
`<!--\\s*${pragma}\\s*-->`,
`<!--.*\n[\\s\\S]*(^|\n)[^\\S\n]*${pragma}[^\\S\n]*($|\n)[\\s\\S]*\n.*-->`
].join("|"),
"m"
);
const matched = text.match(regex);
return matched && matched.index === 0;
}
function extract(text) {
// yaml (---) and toml (+++)
const matched = text.match(
/^((---|\+\+\+)(?:\n[\s\S]*?\n|\n)\2(?:\n|$))?([\s\S]*)/
);
const frontMatter = matched[1];
const mainContent = matched[3];
return { frontMatter, mainContent };
}
module.exports = {
startWithPragma,
hasPragma: text => startWithPragma(extract(text).mainContent.trimLeft()),
insertPragma: text => {
const extracted = extract(text);
const pragma = `<!-- @${pragmas[0]} -->`;
return extracted.frontMatter
? `${extracted.frontMatter}\n\n${pragma}\n\n${extracted.mainContent}`
: `${pragma}\n\n${extracted.mainContent}`;
}
};

View File

@ -1,7 +1,8 @@
"use strict";
const util = require("../common/util");
const privateUtil = require("../common/util");
const embed = require("./embed");
const pragma = require("./pragma");
const doc = require("../doc");
const docBuilders = doc.builders;
const concat = docBuilders.concat;
@ -11,15 +12,10 @@ const hardline = docBuilders.hardline;
const softline = docBuilders.softline;
const fill = docBuilders.fill;
const align = docBuilders.align;
const group = docBuilders.group;
const printDocToString = doc.printer.printDocToString;
const printerOptions = require("./options");
const SINGLE_LINE_NODE_TYPES = [
"heading",
"tableCell",
"footnoteDefinition",
"link"
];
const SINGLE_LINE_NODE_TYPES = ["heading", "tableCell", "link"];
const SIBLING_NODE_TYPES = ["listItem", "definition", "footnoteDefinition"];
@ -51,7 +47,7 @@ function genericPrint(path, options, print) {
if (shouldRemainTheSameContent(path)) {
return concat(
util
privateUtil
.splitText(
options.originalText.slice(
node.position.start.offset,
@ -69,10 +65,7 @@ function genericPrint(path, options, print) {
switch (node.type) {
case "root":
return concat([
normalizeDoc(printChildren(path, options, print)),
hardline
]);
return concat([normalizeDoc(printRoot(path, options, print)), hardline]);
case "paragraph":
return printChildren(path, options, print, {
postprocessor: fill
@ -85,8 +78,8 @@ function genericPrint(path, options, print) {
.replace(
new RegExp(
[
`(^|[${util.punctuationCharRange}])(_+)`,
`(_+)([${util.punctuationCharRange}]|$)`
`(^|[${privateUtil.punctuationCharRange}])(_+)`,
`(_+)([${privateUtil.punctuationCharRange}]|$)`
].join("|"),
"g"
),
@ -118,8 +111,8 @@ function genericPrint(path, options, print) {
(prevNode &&
prevNode.type === "sentence" &&
prevNode.children.length > 0 &&
util.getLast(prevNode.children).type === "word" &&
!util.getLast(prevNode.children).hasTrailingPunctuation) ||
privateUtil.getLast(prevNode.children).type === "word" &&
!privateUtil.getLast(prevNode.children).hasTrailingPunctuation) ||
(nextNode &&
nextNode.type === "sentence" &&
nextNode.children.length > 0 &&
@ -134,7 +127,7 @@ function genericPrint(path, options, print) {
case "delete":
return concat(["~~", printChildren(path, options, print), "~~"]);
case "inlineCode": {
const backtickCount = util.getMaxContinuousCount(node.value, "`");
const backtickCount = privateUtil.getMaxContinuousCount(node.value, "`");
const style = backtickCount === 1 ? "``" : "`";
const gap = backtickCount ? " " : "";
return concat([style, gap, node.value, gap, style]);
@ -195,7 +188,10 @@ function genericPrint(path, options, print) {
// fenced code block
const styleUnit = options.__inJsTemplate ? "~" : "`";
const style = styleUnit.repeat(
Math.max(3, util.getMaxContinuousCount(node.value, styleUnit) + 1)
Math.max(
3,
privateUtil.getMaxContinuousCount(node.value, styleUnit) + 1
)
);
return concat([
style,
@ -207,15 +203,20 @@ function genericPrint(path, options, print) {
]);
}
case "yaml":
return concat(["---", hardline, node.value, hardline, "---"]);
case "toml":
return concat(["+++", hardline, node.value, hardline, "+++"]);
case "toml": {
const style = node.type === "yaml" ? "---" : "+++";
return node.value
? concat([style, hardline, node.value, hardline, style])
: concat([style, hardline, style]);
}
case "html": {
const parentNode = path.getParentNode();
return parentNode.type === "root" &&
util.getLast(parentNode.children) === node
? node.value.trimRight()
: node.value;
return replaceNewlinesWithHardlines(
parentNode.type === "root" &&
privateUtil.getLast(parentNode.children) === node
? node.value.trimRight()
: node.value
);
}
case "list": {
const nthSiblingIndex = getNthListSiblingIndex(
@ -235,32 +236,34 @@ function genericPrint(path, options, print) {
return printChildren(path, options, print, {
processor: (childPath, index) => {
const prefix = node.ordered
? (index === 0
? node.start
: isGitDiffFriendlyOrderedList ? 1 : node.start + index) +
(nthSiblingIndex % 2 === 0 ? ". " : ") ")
: nthSiblingIndex % 2 === 0 ? "* " : "- ";
const prefix = getPrefix();
return concat([
prefix,
align(" ".repeat(prefix.length), childPath.call(print))
align(
" ".repeat(prefix.length),
printListItem(childPath, options, print, prefix)
)
]);
function getPrefix() {
const rawPrefix = node.ordered
? (index === 0
? node.start
: isGitDiffFriendlyOrderedList ? 1 : node.start + index) +
(nthSiblingIndex % 2 === 0 ? ". " : ") ")
: nthSiblingIndex % 2 === 0 ? "* " : "- ";
// do not print trailing spaces for empty list item since it might be treated as `break` node
// by [doc-printer](https://github.com/prettier/prettier/blob/1.10.2/src/doc/doc-printer.js#L395-L405),
// we don't want to preserve unnecessary trailing spaces.
const listItem = childPath.getValue();
return listItem.children.length
? alignListPrefix(rawPrefix, options)
: rawPrefix;
}
}
});
}
case "listItem": {
const prefix =
node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
return concat([
prefix,
printChildren(path, options, print, {
processor: (childPath, index) =>
index === 0 && childPath.getValue().type !== "list"
? align(" ".repeat(prefix.length), childPath.call(print))
: childPath.call(print)
})
]);
}
case "thematicBreak": {
const counter = getAncestorCounter(path, "list");
if (counter === -1) {
@ -284,7 +287,7 @@ function genericPrint(path, options, print) {
case "imageReference":
switch (node.referenceType) {
case "full":
return concat(["![", node.alt, "][", node.identifier, "]"]);
return concat(["![", node.alt || "", "][", node.identifier, "]"]);
default:
return concat([
"![",
@ -305,13 +308,28 @@ function genericPrint(path, options, print) {
return concat(["[^", printChildren(path, options, print), "]"]);
case "footnoteReference":
return concat(["[^", node.identifier, "]"]);
case "footnoteDefinition":
case "footnoteDefinition": {
const nextNode = path.getParentNode().children[path.getName() + 1];
return concat([
"[^",
node.identifier,
"]: ",
printChildren(path, options, print)
group(
concat([
align(
" ".repeat(options.tabWidth),
printChildren(path, options, print, {
processor: (childPath, index) =>
index === 0
? group(concat([softline, softline, childPath.call(print)]))
: childPath.call(print)
})
),
nextNode && nextNode.type === "footnoteDefinition" ? softline : ""
])
)
]);
}
case "table":
return printTable(path, options, print);
case "tableCell":
@ -324,11 +342,47 @@ function genericPrint(path, options, print) {
hardline
]);
case "tableRow": // handled in "table"
case "listItem": // handled in "list"
default:
throw new Error(`Unknown markdown type ${JSON.stringify(node.type)}`);
}
}
function printListItem(path, options, print, listPrefix) {
const node = path.getValue();
const prefix = node.checked === null ? "" : node.checked ? "[x] " : "[ ] ";
return concat([
prefix,
printChildren(path, options, print, {
processor: (childPath, index) => {
if (index === 0 && childPath.getValue().type !== "list") {
return align(" ".repeat(prefix.length), childPath.call(print));
}
const alignment = " ".repeat(
clamp(options.tabWidth - listPrefix.length, 0, 3) // 4+ will cause indented code block
);
return concat([alignment, align(alignment, childPath.call(print))]);
}
})
]);
}
function alignListPrefix(prefix, options) {
const additionalSpaces = getAdditionalSpaces();
return (
prefix +
" ".repeat(
additionalSpaces >= 4 ? 0 : additionalSpaces // 4+ will cause indented code block
)
);
function getAdditionalSpaces() {
const restSpaces = prefix.length % options.tabWidth;
return restSpaces === 0 ? 0 : options.tabWidth - restSpaces;
}
}
function getNthListSiblingIndex(node, parentNode) {
return getNthSiblingIndex(
node,
@ -337,6 +391,10 @@ function getNthListSiblingIndex(node, parentNode) {
);
}
function replaceNewlinesWithHardlines(str) {
return join(hardline, str.split("\n"));
}
function getNthSiblingIndex(node, parentNode, condition) {
condition = condition || (() => true);
@ -407,7 +465,7 @@ function printTable(path, options, print) {
const columnMaxWidths = contents.reduce(
(currentWidths, rowContents) =>
currentWidths.map((width, columnIndex) =>
Math.max(width, util.getStringWidth(rowContents[columnIndex]))
Math.max(width, privateUtil.getStringWidth(rowContents[columnIndex]))
),
contents[0].map(() => 3) // minimum width = 3 (---, :--, :-:, --:)
);
@ -461,21 +519,83 @@ function printTable(path, options, print) {
}
function alignLeft(text, width) {
return concat([text, " ".repeat(width - util.getStringWidth(text))]);
return concat([text, " ".repeat(width - privateUtil.getStringWidth(text))]);
}
function alignRight(text, width) {
return concat([" ".repeat(width - util.getStringWidth(text)), text]);
return concat([" ".repeat(width - privateUtil.getStringWidth(text)), text]);
}
function alignCenter(text, width) {
const spaces = width - util.getStringWidth(text);
const spaces = width - privateUtil.getStringWidth(text);
const left = Math.floor(spaces / 2);
const right = spaces - left;
return concat([" ".repeat(left), text, " ".repeat(right)]);
}
}
function printRoot(path, options, print) {
/** @typedef {{ index: number, offset: number }} IgnorePosition */
/** @type {Array<{start: IgnorePosition, end: IgnorePosition}>} */
const ignoreRanges = [];
/** @type {IgnorePosition | null} */
let ignoreStart = null;
const children = path.getValue().children;
children.forEach((childNode, index) => {
switch (isPrettierIgnore(childNode)) {
case "start":
if (ignoreStart === null) {
ignoreStart = { index, offset: childNode.position.end.offset };
}
break;
case "end":
if (ignoreStart !== null) {
ignoreRanges.push({
start: ignoreStart,
end: { index, offset: childNode.position.start.offset }
});
ignoreStart = null;
}
break;
default:
// do nothing
break;
}
});
return printChildren(path, options, print, {
processor: (childPath, index) => {
if (ignoreRanges.length !== 0) {
const ignoreRange = ignoreRanges[0];
if (index === ignoreRange.start.index) {
return concat([
children[ignoreRange.start.index].value,
options.originalText.slice(
ignoreRange.start.offset,
ignoreRange.end.offset
),
children[ignoreRange.end.index].value
]);
}
if (ignoreRange.start.index < index && index < ignoreRange.end.index) {
return false;
}
if (index === ignoreRange.end.index) {
ignoreRanges.shift();
return false;
}
}
return childPath.call(print);
}
});
}
function printChildren(path, options, print, events) {
events = events || {};
@ -485,28 +605,15 @@ function printChildren(path, options, print, events) {
const node = path.getValue();
const parts = [];
let counter = 0;
let lastChildNode;
let prettierIgnore = false;
path.map((childPath, index) => {
const childNode = childPath.getValue();
const result = prettierIgnore
? options.originalText.slice(
childNode.position.start.offset,
childNode.position.end.offset
)
: processor(childPath, index);
prettierIgnore = false;
const result = processor(childPath, index);
if (result !== false) {
prettierIgnore = isPrettierIgnore(childNode);
const data = {
parts,
index: counter++,
prevNode: lastChildNode,
parentNode: node,
options
@ -536,10 +643,15 @@ function printChildren(path, options, print, events) {
return postprocessor(parts);
}
/** @return {false | 'next' | 'start' | 'end'} */
function isPrettierIgnore(node) {
return (
node.type === "html" && /^<!--\s*prettier-ignore\s*-->$/.test(node.value)
if (node.type !== "html") {
return false;
}
const match = node.value.match(
/^<!--\s*prettier-ignore(?:-(start|end))?\s*-->$/
);
return match === null ? false : match[1] ? match[1] : "next";
}
function shouldNotPrePrintHardline(node, data) {
@ -564,7 +676,7 @@ function shouldPrePrintDoubleHardline(node, data) {
const isPrevNodeLooseListItem =
data.prevNode && data.prevNode.type === "listItem" && data.prevNode.loose;
const isPrevNodePrettierIgnore = isPrettierIgnore(data.prevNode);
const isPrevNodePrettierIgnore = isPrettierIgnore(data.prevNode) === "next";
return (
isPrevNodeLooseListItem ||
@ -595,7 +707,7 @@ function shouldRemainTheSameContent(path) {
}
function normalizeDoc(doc) {
return util.mapDoc(doc, currentDoc => {
return privateUtil.mapDoc(doc, currentDoc => {
if (!currentDoc.parts) {
return currentDoc;
}
@ -647,7 +759,7 @@ function printTitle(title, options) {
function normalizeParts(parts) {
return parts.reduce((current, part) => {
const lastPart = util.getLast(current);
const lastPart = privateUtil.getLast(current);
if (typeof lastPart === "string" && typeof part === "string") {
current.splice(-1, 1, lastPart + part);
@ -659,21 +771,49 @@ function normalizeParts(parts) {
}, []);
}
function clean(ast, newObj) {
// for markdown codeblock
function clamp(value, min, max) {
return value < min ? min : value > max ? max : value;
}
function clean(ast, newObj, parent) {
// for codeblock
if (ast.type === "code") {
delete newObj.value;
}
// for markdown whitespace: "\n" and " " are considered the same
// for whitespace: "\n" and " " are considered the same
if (ast.type === "whitespace" && ast.value === "\n") {
newObj.value = " ";
}
// for insert pragma
if (
parent &&
parent.type === "root" &&
(parent.children[0] === ast ||
((parent.children[0].type === "yaml" ||
parent.children[0].type === "toml") &&
parent.children[1] === ast)) &&
ast.type === "html" &&
pragma.startWithPragma(ast.value)
) {
return null;
}
}
function hasPrettierIgnore(path) {
const index = +path.getName();
if (index === 0) {
return false;
}
const prevNode = path.getParentNode().children[index - 1];
return isPrettierIgnore(prevNode) === "next";
}
module.exports = {
options: printerOptions,
print: genericPrint,
embed,
massageAstNode: clean,
hasPrettierIgnore: util.hasIgnoreComment
hasPrettierIgnore,
insertPragma: pragma.insertPragma
};

View File

@ -7,7 +7,7 @@ const hardline = docBuilders.hardline;
function embed(path, print, textToDoc, options) {
const node = path.getValue();
const parent = path.getParentNode();
if (!parent || parent.tag !== "root") {
if (!parent || parent.tag !== "root" || node.unary) {
return null;
}
@ -15,7 +15,7 @@ function embed(path, print, textToDoc, options) {
if (node.tag === "style") {
const langAttr = node.attrs.find(attr => attr.name === "lang");
if (!langAttr) {
if (!langAttr || langAttr.value === "postcss") {
parser = "css";
} else if (langAttr.value === "scss") {
parser = "scss";
@ -28,7 +28,7 @@ function embed(path, print, textToDoc, options) {
const langAttr = node.attrs.find(attr => attr.name === "lang");
if (!langAttr) {
parser = "babylon";
} else if (langAttr.value === "ts") {
} else if (langAttr.value === "ts" || langAttr.value === "tsx") {
parser = "typescript";
}
}

View File

@ -388,12 +388,16 @@ function parse(text /*, parsers, opts*/) {
attrs,
unary,
start,
contentStart: end,
children: []
};
obj.children.push(newObj);
objStack.push(newObj);
obj = newObj;
if (unary) {
newObj.end = end;
} else {
newObj.contentStart = end;
objStack.push(newObj);
obj = newObj;
}
},
end: function(tag, start, end) {
objStack.pop();

View File

@ -3,6 +3,7 @@
const embed = require("./embed");
const docBuilders = require("../doc").builders;
const concat = docBuilders.concat;
const hardline = docBuilders.hardline;
function genericPrint(path, options, print) {
const n = path.getValue();
@ -15,8 +16,17 @@ function genericPrint(path, options, print) {
res.push(childPath.call(print));
index = child.end;
}, "children");
// If there are no children, we just print the node from start to end.
// Otherwise, index should point to the end of the last child, and we
// need to print the closing tag.
res.push(options.originalText.slice(index, n.end));
// Only force a trailing newline if there were any contents.
if (n.tag === "root" && n.children.length) {
res.push(hardline);
}
return concat(res);
}

View File

@ -4,7 +4,6 @@ const assert = require("assert");
const comments = require("./comments");
const FastPath = require("../common/fast-path");
const multiparser = require("./multiparser");
const util = require("../common/util");
const doc = require("../doc");
const docBuilders = doc.builders;
@ -75,7 +74,10 @@ function genericPrint(path, options, printPath, args) {
// Escape hatch
if (printer.hasPrettierIgnore && printer.hasPrettierIgnore(path)) {
return options.originalText.slice(util.locStart(node), util.locEnd(node));
return options.originalText.slice(
options.locStart(node),
options.locEnd(node)
);
}
if (node) {

View File

@ -9,20 +9,21 @@ const indent = docBuilders.indent;
const lineSuffix = docBuilders.lineSuffix;
const join = docBuilders.join;
const cursor = docBuilders.cursor;
const util = require("../common/util");
const privateUtil = require("../common/util");
const sharedUtil = require("../common/util-shared");
const childNodesCacheKey = Symbol("child-nodes");
const locStart = util.locStart;
const locEnd = util.locEnd;
const getNextNonSpaceNonCommentCharacter =
util.getNextNonSpaceNonCommentCharacter;
const getNextNonSpaceNonCommentCharacterIndex =
util.getNextNonSpaceNonCommentCharacterIndex;
const addLeadingComment = sharedUtil.addLeadingComment;
const addTrailingComment = sharedUtil.addTrailingComment;
const addDanglingComment = sharedUtil.addDanglingComment;
function getSortedChildNodes(node, text, options, resultArray) {
if (!node) {
return;
}
const printer = options.printer;
const locStart = options.locStart;
const locEnd = options.locEnd;
if (resultArray) {
if (node && printer.canAttachComment && printer.canAttachComment(node)) {
@ -45,13 +46,22 @@ function getSortedChildNodes(node, text, options, resultArray) {
return node[childNodesCacheKey];
}
let names;
if (node && typeof node === "object") {
names = Object.keys(node).filter(
n =>
n !== "enclosingNode" && n !== "precedingNode" && n !== "followingNode"
);
} else {
let childNodes;
if (printer.getCommentChildNodes) {
childNodes = printer.getCommentChildNodes(node);
} else if (node && typeof node === "object") {
childNodes = Object.keys(node)
.filter(
n =>
n !== "enclosingNode" &&
n !== "precedingNode" &&
n !== "followingNode"
)
.map(n => node[n]);
}
if (!childNodes) {
return;
}
@ -62,9 +72,9 @@ function getSortedChildNodes(node, text, options, resultArray) {
});
}
for (let i = 0, nameCount = names.length; i < nameCount; ++i) {
getSortedChildNodes(node[names[i]], text, options, resultArray);
}
childNodes.forEach(childNode => {
getSortedChildNodes(childNode, text, options, resultArray);
});
return resultArray;
}
@ -73,6 +83,8 @@ function getSortedChildNodes(node, text, options, resultArray) {
// .precedingNode, .enclosingNode, and/or .followingNode properties, at
// least one of which is guaranteed to be defined.
function decorateComment(node, comment, text, options) {
const locStart = options.locStart;
const locEnd = options.locEnd;
const childNodes = getSortedChildNodes(node, text, options);
let precedingNode;
let followingNode;
@ -125,17 +137,23 @@ function decorateComment(node, comment, text, options) {
comment.enclosingNode.type === "TemplateLiteral"
) {
const quasis = comment.enclosingNode.quasis;
const commentIndex = findExpressionIndexForComment(quasis, comment);
const commentIndex = findExpressionIndexForComment(
quasis,
comment,
options
);
if (
precedingNode &&
findExpressionIndexForComment(quasis, precedingNode) !== commentIndex
findExpressionIndexForComment(quasis, precedingNode, options) !==
commentIndex
) {
precedingNode = null;
}
if (
followingNode &&
findExpressionIndexForComment(quasis, followingNode) !== commentIndex
findExpressionIndexForComment(quasis, followingNode, options) !==
commentIndex
) {
followingNode = null;
}
@ -156,6 +174,8 @@ function attach(comments, ast, text, options) {
}
const tiesToBreak = [];
const locStart = options.locStart;
const locEnd = options.locEnd;
comments.forEach((comment, i) => {
if (options.parser === "json" && locStart(comment) - locStart(ast) <= 0) {
@ -169,51 +189,26 @@ function attach(comments, ast, text, options) {
const enclosingNode = comment.enclosingNode;
const followingNode = comment.followingNode;
const pluginHandleOwnLineComment =
options.printer.handleComments && options.printer.handleComments.ownLine
? options.printer.handleComments.ownLine
: () => false;
const pluginHandleEndOfLineComment =
options.printer.handleComments && options.printer.handleComments.endOfLine
? options.printer.handleComments.endOfLine
: () => false;
const pluginHandleRemainingComment =
options.printer.handleComments && options.printer.handleComments.remaining
? options.printer.handleComments.remaining
: () => false;
const isLastComment = comments.length - 1 === i;
if (util.hasNewline(text, locStart(comment), { backwards: true })) {
if (privateUtil.hasNewline(text, locStart(comment), { backwards: true })) {
// If a comment exists on its own line, prefer a leading comment.
// We also need to check if it's the first line of the file.
if (
handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleTryStatementComments(enclosingNode, followingNode, comment) ||
handleClassComments(
enclosingNode,
precedingNode,
followingNode,
comment
) ||
handleImportSpecifierComments(enclosingNode, comment) ||
handleForComments(enclosingNode, precedingNode, comment) ||
handleUnionTypeComments(
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleImportDeclarationComments(
text,
enclosingNode,
precedingNode,
comment
) ||
handleAssignmentPatternComments(enclosingNode, comment) ||
handleMethodNameComments(text, enclosingNode, precedingNode, comment)
pluginHandleOwnLineComment(comment, text, options, ast, isLastComment)
) {
// We're good
} else if (followingNode) {
@ -228,43 +223,9 @@ function attach(comments, ast, text, options) {
/* istanbul ignore next */
addDanglingComment(ast, comment);
}
} else if (util.hasNewline(text, locEnd(comment))) {
} else if (privateUtil.hasNewline(text, locEnd(comment))) {
if (
handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleConditionalExpressionComments(
enclosingNode,
precedingNode,
followingNode,
comment,
text
) ||
handleImportSpecifierComments(enclosingNode, comment) ||
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleClassComments(
enclosingNode,
precedingNode,
followingNode,
comment
) ||
handleLabeledStatementComments(enclosingNode, comment) ||
handleCallExpressionComments(precedingNode, enclosingNode, comment) ||
handlePropertyComments(enclosingNode, comment) ||
handleExportNamedDeclarationComments(enclosingNode, comment) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleTypeAliasComments(enclosingNode, followingNode, comment) ||
handleVariableDeclaratorComments(enclosingNode, followingNode, comment)
pluginHandleEndOfLineComment(comment, text, options, ast, isLastComment)
) {
// We're good
} else if (precedingNode) {
@ -282,19 +243,7 @@ function attach(comments, ast, text, options) {
}
} else {
if (
handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) ||
handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) ||
handleCommentInEmptyParens(text, enclosingNode, comment) ||
handleMethodNameComments(text, enclosingNode, precedingNode, comment) ||
handleOnlyComments(enclosingNode, ast, comment, isLastComment) ||
handleCommentAfterArrowParams(text, enclosingNode, comment) ||
handleFunctionNameComments(text, enclosingNode, precedingNode, comment)
pluginHandleRemainingComment(comment, text, options, ast, isLastComment)
) {
// We're good
} else if (precedingNode && followingNode) {
@ -307,7 +256,7 @@ function attach(comments, ast, text, options) {
if (tieCount > 0) {
const lastTie = tiesToBreak[tieCount - 1];
if (lastTie.followingNode !== comment.followingNode) {
breakTies(tiesToBreak, text);
breakTies(tiesToBreak, text, options);
}
}
tiesToBreak.push(comment);
@ -325,7 +274,7 @@ function attach(comments, ast, text, options) {
}
});
breakTies(tiesToBreak, text);
breakTies(tiesToBreak, text, options);
comments.forEach(comment => {
// These node references were useful for breaking ties, but we
@ -337,7 +286,7 @@ function attach(comments, ast, text, options) {
});
}
function breakTies(tiesToBreak, text) {
function breakTies(tiesToBreak, text, options) {
const tieCount = tiesToBreak.length;
if (tieCount === 0) {
return;
@ -345,7 +294,7 @@ function breakTies(tiesToBreak, text) {
const precedingNode = tiesToBreak[0].precedingNode;
const followingNode = tiesToBreak[0].followingNode;
let gapEndPos = locStart(followingNode);
let gapEndPos = options.locStart(followingNode);
// Iterate backwards through tiesToBreak, examining the gaps
// between the tied comments. In order to qualify as leading, a
@ -362,9 +311,9 @@ function breakTies(tiesToBreak, text) {
assert.strictEqual(comment.precedingNode, precedingNode);
assert.strictEqual(comment.followingNode, followingNode);
const gap = text.slice(locEnd(comment), gapEndPos).trim();
const gap = text.slice(options.locEnd(comment), gapEndPos).trim();
if (gap === "" || /^\(+$/.test(gap)) {
gapEndPos = locStart(comment);
gapEndPos = options.locStart(comment);
} else {
// The gap string contained something other than whitespace or open
// parentheses.
@ -383,540 +332,14 @@ function breakTies(tiesToBreak, text) {
tiesToBreak.length = 0;
}
function addCommentHelper(node, comment) {
const comments = node.comments || (node.comments = []);
comments.push(comment);
comment.printed = false;
// For some reason, TypeScript parses `// x` inside of JSXText as a comment
// We already "print" it via the raw text, we don't need to re-print it as a
// comment
if (node.type === "JSXText") {
comment.printed = true;
}
}
function addLeadingComment(node, comment) {
comment.leading = true;
comment.trailing = false;
addCommentHelper(node, comment);
}
function addDanglingComment(node, comment) {
comment.leading = false;
comment.trailing = false;
addCommentHelper(node, comment);
}
function addTrailingComment(node, comment) {
comment.leading = false;
comment.trailing = true;
addCommentHelper(node, comment);
}
function addBlockStatementFirstComment(node, comment) {
const body = node.body.filter(n => n.type !== "EmptyStatement");
if (body.length === 0) {
addDanglingComment(node, comment);
} else {
addLeadingComment(body[0], comment);
}
}
function addBlockOrNotComment(node, comment) {
if (node.type === "BlockStatement") {
addBlockStatementFirstComment(node, comment);
} else {
addLeadingComment(node, comment);
}
}
// There are often comments before the else clause of if statements like
//
// if (1) { ... }
// // comment
// else { ... }
//
// They are being attached as leading comments of the BlockExpression which
// is not well printed. What we want is to instead move the comment inside
// of the block and make it leadingComment of the first element of the block
// or dangling comment of the block if there is nothing inside
//
// if (1) { ... }
// else {
// // comment
// ...
// }
function handleIfStatementComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) {
if (
!enclosingNode ||
enclosingNode.type !== "IfStatement" ||
!followingNode
) {
return false;
}
// We unfortunately have no way using the AST or location of nodes to know
// if the comment is positioned before the condition parenthesis:
// if (a /* comment */) {}
// The only workaround I found is to look at the next character to see if
// it is a ).
const nextCharacter = getNextNonSpaceNonCommentCharacter(text, comment);
if (nextCharacter === ")") {
addTrailingComment(precedingNode, comment);
return true;
}
if (followingNode.type === "BlockStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
if (followingNode.type === "IfStatement") {
addBlockOrNotComment(followingNode.consequent, comment);
return true;
}
// For comments positioned after the condition parenthesis in an if statement
// before the consequent with or without brackets on, such as
// if (a) /* comment */ {} or if (a) /* comment */ true,
// we look at the next character to see if it is a { or if the following node
// is the consequent for the if statement
if (nextCharacter === "{" || enclosingNode.consequent === followingNode) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
// Same as IfStatement but for TryStatement
function handleTryStatementComments(enclosingNode, followingNode, comment) {
if (
!enclosingNode ||
enclosingNode.type !== "TryStatement" ||
!followingNode
) {
return false;
}
if (followingNode.type === "BlockStatement") {
addBlockStatementFirstComment(followingNode, comment);
return true;
}
if (followingNode.type === "TryStatement") {
addBlockOrNotComment(followingNode.finalizer, comment);
return true;
}
if (followingNode.type === "CatchClause") {
addBlockOrNotComment(followingNode.body, comment);
return true;
}
return false;
}
function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
if (
enclosingNode &&
enclosingNode.type === "MemberExpression" &&
followingNode &&
followingNode.type === "Identifier"
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleConditionalExpressionComments(
enclosingNode,
precedingNode,
followingNode,
comment,
text
) {
const isSameLineAsPrecedingNode =
precedingNode &&
!util.hasNewlineInRange(text, locEnd(precedingNode), locStart(comment));
if (
(!precedingNode || !isSameLineAsPrecedingNode) &&
enclosingNode &&
enclosingNode.type === "ConditionalExpression" &&
followingNode
) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
function handleObjectPropertyAssignment(enclosingNode, precedingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ObjectProperty" ||
enclosingNode.type === "Property") &&
enclosingNode.shorthand &&
enclosingNode.key === precedingNode &&
enclosingNode.value.type === "AssignmentPattern"
) {
addTrailingComment(enclosingNode.value.left, comment);
return true;
}
return false;
}
function handleClassComments(
enclosingNode,
precedingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "ClassDeclaration" ||
enclosingNode.type === "ClassExpression") &&
(enclosingNode.decorators && enclosingNode.decorators.length > 0) &&
!(followingNode && followingNode.type === "Decorator")
) {
if (!enclosingNode.decorators || enclosingNode.decorators.length === 0) {
addLeadingComment(enclosingNode, comment);
} else {
addTrailingComment(
enclosingNode.decorators[enclosingNode.decorators.length - 1],
comment
);
}
return true;
}
return false;
}
function handleMethodNameComments(text, enclosingNode, precedingNode, comment) {
// This is only needed for estree parsers (flow, typescript) to attach
// after a method name:
// obj = { fn /*comment*/() {} };
if (
enclosingNode &&
precedingNode &&
(enclosingNode.type === "Property" ||
enclosingNode.type === "MethodDefinition") &&
precedingNode.type === "Identifier" &&
enclosingNode.key === precedingNode &&
// special Property case: { key: /*comment*/(value) };
// comment should be attached to value instead of key
getNextNonSpaceNonCommentCharacter(text, precedingNode) !== ":"
) {
addTrailingComment(precedingNode, comment);
return true;
}
// Print comments between decorators and class methods as a trailing comment
// on the decorator node instead of the method node
if (
precedingNode &&
enclosingNode &&
precedingNode.type === "Decorator" &&
(enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "ClassProperty" ||
enclosingNode.type === "TSAbstractClassProperty" ||
enclosingNode.type === "TSAbstractMethodDefinition" ||
enclosingNode.type === "MethodDefinition")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleFunctionNameComments(
text,
enclosingNode,
precedingNode,
comment
) {
if (getNextNonSpaceNonCommentCharacter(text, comment) !== "(") {
return false;
}
if (
precedingNode &&
enclosingNode &&
(enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "FunctionExpression" ||
enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "MethodDefinition" ||
enclosingNode.type === "ObjectMethod")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleCommentAfterArrowParams(text, enclosingNode, comment) {
if (!(enclosingNode && enclosingNode.type === "ArrowFunctionExpression")) {
return false;
}
const index = getNextNonSpaceNonCommentCharacterIndex(text, comment);
if (text.substr(index, 2) === "=>") {
addDanglingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleCommentInEmptyParens(text, enclosingNode, comment) {
if (getNextNonSpaceNonCommentCharacter(text, comment) !== ")") {
return false;
}
// Only add dangling comments to fix the case when no params are present,
// i.e. a function without any argument.
if (
enclosingNode &&
(((enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "FunctionExpression" ||
(enclosingNode.type === "ArrowFunctionExpression" &&
(enclosingNode.body.type !== "CallExpression" ||
enclosingNode.body.arguments.length === 0)) ||
enclosingNode.type === "ClassMethod" ||
enclosingNode.type === "ObjectMethod") &&
enclosingNode.params.length === 0) ||
(enclosingNode.type === "CallExpression" &&
enclosingNode.arguments.length === 0))
) {
addDanglingComment(enclosingNode, comment);
return true;
}
if (
enclosingNode &&
(enclosingNode.type === "MethodDefinition" &&
enclosingNode.value.params.length === 0)
) {
addDanglingComment(enclosingNode.value, comment);
return true;
}
return false;
}
function handleLastFunctionArgComments(
text,
precedingNode,
enclosingNode,
followingNode,
comment
) {
// Type definitions functions
if (
precedingNode &&
precedingNode.type === "FunctionTypeParam" &&
enclosingNode &&
enclosingNode.type === "FunctionTypeAnnotation" &&
followingNode &&
followingNode.type !== "FunctionTypeParam"
) {
addTrailingComment(precedingNode, comment);
return true;
}
// Real functions
if (
precedingNode &&
(precedingNode.type === "Identifier" ||
precedingNode.type === "AssignmentPattern") &&
enclosingNode &&
(enclosingNode.type === "ArrowFunctionExpression" ||
enclosingNode.type === "FunctionExpression" ||
enclosingNode.type === "FunctionDeclaration" ||
enclosingNode.type === "ObjectMethod" ||
enclosingNode.type === "ClassMethod") &&
getNextNonSpaceNonCommentCharacter(text, comment) === ")"
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleImportSpecifierComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "ImportSpecifier") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleLabeledStatementComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "LabeledStatement") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleCallExpressionComments(precedingNode, enclosingNode, comment) {
if (
enclosingNode &&
enclosingNode.type === "CallExpression" &&
precedingNode &&
enclosingNode.callee === precedingNode &&
enclosingNode.arguments.length > 0
) {
addLeadingComment(enclosingNode.arguments[0], comment);
return true;
}
return false;
}
function handleUnionTypeComments(
precedingNode,
enclosingNode,
followingNode,
comment
) {
if (
enclosingNode &&
(enclosingNode.type === "UnionTypeAnnotation" ||
enclosingNode.type === "TSUnionType")
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handlePropertyComments(enclosingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "Property" ||
enclosingNode.type === "ObjectProperty")
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleExportNamedDeclarationComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "ExportNamedDeclaration") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleOnlyComments(enclosingNode, ast, comment, isLastComment) {
// With Flow the enclosingNode is undefined so use the AST instead.
if (ast && ast.body && ast.body.length === 0) {
if (isLastComment) {
addDanglingComment(ast, comment);
} else {
addLeadingComment(ast, comment);
}
return true;
} else if (
enclosingNode &&
enclosingNode.type === "Program" &&
enclosingNode.body.length === 0 &&
enclosingNode.directives &&
enclosingNode.directives.length === 0
) {
if (isLastComment) {
addDanglingComment(enclosingNode, comment);
} else {
addLeadingComment(enclosingNode, comment);
}
return true;
}
return false;
}
function handleForComments(enclosingNode, precedingNode, comment) {
if (
enclosingNode &&
(enclosingNode.type === "ForInStatement" ||
enclosingNode.type === "ForOfStatement")
) {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleImportDeclarationComments(
text,
enclosingNode,
precedingNode,
comment
) {
if (
precedingNode &&
enclosingNode &&
enclosingNode.type === "ImportDeclaration" &&
util.hasNewline(text, util.locEnd(comment))
) {
addTrailingComment(precedingNode, comment);
return true;
}
return false;
}
function handleAssignmentPatternComments(enclosingNode, comment) {
if (enclosingNode && enclosingNode.type === "AssignmentPattern") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleTypeAliasComments(enclosingNode, followingNode, comment) {
if (enclosingNode && enclosingNode.type === "TypeAlias") {
addLeadingComment(enclosingNode, comment);
return true;
}
return false;
}
function handleVariableDeclaratorComments(
enclosingNode,
followingNode,
comment
) {
if (
enclosingNode &&
enclosingNode.type === "VariableDeclarator" &&
followingNode &&
(followingNode.type === "ObjectExpression" ||
followingNode.type === "ArrayExpression")
) {
addLeadingComment(followingNode, comment);
return true;
}
return false;
}
function printComment(commentPath, options) {
const comment = commentPath.getValue();
comment.printed = true;
return options.printer.printComment(commentPath, options);
}
function findExpressionIndexForComment(quasis, comment) {
const startPos = locStart(comment) - 1;
function findExpressionIndexForComment(quasis, comment, options) {
const startPos = options.locStart(comment) - 1;
for (let i = 1; i < quasis.length; ++i) {
if (startPos < getQuasiRange(quasis[i]).start) {
@ -945,14 +368,16 @@ function printLeadingComment(commentPath, print, options) {
if (!contents) {
return "";
}
const isBlock = util.isBlockComment(comment);
const isBlock = privateUtil.isBlockComment(comment);
// Leading block comments should see if they need to stay on the
// same line or not.
if (isBlock) {
return concat([
contents,
util.hasNewline(options.originalText, locEnd(comment)) ? hardline : " "
privateUtil.hasNewline(options.originalText, options.locEnd(comment))
? hardline
: " "
]);
}
@ -965,10 +390,21 @@ function printTrailingComment(commentPath, print, options) {
if (!contents) {
return "";
}
const isBlock = util.isBlockComment(comment);
const isBlock = privateUtil.isBlockComment(comment);
// We don't want the line to break
// when the parentParentNode is a ClassDeclaration/-Expression
// And the parentNode is in the superClass property
const parentNode = commentPath.getNode(1);
const parentParentNode = commentPath.getNode(2);
const isParentSuperClass =
parentParentNode &&
(parentParentNode.type === "ClassDeclaration" ||
parentParentNode.type === "ClassExpression") &&
parentParentNode.superClass === parentNode;
if (
util.hasNewline(options.originalText, locStart(comment), {
privateUtil.hasNewline(options.originalText, options.locStart(comment), {
backwards: true
})
) {
@ -984,15 +420,16 @@ function printTrailingComment(commentPath, print, options) {
// if this a comment on its own line; normal trailing comments are
// always at the end of another expression.
const isLineBeforeEmpty = util.isPreviousLineEmpty(
const isLineBeforeEmpty = privateUtil.isPreviousLineEmpty(
options.originalText,
comment
comment,
options.locStart
);
return lineSuffix(
concat([hardline, isLineBeforeEmpty ? hardline : "", contents])
);
} else if (isBlock) {
} else if (isBlock || isParentSuperClass) {
// Trailing block comments never need a newline
return concat([" ", contents]);
}
@ -1062,7 +499,12 @@ function printComments(path, print, options, needsSemi) {
leadingParts.push(contents);
const text = options.originalText;
if (util.hasNewline(text, util.skipNewline(text, util.locEnd(comment)))) {
if (
privateUtil.hasNewline(
text,
privateUtil.skipNewline(text, options.locEnd(comment))
)
) {
leadingParts.push(hardline);
}
} else if (trailing) {

19
src/main/get-plugin.js Normal file
View File

@ -0,0 +1,19 @@
"use strict";
function getPlugin(options) {
const astFormat = options.astFormat;
if (!astFormat) {
throw new Error("getPlugin() requires astFormat to be set");
}
const printerPlugin = options.plugins.find(
plugin => plugin.printers[astFormat]
);
if (!printerPlugin) {
throw new Error(`Couldn't find plugin for AST format "${astFormat}"`);
}
return printerPlugin;
}
module.exports = getPlugin;

View File

@ -1,21 +0,0 @@
"use strict";
function getPrinter(options) {
const astFormat = options.astFormat;
if (!astFormat) {
throw new Error("getPrinter() requires astFormat to be set");
}
const printerPlugin = options.plugins.find(
plugin => plugin.printers[astFormat]
);
if (!printerPlugin) {
throw new Error(
`Couldn't find printer plugin for AST format "${astFormat}"`
);
}
return printerPlugin.printers[astFormat];
}
module.exports = getPrinter;

View File

@ -20,10 +20,14 @@ function textToDoc(text, partialNextOptions, parentOptions) {
Object.assign({}, parentOptions, partialNextOptions, {
parentParser: parentOptions.parser,
originalText: text
})
}),
{ passThrough: true, inferParser: false }
);
const ast = require("./parser").parse(text, nextOptions);
const result = require("./parser").parse(text, nextOptions);
const ast = result.ast;
text = result.text;
const astComments = ast.comments;
delete ast.comments;
comments.attach(astComments, ast, text, nextOptions);

View File

@ -0,0 +1,22 @@
"use strict";
function apiDescriptor(name, value) {
return arguments.length === 1
? JSON.stringify(name)
: `\`{ ${apiDescriptor(name)}: ${JSON.stringify(value)} }\``;
}
function cliDescriptor(name, value) {
return value === false
? `\`--no-${name}\``
: value === true || arguments.length === 1
? `\`--${name}\``
: value === ""
? `\`--${name}\` without an argument`
: `\`--${name}=${value}\``;
}
module.exports = {
apiDescriptor,
cliDescriptor
};

View File

@ -0,0 +1,153 @@
"use strict";
const leven = require("leven");
const validator = require("./options-validator");
const descriptors = require("./options-descriptor");
function normalizeOptions(options, optionInfos, opts) {
opts = opts || {};
const logger =
opts.logger === false
? { warn() {} }
: opts.logger !== undefined ? opts.logger : console;
const descriptor = opts.descriptor || descriptors.apiDescriptor;
const passThrough = opts.passThrough || [];
const optionInfoMap = optionInfos.reduce(
(reduced, optionInfo) =>
Object.assign(reduced, { [optionInfo.name]: optionInfo }),
{}
);
const normalizedOptions = Object.keys(options).reduce((newOptions, key) => {
const optionInfo = optionInfoMap[key];
let optionName = key;
let optionValue = options[key];
if (!optionInfo) {
if (passThrough === true || passThrough.indexOf(optionName) !== -1) {
newOptions[optionName] = optionValue;
} else {
logger.warn(
createUnknownOptionMessage(
optionName,
optionValue,
optionInfos,
descriptor
)
);
}
return newOptions;
}
if (!optionInfo.deprecated) {
optionValue = normalizeOption(optionValue, optionInfo);
} else if (typeof optionInfo.redirect === "string") {
logger.warn(createRedirectOptionMessage(optionInfo, descriptor));
optionName = optionInfo.redirect;
} else if (optionValue) {
logger.warn(createRedirectOptionMessage(optionInfo, descriptor));
optionValue = optionInfo.redirect.value;
optionName = optionInfo.redirect.option;
}
if (optionInfo.choices) {
const choiceInfo = optionInfo.choices.find(
choice => choice.value === optionValue
);
if (choiceInfo && choiceInfo.deprecated) {
logger.warn(
createRedirectChoiceMessage(optionInfo, choiceInfo, descriptor)
);
optionValue = choiceInfo.redirect;
}
}
if (optionInfo.array && !Array.isArray(optionValue)) {
optionValue = [optionValue];
}
if (optionValue !== optionInfo.default) {
validator.validateOption(optionValue, optionInfoMap[optionName], {
descriptor
});
}
newOptions[optionName] = optionValue;
return newOptions;
}, {});
return normalizedOptions;
}
function normalizeOption(option, optionInfo) {
return optionInfo.type === "int" ? Number(option) : option;
}
function createUnknownOptionMessage(key, value, optionInfos, descriptor) {
const messages = [`Ignored unknown option ${descriptor(key, value)}.`];
const suggestedOptionInfo = optionInfos.find(
optionInfo => leven(optionInfo.name, key) < 3
);
if (suggestedOptionInfo) {
messages.push(`Did you mean ${JSON.stringify(suggestedOptionInfo.name)}?`);
}
return messages.join(" ");
}
function createRedirectOptionMessage(optionInfo, descriptor) {
return `${descriptor(
optionInfo.name
)} is deprecated. Prettier now treats it as ${
typeof optionInfo.redirect === "string"
? descriptor(optionInfo.redirect)
: descriptor(optionInfo.redirect.option, optionInfo.redirect.value)
}.`;
}
function createRedirectChoiceMessage(optionInfo, choiceInfo, descriptor) {
return `${descriptor(
optionInfo.name,
choiceInfo.value
)} is deprecated. Prettier now treats it as ${descriptor(
optionInfo.name,
choiceInfo.redirect
)}.`;
}
function normalizeApiOptions(options, optionInfos, opts) {
return normalizeOptions(
options,
optionInfos,
Object.assign({ descriptor: descriptors.apiDescriptor }, opts)
);
}
function normalizeCliOptions(options, optionInfos, opts) {
const args = options["_"] || [];
const newOptions = normalizeOptions(
Object.keys(options).reduce(
(reduced, key) =>
Object.assign(
reduced,
key.length === 1 // omit alias
? null
: { [key]: options[key] }
),
{}
),
optionInfos,
Object.assign({ descriptor: descriptors.cliDescriptor }, opts)
);
newOptions["_"] = args;
return newOptions;
}
module.exports = {
normalizeApiOptions,
normalizeCliOptions
};

View File

@ -0,0 +1,81 @@
"use strict";
const descriptors = require("./options-descriptor");
function validateOption(value, optionInfo, opts) {
opts = opts || {};
const descriptor = opts.descriptor || descriptors.apiDescriptor;
if (
typeof optionInfo.exception === "function" &&
optionInfo.exception(value)
) {
return;
}
try {
validateOptionType(value, optionInfo);
} catch (error) {
throw new Error(
`Invalid \`${descriptor(optionInfo.name)}\` value. ${
error.message
}, but received \`${JSON.stringify(value)}\`.`
);
}
}
function validateOptionType(value, optionInfo) {
if (optionInfo.array) {
if (!Array.isArray(value)) {
throw new Error(`Expected an array`);
}
value.forEach(v =>
validateOptionType(v, Object.assign({}, optionInfo, { array: false }))
);
} else {
switch (optionInfo.type) {
case "int":
validateIntOption(value);
break;
case "boolean":
validateBooleanOption(value);
break;
case "choice":
validateChoiceOption(value, optionInfo.choices);
break;
}
}
}
function validateBooleanOption(value) {
if (typeof value !== "boolean") {
throw new Error(`Expected a boolean`);
}
}
function validateIntOption(value) {
if (
!(
typeof value === "number" &&
Math.floor(value) === value &&
value >= 0 &&
value !== Infinity
)
) {
throw new Error(`Expected an integer`);
}
}
function validateChoiceOption(value, choiceInfos) {
if (!choiceInfos.some(choiceInfo => choiceInfo.value === value)) {
const choices = choiceInfos
.filter(choiceInfo => !choiceInfo.deprecated)
.map(choiceInfo => JSON.stringify(choiceInfo.value))
.sort();
const head = choices.slice(0, -2);
const tail = choices.slice(-2);
throw new Error(`Expected ${head.concat(tail.join(" or ")).join(", ")}`);
}
}
module.exports = { validateOption };

View File

@ -1,139 +1,120 @@
"use strict";
const path = require("path");
const validate = require("jest-validate").validate;
const deprecatedConfig = require("./deprecated");
const getSupportInfo = require("../common/support").getSupportInfo;
const normalizer = require("./options-normalizer");
const loadPlugins = require("../common/load-plugins");
const resolveParser = require("./parser").resolveParser;
const getPrinter = require("./get-printer");
const getPlugin = require("./get-plugin");
const defaults = {
cursorOffset: -1,
rangeStart: 0,
rangeEnd: Infinity,
useTabs: false,
tabWidth: 2,
printWidth: 80,
singleQuote: false,
trailingComma: "none",
bracketSpacing: true,
jsxBracketSameLine: false,
parser: "babylon",
parentParser: "",
insertPragma: false,
requirePragma: false,
semi: true,
proseWrap: "preserve",
arrowParens: "avoid",
plugins: [],
const hiddenDefaults = {
astFormat: "estree",
printer: {},
__inJsTemplate: false
locStart: null,
locEnd: null
};
const exampleConfig = Object.assign({}, defaults, {
filepath: "path/to/Filename",
printWidth: 80,
originalText: "text"
});
// Copy options and fill in default values.
function normalize(options) {
const normalized = Object.assign({}, options || {});
const filepath = normalized.filepath;
function normalize(options, opts) {
opts = opts || {};
normalized.plugins = loadPlugins(normalized);
const rawOptions = Object.assign({}, options);
if (
filepath &&
!normalized.parentParser &&
(!normalized.parser || normalized.parser === defaults.parser)
) {
const extension = path.extname(filepath);
const filename = path.basename(filepath).toLowerCase();
const plugins = loadPlugins(rawOptions.plugins);
rawOptions.plugins = plugins;
const language = getSupportInfo(null, normalized).languages.find(
language =>
typeof language.since === "string" &&
(language.extensions.indexOf(extension) > -1 ||
(language.filenames &&
language.filenames.find(name => name.toLowerCase() === filename)))
);
const supportOptions = getSupportInfo(null, {
plugins,
pluginsLoaded: true,
showUnreleased: true,
showDeprecated: true
}).options;
const defaults = supportOptions.reduce(
(reduced, optionInfo) =>
Object.assign(reduced, { [optionInfo.name]: optionInfo.default }),
Object.assign({}, hiddenDefaults)
);
if (language) {
normalized.parser = language.parsers[0];
if (opts.inferParser !== false) {
if (
rawOptions.filepath &&
(!rawOptions.parser || rawOptions.parser === defaults.parser)
) {
const inferredParser = inferParser(
rawOptions.filepath,
rawOptions.plugins
);
if (inferredParser) {
rawOptions.parser = inferredParser;
}
}
}
if (normalized.parser === "json") {
normalized.trailingComma = "none";
}
const parser = resolveParser(
!rawOptions.parser
? rawOptions
: // handle deprecated parsers
normalizer.normalizeApiOptions(
rawOptions,
[supportOptions.find(x => x.name === "parser")],
{ passThrough: true, logger: false }
)
);
rawOptions.astFormat = parser.astFormat;
rawOptions.locEnd = parser.locEnd;
rawOptions.locStart = parser.locStart;
/* istanbul ignore if */
if (typeof normalized.trailingComma === "boolean") {
// Support a deprecated boolean type for the trailing comma config
// for a few versions. This code can be removed later.
normalized.trailingComma = "es5";
const plugin = getPlugin(rawOptions);
rawOptions.printer = plugin.printers[rawOptions.astFormat];
// eslint-disable-next-line no-console
console.warn(
"Warning: `trailingComma` without any argument is deprecated. " +
'Specify "none", "es5", or "all".'
const pluginDefaults = supportOptions
.filter(
optionInfo =>
optionInfo.pluginDefaults && optionInfo.pluginDefaults[plugin.name]
)
.reduce(
(reduced, optionInfo) =>
Object.assign(reduced, {
[optionInfo.name]: optionInfo.pluginDefaults[plugin.name]
}),
{}
);
}
/* istanbul ignore if */
if (typeof normalized.proseWrap === "boolean") {
normalized.proseWrap = normalized.proseWrap ? "always" : "never";
const mixedDefaults = Object.assign({}, defaults, pluginDefaults);
// eslint-disable-next-line no-console
console.warn(
"Warning: `proseWrap` with boolean value is deprecated. " +
'Use "always", "never", or "preserve" instead.'
);
}
/* istanbul ignore if */
if (normalized.parser === "postcss") {
normalized.parser = "css";
// eslint-disable-next-line no-console
console.warn(
'Warning: `parser` with value "postcss" is deprecated. ' +
'Use "css", "less" or "scss" instead.'
);
}
const parserBackup = normalized.parser;
if (typeof normalized.parser === "function") {
// Delete the function from the object to pass validation.
delete normalized.parser;
}
validate(normalized, { exampleConfig, deprecatedConfig });
// Restore the option back to a function;
normalized.parser = parserBackup;
// For backward compatibility. Deprecated in 0.0.10
/* istanbul ignore if */
if ("useFlowParser" in normalized) {
normalized.parser = normalized.useFlowParser ? "flow" : "babylon";
delete normalized.useFlowParser;
}
normalized.astFormat = resolveParser(normalized).astFormat;
normalized.printer = getPrinter(normalized);
Object.keys(defaults).forEach(k => {
if (normalized[k] == null) {
normalized[k] = defaults[k];
Object.keys(mixedDefaults).forEach(k => {
if (rawOptions[k] == null) {
rawOptions[k] = mixedDefaults[k];
}
});
return normalized;
if (rawOptions.parser === "json") {
rawOptions.trailingComma = "none";
}
return normalizer.normalizeApiOptions(
rawOptions,
supportOptions,
Object.assign({ passThrough: Object.keys(hiddenDefaults) }, opts)
);
}
module.exports = { normalize, defaults };
function inferParser(filepath, plugins) {
const extension = path.extname(filepath);
const filename = path.basename(filepath).toLowerCase();
const language = getSupportInfo(null, {
plugins,
pluginsLoaded: true
}).languages.find(
language =>
language.since !== null &&
(language.extensions.indexOf(extension) > -1 ||
(language.filenames &&
language.filenames.find(name => name.toLowerCase() === filename)))
);
return language && language.parsers[0];
}
module.exports = { normalize, hiddenDefaults };

View File

@ -2,6 +2,10 @@
const path = require("path");
const ConfigError = require("../common/errors").ConfigError;
const js = require("../language-js/index.js");
const locStart = js.locStart;
const locEnd = js.locEnd;
function getParsers(options) {
return options.plugins.reduce(
@ -17,7 +21,9 @@ function resolveParser(opts, parsers) {
// Custom parser API always works with JavaScript.
return {
parse: opts.parser,
astFormat: "estree"
astFormat: "estree",
locStart,
locEnd
};
}
@ -28,7 +34,9 @@ function resolveParser(opts, parsers) {
try {
return {
parse: eval("require")(path.resolve(process.cwd(), opts.parser)),
astFormat: "estree"
astFormat: "estree",
locStart,
locEnd
};
} catch (err) {
/* istanbul ignore next */
@ -58,7 +66,14 @@ function parse(text, opts) {
const parser = resolveParser(opts, parsers);
try {
return parser.parse(text, parsersForCustomParserApi, opts);
if (parser.preprocess) {
text = parser.preprocess(text, opts);
}
return {
text,
ast: parser.parse(text, parsersForCustomParserApi, opts)
};
} catch (error) {
const loc = error.loc;

View File

@ -51,8 +51,8 @@ export class SnapshotLogger {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class A // comment 1
// comment 2
extends B {}
// comment 2
extends B {}
class A extends B // comment1
// comment2
@ -66,8 +66,8 @@ class A extends B /* a */ {
class A /* a */ extends B {}
(class A // comment 1
// comment 2
extends B {});
// comment 2
extends B {});
(class A extends B // comment1
// comment2
@ -91,8 +91,8 @@ class X {
TEMPLATE =
// tab index is needed so we can focus, which is needed for keyboard events
'<div class="ag-large-text" tabindex="0">' +
'<div class="ag-large-textarea"></div>' +
"</div>";
'<div class="ag-large-textarea"></div>' +
"</div>";
}
export class SnapshotLogger {

View File

@ -50,11 +50,9 @@ export class VisTimelineComponent2
implements AfterViewInit, OnChanges, OnDestroy, AndSomethingReallyReallyLong {
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class MyContractSelectionWidget extends React.Component<
void,
MyContractSelectionWidgetPropsType,
void
> implements SomethingLarge {
class MyContractSelectionWidget
extends React.Component<void, MyContractSelectionWidgetPropsType, void>
implements SomethingLarge {
method() {}
}
@ -65,7 +63,8 @@ class DisplayObject extends utils.EventEmitter
implements interaction_InteractiveTarget {}
class DisplayObject extends utils.EventEmitter
implements interaction_InteractiveTarget,
implements
interaction_InteractiveTarget,
somethingElse_SomeOtherThing,
somethingElseAgain_RunningOutOfNames {}
@ -84,7 +83,8 @@ class Foo extends Immutable.Record({
export class VisTimelineComponent
implements AfterViewInit, OnChanges, OnDestroy {}
export class VisTimelineComponent2
implements AfterViewInit,
implements
AfterViewInit,
OnChanges,
OnDestroy,
AndSomethingReallyReallyLong {}
@ -161,6 +161,25 @@ class C {
`;
exports[`property.js 1`] = `
class A {
foobar =
// comment to break
1 +
// comment to break again
2;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class A {
foobar =
// comment to break
1 +
// comment to break again
2;
}
`;
exports[`ternary.js 1`] = `
if (1) (class {}) ? 1 : 2;
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -0,0 +1,7 @@
class A {
foobar =
// comment to break
1 +
// comment to break again
2;
}

View File

@ -173,3 +173,41 @@ class Point {
}
`;
exports[`with_comments.js 1`] = `
class A {
#foobar =
// comment to break
1 +
// comment to break again
2;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class A {
#foobar =
// comment to break
1 +
// comment to break again
2;
}
`;
exports[`with_comments.js 2`] = `
class A {
#foobar =
// comment to break
1 +
// comment to break again
2;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class A {
#foobar =
// comment to break
1 +
// comment to break again
2
}
`;

View File

@ -0,0 +1,7 @@
class A {
#foobar =
// comment to break
1 +
// comment to break again
2;
}

View File

@ -82,6 +82,33 @@ exports[`blank.js 1`] = `
`;
exports[`break-continue-statements.js 1`] = `
for (;;) {
break /* comment */;
continue /* comment */;
}
loop: for (;;) {
break /* comment */ loop;
break loop /* comment */;
continue /* comment */ loop;
continue loop /* comment */;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (;;) {
break; /* comment */
continue; /* comment */
}
loop: for (;;) {
break /* comment */ loop;
break loop /* comment */;
continue /* comment */ loop;
continue loop /* comment */;
}
`;
exports[`call_comment.js 1`] = `
render( // Warm any cache
<ChildUpdates renderAnchor={true} anchorClassOn={true} />,
@ -228,9 +255,41 @@ import("something" /* Hello */ + "else");
exports[`export.js 1`] = `
export //comment
{}
export /* comment */ {};
export {
foo // comment
}
export {
// comment
bar
}
export {
fooo, // comment
barr, // comment
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//comment
export {};
export //comment
{};
export /* comment */{};
export {
foo // comment
};
export {
// comment
bar
};
export {
fooo, // comment
barr // comment
};
`;
@ -439,20 +498,21 @@ true
if (1) {
// comment
false;
} else if (2)
// comment
true;
}
// comment
else if (2) true;
// multi
// ple
// lines
else if (3)
// multi
// ple
// lines
// existing comment
true;
// okay?
else if (4) {
// okay?
// empty with existing comment
} else {
// comment
}
// comment
else {
}
if (5)
@ -465,8 +525,8 @@ if (6) {
} else if (7)
// comment
true;
// comment
else {
// comment
true;
}
@ -478,9 +538,9 @@ if (8) {
// comment
// comment
true;
// comment
// comment
else {
// comment
// comment
true;
}
@ -496,8 +556,7 @@ else if (12)
else if (13)
/* comment */ /* comment */ // comment
true;
else {
/* comment */
/* comment */ else {
true;
}
@ -722,8 +781,9 @@ if (1) {
// Comments trigger invalid JavaScript in-between else if
if (1) {
} else {
// Comment
}
// Comment
else {
}
// The comment makes the line break in a weird way

View File

@ -0,0 +1,11 @@
for (;;) {
break /* comment */;
continue /* comment */;
}
loop: for (;;) {
break /* comment */ loop;
break loop /* comment */;
continue /* comment */ loop;
continue loop /* comment */;
}

View File

@ -1,2 +1,18 @@
export //comment
{}
export /* comment */ {};
export {
foo // comment
}
export {
// comment
bar
}
export {
fooo, // comment
barr, // comment
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,292 @@
.parent {
@at-root {
.child1 {
width: 100px;
}
.child2 {
width: 200px;
}
}
}
.parent {
@at-root {
.child1 {
width: 100px;
}
.child2 {
width: 200px;
}
}
}
.parent {
@at-root {
.child1 {
width: 100px;
}
.child2 {
width: 200px;
}
}
}
.parent {
@at-root {
.child1 {
width: 100px;
}
.child2 {
width: 200px;
}
}
}
.parent
{
@at-root
{
.child1
{
width
:
100px
;
}
.child2
{
width
:
200px
;
}
}
}
.parent
{
@at-root
{
.child1
{
width
:
100px
;
}
.child2
{
width
:
200px
;
}
}
}
.parent {
@at-root .child {
width: 100px;
}
}
.parent {
@at-root .child {
width: 100px;
}
}
.parent{
@at-root .child{
width: 100px;
}
}
.parent {
@at-root .child {
width: 100px;
}
}
.parent
{
@at-root
.child
{
width
:
100px
;
}
}
.parent
{
@at-root
.child
{
width
:
100px
;
}
}
.parent {
@at-root
input[
type
=
'radio'
]
{
color
:
red
;
}
}
@media print {
.page {
width: 8in;
@at-root (with: media) {
color: red;
}
}
}
@media print {
.page {
width: 8in;
@at-root (with: media) {
color: red;
}
}
}
@media print{
.page{
width: 8in;
@at-root (with:media){
color: red;
}
}
}
@media print {
.page {
width: 8in;
@at-root ( with : media ) {
color: red;
}
}
}
@media print {
.page {
width: 8in;
@at-root ( with : media ) {
color: red;
}
}
}
@media print {
.page {
width: 8in;
@at-root
(with: media) {
color: red;
}
}
}
@media print
{
.page
{
width
:
8in
;
@at-root
(
with
:
media
)
{
color
:
red
;
}
}
}
@media print
{
.page
{
width
:
8in
;
@at-root
(
with
:
media
)
{
color
:
red
;
}
}
}
@media print {
.page {
width: 8in;
@at-root (without: media) {
color: red;
}
}
}

Some files were not shown because too many files have changed in this diff Show More