Merge commit '326cdc53aa67dc3e8d68eb24fcc08b3e4a7b9bde'

Update to ktlint 0.23.1
https://github.com/shyiko/ktlint/tree/0.23.1

Test: None
diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..a848655
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,19 @@
+version: '{branch}.{build}'
+
+skip_tags: true
+
+environment:
+  matrix:
+    - JAVA_HOME: C:\Program Files\Java\jdk1.8.0
+
+install:
+  - cmd: echo %JAVA_HOME%
+  - cmd: echo %M2_HOME%
+
+test_script:
+  - mvn clean verify
+
+cache:
+  - C:\Users\appveyor\.m2
+
+build: off
diff --git a/.gitignore b/.gitignore
index 6e3b916..11693fe 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@
 .gradle
 build
 !ktlint/src/main/resources/config/.idea
+/.idea
+*.iml
diff --git a/.travis.yml b/.travis.yml
index 108bf07..606bfc1 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,12 @@
 language: java
 jdk:
-  - oraclejdk8
-script: ./mvnw clean verify
+- oraclejdk8
+script: if [[ $TRAVIS_REPO_SLUG == "shyiko/ktlint" && $TRAVIS_BRANCH == "master" &&
+  $TRAVIS_PULL_REQUEST == "false" ]]; then ./mvnw -Ddeploy=maven-central -DskipStaging=true -Dgpg.skip=true &&
+  ./mvnw -Ddeploy=github -Dgpg.skip=true -Dgithub.draft=true -Dgithub.description="https://github.com/shyiko/ktlint#access-to-the-latest-master-snapshot"; else ./mvnw clean verify;
+  fi
+env:
+  global:
+  - secure: ApIfhi3JFZSZXoy5jwSev6AAx85PM5ABl2+RvUokon/xv5ztxX+hTKYMQ068BpYW7glegC+qvk1RQSHZ2zzD95lFngu7saZUWk7g/fqg92lqz5KIicIbvJ8h1An/8vuCq+mg9Of9Io1oBFF0e4RuzoCG7DIks4oaEypOujnadSx+cks9sZpNwXNHjccc3XyTKieFr3WIa9rcrNmkTqojJToRUxKAt5+XU2GLDSgqj8a8EtE/n/1fM3voYOdj2aYFl+P1zPZ4kKliP68rkdpe6kCGCMcJFt3hXpqGs8X40T+8nClARK9kSi9KgQdz/Qbs3Gb5jh7JWc31G8gWFx2g2RQX0edKD0udygmOAAFNBPJIKrmHnWSe/oyPxBHnW2eYb7w1LvCVC4LtlErXrKcXiz0PXjMuBuK4N82RHIHzVeANiewRoiMvxFo8sfmTtCiu+Nx7+CiIMoxP5P3VS5GN++nctFIHvqIqvSQElFdk0rYrrFSEk9ct+NVvhw3UHwAHt1gbjq/dUayLrljGBPIAsHy1GOe0fX56dX8xHLJO2VM+eWAbNYiFyQxhFCI1LOv+PKiW8diQ+txP3EoXclBZbYDPQqZ/RlCKCcGwJNQqhMSPciYzjZrV7vLk4lzpuCM26IEBMr5Oyeu0zlMWQI7sI6V/BUCejPK2IkrFweEYHws=
+  - secure: ztSej57b5xzWgCVOBBFrw8dmjwIVkBPGZwwpIdB/OXcN4xLM3dxWQaq1ADxzOqNdaLb3GOTJ0VJpdAqcjlQjTaq8PZyLULaB44cxiCdi9huNpHwfdUBWsJqD4mg3CQzf7jmAuUtf/aaP32WC0r5kH/Crn1awwatdGwuOZyPfpK6H9dP4KPDytClPnR/GO0WRyxk1t0OeRM01NCY6E69xiKEqC1q+Y0bu5GeVs8uQiSJchf5O8B/jBP74Gwi8cV5hPvfK9onbqCpI/ecen5dR+Z3hGl71eO20yU0iH5jC2sksKaPsvYW6zLJb0ederzfR92h2mFA2HlPtYjKibmvgzTBaOXqUCnUCPpnszgCtJ2W4gJPzRaNlba0GqqUakdrwmhdojbvEMcoF0cRUlvLI5nO1WE1V3OIno5R4q5tY1DydO7nqF2RJ/yDVjKI4S7kyPnZdSSY9FgRM5xnWPsuVS59oBmVfBaRoa+qv9lEWMSTL0NBAYtqrmjKQWGBuSqHBBdEuiGIWsrC+Z2zVEmUe+4U3GNSfFnHgGr5YoJuCvIxS6qU2KUcLHimVMvprXNHUmCgr2J+PFaoVwHH1yLSyIiNy7fRiO0nsGssCApqW6Yy3fFLUbjOmzBrpog94lKs4b+5V/MV6nQsRcqz53poHOVbyZF2mkyxOJQyUbpUOAFA=
+  - secure: 0QUfiKxjMTE4FaCmbYT5Jm8Fuez5Pw/BjvNdPSHjpEp0Cz+/7KQeSYB30lG3LxhW48gAoXrJebAHZZvCKMDZBaFicqueeYHx+CO8XqNSUdXqvARHzL/QtIeP3+uLKAuNq/sfUqu+uPCcPl193aIQ21inW0+1X9VL2map7JRRm/7tyc8zEjWOyCKEwm5yTj7sdpY1Si03RPqVvGwxb+i7z4qbEFJeY3bfAtRgFXnwn5Z6OZ0T9NzdLFFYooKO0wfFbsJz4nBGZakVGLJa4qjVYf8F/PTqDDdFGufCblaaZnn5rFHg1ENGHlZJPKLd15doyeEsY3boCd2Fh25m7U97HT5Nw/1wpI6cdC718YVnCntsCB1YgtCYHLDMGogLKYFsmkessjg6h7dswaj4Q9YtlxZcjzfVTeHzEvCMQUDt83ugeU6SjetBZKHm0JmZ4Y7JIZdNbYz4Umhb7mn2qUHaAiGROTF6iOmEfS1V1Ha0utue+lxcm0GSr4gk1wnbeE6CinlRrqW9Xq17y0umWucCRQdOCem1fX13AORzzzOMGdUHvWzZw6UmJN6C+uqFcvMK/ze7IIlOiOcIkyfApMViv/oziYlarEH3DuL34hHrg2StUdMJwbb4cGIxlnoRxAHEASWkg0Ak69HMWgtVCZgw/HVozrxlb9ZpWrgAgCIQG/4=
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 41af543..6da54b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,157 @@
 All notable changes to this project will be documented in this file.  
 This project adheres to [Semantic Versioning](http://semver.org/).
 
+## [0.23.0] - 2018-05-02
+
+### Added
+- `comment-spacing` ([#198](https://github.com/shyiko/ktlint/pull/198)),  
+  `filename` ([#194](https://github.com/shyiko/ktlint/pull/194)) rules.
+- `parameter-list-wrapping` left parenthesis placement check ([#201](https://github.com/shyiko/ktlint/pull/201)).
+- `parameter-list-wrapping` auto-correction when `max_line_length` is exceeded ([#200](https://github.com/shyiko/ktlint/pull/200)).
+
+### Fixed
+- "Unused import" false positive (x.y.zNNN import inside x.y.z package) ([#204](https://github.com/shyiko/ktlint/issues/204)).
+
+### Changed
+- `kotlin-compiler` version to 1.2.41 (from 1.2.40).
+
+## [0.22.0] - 2018-04-22
+
+### Added
+- `--apply-to-idea-project` (as an alternative to (global) `--apply-to-idea`) ([#178](https://github.com/shyiko/ktlint/issues/178)).
+- Check to verify that annotations are placed before the modifiers ([#183](https://github.com/shyiko/ktlint/pull/183)).
+- Access to PsiFile location information ([#194](https://github.com/shyiko/ktlint/pull/194)).
+
+### Fixed
+- `--format` commenting out operators (`chain-wrapping` rule) ([#193](https://github.com/shyiko/ktlint/pull/193)).
+
+### Changed
+- `indent` rule (`continuation_indent_size` is now ignored) ([#171](https://github.com/shyiko/ktlint/issues/171)).  
+NOTE: if you have a custom `continuation_indent_size` (and `gcd(indent_size, continuation_indent_size) == 1`) ktlint
+won't check the indentation.
+- `--apply-to-idea` to inherit "Predefined style / Kotlin style guide" (Kotlin plugin 1.2.20+).
+- `kotlin-compiler` version to 1.2.40 (from 1.2.30).
+
+## [0.21.0] - 2018-03-29
+
+### Changed
+- `indent` rule to ignore `where <type constraint list>` clause ([#180](https://github.com/shyiko/ktlint/issues/180)).
+
+## [0.20.0] - 2018-03-20
+
+### Added
+- Ability to load 3rd party reporters from the command-line (e.g. `--reporter=<name>,artifact=<group_id>:<artifact_id>:<version>`) ([#176](https://github.com/shyiko/ktlint/issues/176)).
+- `--ruleset`/`--reporter` dependency tree validation.
+
+### Fixed
+- Handling of spaces in `--reporter=...,output=<path_to_a_file>` ([#177](https://github.com/shyiko/ktlint/issues/177)).
+- `+`, `-`, `*`, `/`, `%`, `&&`, `||` wrapping ([#168](https://github.com/shyiko/ktlint/issues/168)).
+
+### Changed
+- `comma-spacing` rule to be more strict ([#173](https://github.com/shyiko/ktlint/issues/173)).
+- `no-line-break-after-else` rule to allow multi-line `if/else` without curly braces. 
+
+## [0.19.0] - 2018-03-04
+
+### Changed
+- Lambda formatting: if lambda is assigned a label, there should be no space between the label and the opening curly brace ([#167](https://github.com/shyiko/ktlint/issues/167)).  
+
+## [0.18.0] - 2018-03-01
+
+### Added
+- Java 9 support ([#152](https://github.com/shyiko/ktlint/issues/152)).
+
+### Changed
+- `kotlin-compiler` version to 1.2.30 (from 1.2.20).
+
+## [0.17.1] - 2018-02-28
+
+### Fixed
+- `Internal Error (parameter-list-wrapping)` when `indent_size=unset` ([#165](https://github.com/shyiko/ktlint/issues/165)). 
+
+## [0.17.0] - 2018-02-28
+
+### Fixed
+- `+`/`-` wrapping inside `catch` block, after `else` and `if (..)` ([#160](https://github.com/shyiko/ktlint/issues/160)). 
+- Multi-line parameter declaration indentation ([#161](https://github.com/shyiko/ktlint/issues/161)).
+- Expected indentation reported by `indent` rule.
+
+### Changed
+- Error code returned by `ktlint --format/-F` when some of the errors cannot be auto-corrected (previously it was 0 instead of expected 1) ([#162](https://github.com/shyiko/ktlint/issues/162)). 
+
+## [0.16.1] - 2018-02-27
+
+### Fixed
+- Handling of negative number condition in `when` block ([#160](https://github.com/shyiko/ktlint/issues/160)). 
+
+## [0.16.0] - 2018-02-27
+
+### Added
+- `parameter-list-wrapping` rule ([#130](https://github.com/shyiko/ktlint/issues/130)).
+- `+`, `-`, `*`, `/`, `%`, `&&`, `||` wrapping check (now part of `chain-wrapping` rule).
+
+### Fixed
+- Unused `componentN` import (where N > 5) false positive ([#142](https://github.com/shyiko/ktlint/issues/142)).
+- max-line-length error suppression ([#158](https://github.com/shyiko/ktlint/issues/158)). 
+
+### Changed
+- `modifier-order` rule to match official [Kotlin Coding Conventions](https://kotlinlang.org/docs/reference/coding-conventions.html#modifiers) ([#146](https://github.com/shyiko/ktlint/issues/146))  
+(`override` modifier should be placed before `suspend`/`tailrec`, not after) 
+
+## [0.15.1] - 2018-02-14
+
+### Fixed
+- Race condition when multiple rules try to modify AST node that gets detached as a result of mutation ([#154](https://github.com/shyiko/ktlint/issues/154)).
+
+## [0.15.0] - 2018-01-18
+
+### Added
+- `no-line-break-after-else` rule ([#125](https://github.com/shyiko/ktlint/issues/125)).
+
+### Changed
+- `kotlin-compiler` version to 1.2.20 (from 1.2.0).
+
+## [0.14.0] - 2017-11-30
+
+### Changed
+- `continuation_indent_size` to 4 when `--android` profile is used ([android/kotlin-guides#37](https://github.com/android/kotlin-guides/issues/37)). 
+
+### Fixed
+- Maven integration ([#117](https://github.com/shyiko/ktlint/issues/117)).
+
+## [0.13.0] - 2017-11-28
+
+### Added
+- `no-line-break-before-assignment` ([#105](https://github.com/shyiko/ktlint/issues/105)),  
+  `chain-wrapping` ([#23](https://github.com/shyiko/ktlint/issues/23))
+(when wrapping chained calls `.`, `?.` and `?:` should be placed on the next line),  
+  `range-spacing` (no spaces around range (`..`) operator) rules.
+- `--print-ast` CLI option which can be used to dump AST of the file   
+(see [README / Creating a ruleset / AST](https://github.com/shyiko/ktlint#ast) for more details)
+- `--color` CLI option for colored output (where supported, e.g. --print-ast, default (plain) reporter, etc) 
+
+### Changed
+- `.editorconfig` property resolution.   
+An explicit `[*.{kt,kts}]` is not required anymore (ktlint looks for sections
+containing `*.kt` (or `*.kts`) and will fallback to `[*]` whenever property cannot be found elsewhere).   
+Also, a search for .editorconfig will no longer stop on first (closest) `.editorconfig` (unless it contains `root=true`). 
+- `max-line-length` rule to assume `max_line_length=100` when `ktlint --android ...` is used  
+(per [Android Kotlin Style Guide](https://android.github.io/kotlin-guides/style.html)).  
+- `kotlin-compiler` version to 1.2.0 (from 1.1.51).
+
+### Fixed
+- `no-empty-class-body` auto-correction at the end of file ([#109](https://github.com/shyiko/ktlint/issues/109)).
+- `max-line-length` rule when applied to KDoc ([#112](https://github.com/shyiko/ktlint/issues/112))  
+(previously KDoc was subject to `max-line-length` even though regular comments were not).
+- Spacing around `=` in @annotation|s (`op-spacing`).
+- Spacing around generic type parameters of functions (e.g. `fun <T>f(): T {}` -> `fun <T> f(): T {}`).
+- `no-consecutive-blank-lines` not triggering at the end of file (when exactly 2 blank lines are present) ([#108](https://github.com/shyiko/ktlint/issues/108)) 
+- `indent` `continuation_indent_size % indent_size != 0` case ([#76](https://github.com/shyiko/ktlint/issues/76))
+- `indent` rule skipping first parameter indentation check. 
+- `final-newline` rule in the context of kotlin script.
+- Git hook (previously files containing space character (among others) in their names were ignored)  
+- Exit code when file cannot be linted due to the invalid syntax or internal error.
+
 ## [0.12.1] - 2017-11-13
 
 ### Fixed
@@ -243,6 +394,20 @@
 
 ## 0.1.0 - 2016-07-27
 
+[0.23.0]: https://github.com/shyiko/ktlint/compare/0.22.0...0.23.0
+[0.22.0]: https://github.com/shyiko/ktlint/compare/0.21.0...0.22.0
+[0.21.0]: https://github.com/shyiko/ktlint/compare/0.20.0...0.21.0
+[0.20.0]: https://github.com/shyiko/ktlint/compare/0.19.0...0.20.0
+[0.19.0]: https://github.com/shyiko/ktlint/compare/0.18.0...0.19.0
+[0.18.0]: https://github.com/shyiko/ktlint/compare/0.17.1...0.18.0
+[0.17.1]: https://github.com/shyiko/ktlint/compare/0.17.0...0.17.1
+[0.17.0]: https://github.com/shyiko/ktlint/compare/0.16.1...0.17.0
+[0.16.1]: https://github.com/shyiko/ktlint/compare/0.16.0...0.16.1
+[0.16.0]: https://github.com/shyiko/ktlint/compare/0.15.1...0.16.0
+[0.15.1]: https://github.com/shyiko/ktlint/compare/0.15.0...0.15.1
+[0.15.0]: https://github.com/shyiko/ktlint/compare/0.14.0...0.15.0
+[0.14.0]: https://github.com/shyiko/ktlint/compare/0.13.0...0.14.0
+[0.13.0]: https://github.com/shyiko/ktlint/compare/0.12.1...0.13.0
 [0.12.1]: https://github.com/shyiko/ktlint/compare/0.12.0...0.12.1
 [0.12.0]: https://github.com/shyiko/ktlint/compare/0.11.1...0.12.0
 [0.11.1]: https://github.com/shyiko/ktlint/compare/0.11.0...0.11.1
diff --git a/README.md b/README.md
index b8bd23c..b2a0a96 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,8 @@
 
 <p align="center">
 <a href="https://travis-ci.org/shyiko/ktlint"><img src="https://travis-ci.org/shyiko/ktlint.svg?branch=master" alt="Build Status"></a>
-<a href="http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.shyiko%22%20AND%20a%3A%22ktlint%22"><img src="https://img.shields.io/maven-central/v/com.github.shyiko/ktlint.svg" alt="Maven Central"></a>
+<a href="https://ci.appveyor.com/project/shyiko/ktlint"><img src="https://ci.appveyor.com/api/projects/status/9dtlak3cj5rum48g?svg=true&passingText=passing" alt="Build Status"></a>
+<a href="https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.github.shyiko%22%20AND%20a%3A%22ktlint%22"><img src="https://img.shields.io/maven-central/v/com.github.shyiko/ktlint.svg" alt="Maven Central"></a>
 <a href="https://ktlint.github.io/"><img src="https://img.shields.io/badge/code%20style-%E2%9D%A4-FF4081.svg" alt="ktlint"></a>
 </p>
 
@@ -38,11 +39,16 @@
 - No trailing whitespaces;
 - No `Unit` returns (`fun fn {}` instead of `fun fn: Unit {}`);
 - No empty (`{}`) class bodies;
+- No spaces around range (`..`) operator;
+- No newline before (binary) `+` & `-`, `*`, `/`, `%`, `&&`, `||`; 
+- When wrapping chained calls `.`, `?.` and `?:` should be placed on the next line;
+- When a line is broken at an assignment (`=`) operator the break comes after the symbol;
+- When class/function signature doesn't fit on a single line, each parameter must be on a separate line;
 - Consistent string templates (`$v` instead of `${v}`, `${p.v}` instead of `${p.v.toString()}`);
 - Consistent order of modifiers;
-- Consistent spacing after keywords, commas; around colons, curly braces, infix operators, etc;
-- Newline at the end of each file  
-(unless `insert_final_newline` is set to false in .editorconfig (see [EditorConfig](#editorconfig) section for more)).
+- Consistent spacing after keywords, commas; around colons, curly braces, infix operators, comments, etc;
+- Newline at the end of each file (not enabled by default, but recommended)  
+(set `insert_final_newline=true` in .editorconfig to enable (see [EditorConfig](#editorconfig) section for more)).
 
 ## EditorConfig
 
@@ -52,9 +58,12 @@
 [*.{kt,kts}]
 # possible values: number (e.g. 2), "unset" (makes ktlint ignore indentation completely)  
 indent_size=4
-continuation_indent_size=8
-insert_final_newline=true
-# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off" 
+# possible values: number (e.g. 2), "unset"
+continuation_indent_size=4
+# true (recommended) / false
+insert_final_newline=unset
+# possible values: number (e.g. 120) (package name, imports & comments are ignored), "off"
+# it's automatically set to 100 on `ktlint --android ...` (per Android Kotlin Style Guide)
 max_line_length=off
 ```
 
@@ -63,14 +72,14 @@
 > Skip all the way to the "Integration" section if you don't plan to use `ktlint`'s command line interface.
 
 ```sh
-curl -sSLO https://github.com/shyiko/ktlint/releases/download/0.12.1/ktlint &&
+curl -sSLO https://github.com/shyiko/ktlint/releases/download/0.23.0/ktlint &&
   chmod a+x ktlint &&
   sudo mv ktlint /usr/local/bin/
 ```
 
 ... or just download `ktlint` from the [releases](https://github.com/shyiko/ktlint/releases) page  (`ktlint.asc` contains PGP signature which you can verify with `curl -sS https://keybase.io/shyiko/pgp_keys.asc | gpg --import && gpg --verify ktlint.asc`).  
 
-On macOS ([or Linux](http://linuxbrew.sh/)) you can also use [brew](http://brew.sh/) - `brew install shyiko/ktlint/ktlint`.
+On macOS ([or Linux](http://linuxbrew.sh/)) you can also use [brew](https://brew.sh/) - `brew install shyiko/ktlint/ktlint`.
 
 > If you don't have curl installed - replace `curl -sL` with `wget -qO-`.
 
@@ -84,7 +93,7 @@
 ```bash
 # check the style of all Kotlin files inside the current dir (recursively)
 # (hidden folders will be skipped)
-$ ktlint
+$ ktlint --color
   src/main/kotlin/Main.kt:10:10: Unused import
   
 # check only certain locations (prepend ! to negate the pattern) 
@@ -118,7 +127,7 @@
 <plugin>
     <groupId>org.apache.maven.plugins</groupId>
     <artifactId>maven-antrun-plugin</artifactId>
-    <version>1.7</version>
+    <version>1.8</version>
     <executions>
         <execution>
             <id>ktlint</id>
@@ -126,7 +135,7 @@
             <configuration>
             <target name="ktlint">
                 <java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
-                    classname="com.github.shyiko.ktlint.Main" classpathref="maven.plugin.classpath">
+                    classpathref="maven.plugin.classpath" classname="com.github.shyiko.ktlint.Main">
                     <arg value="src/**/*.kt"/>
                     <!-- to generate report in checkstyle format prepend following args: -->
                     <!-- 
@@ -144,7 +153,7 @@
             <configuration>
             <target name="ktlint">
                 <java taskname="ktlint" dir="${basedir}" fork="true" failonerror="true"
-                    classname="com.github.shyiko.ktlint.Main" classpathref="maven.plugin.classpath">
+                    classpathref="maven.plugin.classpath" classname="com.github.shyiko.ktlint.Main">
                     <arg value="-F"/>
                     <arg value="src/**/*.kt"/>
                 </java>
@@ -157,7 +166,7 @@
         <dependency>
             <groupId>com.github.shyiko</groupId>
             <artifactId>ktlint</artifactId>
-            <version>0.12.1</version>
+            <version>0.23.0</version>
         </dependency>
         <!-- additional 3rd party ruleset(s) can be specified here -->
     </dependencies>
@@ -170,13 +179,16 @@
 
 #### ... with [Gradle](https://gradle.org/)
 
+#### (without a plugin)
+
 > build.gradle
 
 ```groovy
-apply plugin: "java"
+// kotlin-gradle-plugin must be applied for configuration below to work
+// (see https://kotlinlang.org/docs/reference/using-gradle.html)
 
 repositories {
-    mavenCentral()
+    jcenter()
 }
 
 configurations {
@@ -184,16 +196,16 @@
 }
 
 dependencies {
-    ktlint "com.github.shyiko:ktlint:0.12.1"
+    ktlint "com.github.shyiko:ktlint:0.23.0"
     // additional 3rd party ruleset(s) can be specified here
-    // just add them to the classpath (ktlint 'groupId:artifactId:version') and 
+    // just add them to the classpath (e.g. ktlint 'groupId:artifactId:version') and 
     // ktlint will pick them up
 }
 
 task ktlint(type: JavaExec, group: "verification") {
     description = "Check Kotlin code style."
-    main = "com.github.shyiko.ktlint.Main"
     classpath = configurations.ktlint
+    main = "com.github.shyiko.ktlint.Main"
     args "src/**/*.kt"
     // to generate report in checkstyle format prepend following args:
     // "--reporter=plain", "--reporter=checkstyle,output=${buildDir}/ktlint.xml"
@@ -203,22 +215,25 @@
 
 task ktlintFormat(type: JavaExec, group: "formatting") {
     description = "Fix Kotlin code style deviations."
-    main = "com.github.shyiko.ktlint.Main"
     classpath = configurations.ktlint
+    main = "com.github.shyiko.ktlint.Main"
     args "-F", "src/**/*.kt"
 }
 ```
 
-> Note: For an Android project this config would typically go into your app/build.gradle.
-
 To check code style - `gradle ktlint` (it's also bound to `gradle check`).  
 To run formatter - `gradle ktlintFormat`.
 
-**Another option** is to use Gradle plugin (in order of appearance):
-- [jlleitschuh/ktlint-gradle](https://github.com/jlleitschuh/ktlint-gradle)
-- [jeremymailen/kotlinter-gradle](https://github.com/jeremymailen/kotlinter-gradle)
+See [Making your Gradle tasks incremental](https://proandroiddev.com/making-your-gradle-tasks-incremental-7f26e4ef09c3) by [Niklas Baudy](https://github.com/vanniktech) on how to make tasks above incremental. 
 
-Each plugin has some unique features (like incremental build support in case of [jeremymailen/kotlinter-gradle](https://github.com/jeremymailen/kotlinter-gradle)) so check them out.
+#### (with a plugin)
+
+Gradle plugins (in order of appearance):
+- [jlleitschuh/ktlint-gradle](https://github.com/jlleitschuh/ktlint-gradle)  
+The very first ktlint gradle plugin.
+
+- [jeremymailen/kotlinter-gradle](https://github.com/jeremymailen/kotlinter-gradle)  
+Gradle plugin featuring incremental build, `*.kts` support.
 
 You might also want to take a look at [diffplug/spotless](https://github.com/diffplug/spotless/tree/master/plugin-gradle#applying-ktlint-to-kotlin-files) which has a built-in support for ktlint. In addition to linting/formatting kotlin code it allows you to keep license headers, markdown documentation, etc. in check.
 
@@ -232,24 +247,29 @@
 > (inside project's root directory)  
 
 ```sh
-ktlint --apply-to-idea
+ktlint --apply-to-idea-project
 # or if you want to be compliant with Android Kotlin Style Guide
-ktlint --apply-to-idea --android 
+ktlint --apply-to-idea-project --android 
 ```
 
 ##### Option #2
 
-Go to `File -> Settings... -> Editor`
-- `General -> Auto Import`
+Go to <kbd>File</kbd> -> <kbd>Settings...</kbd> -> <kbd>Editor</kbd>
+- <kbd>General</kbd> -> <kbd>Auto Import</kbd>
   - check `Optimize imports on the fly (for current project)`.
-- `Code Style -> Kotlin`
-  - open `Imports` tab, select all `Use single name import` options and remove `import java.util.*` from `Packages to Use Import with '*'`.
-  - open `Blank Lines` tab, change `Keep Maximum Blank Lines` -> `In declarations` & `In code` to 1 and `Before '}'` to 0.
-  - (optional but recommended) open `Wrapping and Braces` tab, uncheck `Method declaration parameters -> Align when multiline`. 
-  - (optional but recommended) open `Tabs and Indents` tab, change `Continuation indent` to 4 (to be compliant with 
-  [Android Kotlin Style Guide](https://android.github.io/kotlin-guides/style.html) value should stay equal 8).
-- `Inspections` 
-  - change `Severity` level of `Unused import directive`, `Redundant semicolon` and (optional but recommended) `Unused symbol` to `ERROR`.
+- <kbd>Code Style</kbd> -> <kbd>Kotlin</kbd>
+  - <kbd>Set from...</kbd> -> <kbd>Predefined style</kbd> -> <kbd>Kotlin style guide</kbd> (Kotlin plugin 1.2.20+).
+  - open <kbd>Imports</kbd> tab
+    - select `Use single name import` (all of them);
+    - remove `import java.util.*` from `Packages to Use Import with '*'`.
+  - open <kbd>Blank Lines</kbd> tab
+    - change `Keep Maximum Blank Lines` / `In declarations` & `In code` to 1 and `Before '}'` to 0.
+  - (optional but recommended) open <kbd>Wrapping and Braces</kbd> tab
+    - uncheck `Method declaration parameters` / `Align when multiline`.     
+  - (optional but recommended) open <kbd>Tabs and Indents</kbd> tab
+    - change `Continuation indent` to the same value as `Indent` (4 by default).   
+- <kbd>Inspections</kbd> 
+  - change `Severity` level of `Unused import directive` and `Redundant semicolon` to `ERROR`.
 
 #### ... with [GNU Emacs](https://www.gnu.org/software/emacs/)
 
@@ -279,6 +299,39 @@
 A complete sample project (with tests and build files) is included in this repo under the [ktlint-ruleset-template](ktlint-ruleset-template) directory 
 (make sure to check [NoVarRuleTest](ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt) as it contains some useful information). 
 
+#### AST
+
+While writing/debugging [Rule](ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/Rule.kt)s it's often helpful to have an AST
+printed out to see the structure rules have to work with. ktlint >= 0.15.0 has `--print-ast` flag specifically for this purpose
+(usage: `ktlint --color --print-ast <file>`).  
+An example of the output is shown below. 
+
+```sh
+$ printf "fun main() {}" | ktlint --color --print-ast --stdin
+
+1: ~.psi.KtFile (~.psi.stubs.elements.KtFileElementType.kotlin.FILE)
+1:   ~.psi.KtPackageDirective (~.psi.stubs.elements.KtPlaceHolderStubElementType.PACKAGE_DIRECTIVE) ""
+1:   ~.psi.KtImportList (~.psi.stubs.elements.KtPlaceHolderStubElementType.IMPORT_LIST) ""
+1:   ~.psi.KtScript (~.psi.stubs.elements.KtScriptElementType.SCRIPT)
+1:     ~.psi.KtBlockExpression (~.KtNodeType.BLOCK)
+1:       ~.psi.KtNamedFunction (~.psi.stubs.elements.KtFunctionElementType.FUN)
+1:         ~.c.i.p.impl.source.tree.LeafPsiElement (~.lexer.KtKeywordToken.fun) "fun"
+1:         ~.c.i.p.impl.source.tree.PsiWhiteSpaceImpl (~.c.i.p.tree.IElementType.WHITE_SPACE) " "
+1:         ~.c.i.p.impl.source.tree.LeafPsiElement (~.lexer.KtToken.IDENTIFIER) "main"
+1:         ~.psi.KtParameterList 
+  (~.psi.stubs.elements.KtPlaceHolderStubElementType.VALUE_PARAMETER_LIST)
+1:           ~.c.i.p.impl.source.tree.LeafPsiElement (~.lexer.KtSingleValueToken.LPAR) "("
+1:           ~.c.i.p.impl.source.tree.LeafPsiElement (~.lexer.KtSingleValueToken.RPAR) ")"
+1:         ~.c.i.p.impl.source.tree.PsiWhiteSpaceImpl (~.c.i.p.tree.IElementType.WHITE_SPACE) " "
+1:         ~.psi.KtBlockExpression (~.KtNodeType.BLOCK)
+1:           ~.c.i.p.impl.source.tree.LeafPsiElement (~.lexer.KtSingleValueToken.LBRACE) "{"
+1:           ~.c.i.p.impl.source.tree.LeafPsiElement (~.lexer.KtSingleValueToken.RBRACE) "}"
+
+   format: <line_number:> <node.psi::class> (<node.elementType>) "<node.text>"
+   legend: ~ = org.jetbrains.kotlin, c.i.p = com.intellij.psi
+   
+```
+
 ## Creating a reporter
 
 Take a look at [ktlint-reporter-plain](ktlint-reporter-plain). 
@@ -288,7 +341,7 @@
 a custom [ReporterProvider](ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ReporterProvider.kt) using
 `META-INF/services/com.github.shyiko.ktlint.core.ReporterProvider`. Pack all of that into a JAR and you're done.
 
-To load a custom (3rd party) reporter use `ktlint --reporter=groupId:artifactId:version` / `ktlint --reporter=/path/to/custom-ktlint-reporter.jar`
+To load a custom (3rd party) reporter use `ktlint --reporter=name,artifact=groupId:artifactId:version` / `ktlint --reporter=name,artifact=/path/to/custom-ktlint-reporter.jar`
 (see `ktlint --help` for more).
  
 ## Badge
@@ -349,6 +402,39 @@
 ./mvnw # shows how to build, test, etc. project
 ```
 
+#### Access to the latest `master` snapshot
+
+Whenever a commit is added to the `master` branch `0.0.0-SNAPSHOT` is automatically uploaded to [Sonatype's snapshots repository](https://oss.sonatype.org/content/repositories/snapshots/com/github/shyiko/ktlint/).
+If you are eager to try upcoming changes (that might or might not be included in the next stable release) you can do 
+so by changing version of ktlint to `0.0.0-SNAPSHOT` + adding a repo: 
+
+##### Maven
+
+```xml
+...
+<repository>
+    <id>sonatype-snapshots</id>
+    <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+    <snapshots>
+        <enabled>true</enabled>
+    </snapshots>
+    <releases>
+        <enabled>false</enabled>
+    </releases>
+</repository>
+...
+```
+
+##### Gradle
+
+```groovy
+repositories {
+  maven {
+    url "https://oss.sonatype.org/content/repositories/snapshots"
+  }
+}
+```
+
 ## Legal
 
 This project is not affiliated with or endorsed by the Jetbrains.  
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..c1c9c6b
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,35 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm" version "1.2.40" apply false
+}
+
+ext.libraries = [
+  "kotlin_stdlib": "org.jetbrains.kotlin:kotlin-stdlib:1.2.40",
+  "kotlin_compiler_embeddable": "org.jetbrains.kotlin:kotlin-compiler-embeddable:1.2.40",
+  "klob": "com.github.shyiko.klob:klob:0.2.0",
+  "aether_api": "org.eclipse.aether:aether-api:1.1.0",
+  "aether_spi": "org.eclipse.aether:aether-spi:1.1.0",
+  "aether_util": "org.eclipse.aether:aether-util:1.1.0",
+  "aether_impl": "org.eclipse.aether:aether-impl:1.1.0",
+  "aether_connector_basic": "org.eclipse.aether:aether-connector-basic:1.1.0",
+  "aether_transport_file": "org.eclipse.aether:aether-transport-file:1.1.0",
+  "aether_transport_http": "org.eclipse.aether:aether-transport-http:1.1.0",
+  // Used to silence aether-transport-http
+  "slf4j_nop": "org.slf4j:slf4j-nop:1.6.2",
+  "maven_aether_provider": "org.apache.maven:maven-aether-provider:3.2.5",
+  "picocli": "info.picocli:picocli:2.3.0",
+  "kolor":  dependencies.create("com.andreapivetta.kolor:kolor:0.0.2") {
+    exclude group: "org.jetbrains.kotlin", module: "kotlin-stdlib-jre8"
+  },
+
+  // Testing libraries
+  "testng": "org.testng:testng:6.8.21",
+  "assertj_core": "org.assertj:assertj-core:1.7.1",
+  "jimfs": "com.google.jimfs:jimfs:1.1"
+]
+
+subprojects {
+  repositories {
+    jcenter()
+    mavenCentral()
+  }
+}
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..fc449c7
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sun Jan 28 12:56:37 PST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..8a0b282
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/ktlint-core/build.gradle b/ktlint-core/build.gradle
new file mode 100644
index 0000000..3d45e08
--- /dev/null
+++ b/ktlint-core/build.gradle
@@ -0,0 +1,11 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+}
+
+dependencies {
+  compile libraries.kotlin_stdlib
+  compile libraries.kotlin_compiler_embeddable
+
+  testCompile libraries.testng
+  testCompile libraries.assertj_core
+}
diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
index 13c7ea4..977b97d 100644
--- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
+++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt
@@ -1,10 +1,13 @@
 package com.github.shyiko.ktlint.core
 
+import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
+import org.jetbrains.kotlin.cli.common.messages.MessageCollector
 import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
 import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.com.intellij.mock.MockProject
 import org.jetbrains.kotlin.com.intellij.openapi.Disposable
+import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.DefaultLogger
 import org.jetbrains.kotlin.com.intellij.openapi.extensions.ExtensionPoint
 import org.jetbrains.kotlin.com.intellij.openapi.extensions.Extensions.getArea
 import org.jetbrains.kotlin.com.intellij.openapi.util.Key
@@ -28,18 +31,30 @@
 import sun.reflect.ReflectionFactory
 import java.util.ArrayList
 import java.util.HashSet
+import org.jetbrains.kotlin.com.intellij.openapi.diagnostic.Logger as DiagnosticLogger
 
 object KtLint {
 
     val EDITOR_CONFIG_USER_DATA_KEY = Key<EditorConfig>("EDITOR_CONFIG")
     val ANDROID_USER_DATA_KEY = Key<Boolean>("ANDROID")
+    val FILE_PATH_USER_DATA_KEY = Key<String>("FILE_PATH")
 
     private val psiFileFactory: PsiFileFactory
     private val nullSuppression = { _: Int, _: String -> false }
 
     init {
+        // do not print anything to the stderr when lexer is unable to match input
+        class LoggerFactory : DiagnosticLogger.Factory {
+            override fun getLoggerInstance(p: String): DiagnosticLogger = object : DefaultLogger(null) {
+                override fun warn(message: String?, t: Throwable?) {}
+                override fun error(message: String?, vararg details: String?) {}
+            }
+        }
+        DiagnosticLogger.setFactory(LoggerFactory::class.java)
+        val compilerConfiguration = CompilerConfiguration()
+        compilerConfiguration.put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, MessageCollector.NONE)
         val project = KotlinCoreEnvironment.createForProduction(Disposable {},
-            CompilerConfiguration(), EnvironmentConfigFiles.EMPTY).project
+            compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES).project
         // everything below (up to PsiFileFactory.getInstance(...)) is to get AST mutations (`ktlint -F ...`) working
         // otherwise it's not needed
         val pomModel: PomModel = object : UserDataHolderBase(), PomModel {
@@ -129,43 +144,100 @@
             throw ParseException(line, col, errorElement.errorDescription)
         }
         val rootNode = psiFile.node
-        rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android"))
+        rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android" - "file_path"))
         rootNode.putUserData(ANDROID_USER_DATA_KEY, userData["android"]?.toBoolean() ?: false)
+        rootNode.putUserData(FILE_PATH_USER_DATA_KEY, userData["file_path"])
         val isSuppressed = calculateSuppressedRegions(rootNode)
-        val r = flatten(ruleSets)
-        rootNode.visit { node ->
-            r.forEach { (id, rule) ->
-                if (!isSuppressed(node.startOffset, id)) {
-                    try {
-                        rule.visit(node, false) { offset, errorMessage, _ ->
-                            val (line, col) = positionByOffset(offset)
-                            cb(LintError(line, col, id, errorMessage))
+        visitor(rootNode, ruleSets).invoke { node, rule, fqRuleId ->
+            // fixme: enforcing suppression based on node.startOffset is wrong
+            // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
+            if (!isSuppressed(node.startOffset, fqRuleId) || node === rootNode) {
+                try {
+                    rule.visit(node, false) { offset, errorMessage, _ ->
+                        val (line, col) = positionByOffset(offset)
+                        cb(LintError(line, col, fqRuleId, errorMessage))
+                    }
+                } catch (e: Exception) {
+                    val (line, col) = positionByOffset(node.startOffset)
+                    throw RuleExecutionException(line, col, fqRuleId, e)
+                }
+            }
+        }
+    }
+
+    private fun visitor(
+        rootNode: ASTNode,
+        ruleSets: Iterable<RuleSet>,
+        concurrent: Boolean = true,
+        filter: (fqRuleId: String) -> Boolean = { true }
+    ): ((node: ASTNode, rule: Rule, fqRuleId: String) -> Unit) -> Unit {
+        val fqrsRestrictedToRoot = mutableListOf<Pair<String, Rule>>()
+        val fqrs = mutableListOf<Pair<String, Rule>>()
+        val fqrsExpectedToBeExecutedLastOnRoot = mutableListOf<Pair<String, Rule>>()
+        val fqrsExpectedToBeExecutedLast = mutableListOf<Pair<String, Rule>>()
+        for (ruleSet in ruleSets) {
+            val prefix = if (ruleSet.id === "standard") "" else "${ruleSet.id}:"
+            for (rule in ruleSet) {
+                val fqRuleId = "$prefix${rule.id}"
+                if (!filter(fqRuleId)) {
+                    continue
+                }
+                val fqr = fqRuleId to rule
+                when {
+                    rule is Rule.Modifier.Last -> fqrsExpectedToBeExecutedLast.add(fqr)
+                    rule is Rule.Modifier.RestrictToRootLast -> fqrsExpectedToBeExecutedLastOnRoot.add(fqr)
+                    rule is Rule.Modifier.RestrictToRoot -> fqrsRestrictedToRoot.add(fqr)
+                    else -> fqrs.add(fqr)
+                }
+            }
+        }
+        return { visit ->
+            for ((fqRuleId, rule) in fqrsRestrictedToRoot) {
+                visit(rootNode, rule, fqRuleId)
+            }
+            if (concurrent) {
+                rootNode.visit { node ->
+                    for ((fqRuleId, rule) in fqrs) {
+                        visit(node, rule, fqRuleId)
+                    }
+                }
+            } else {
+                for ((fqRuleId, rule) in fqrs) {
+                    rootNode.visit { node ->
+                        visit(node, rule, fqRuleId)
+                    }
+                }
+            }
+            for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLastOnRoot) {
+                visit(rootNode, rule, fqRuleId)
+            }
+            if (!fqrsExpectedToBeExecutedLast.isEmpty()) {
+                if (concurrent) {
+                    rootNode.visit { node ->
+                        for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
+                            visit(node, rule, fqRuleId)
                         }
-                    } catch (e: Exception) {
-                        val (line, col) = positionByOffset(node.startOffset)
-                        throw RuleExecutionException(line, col, id, e)
+                    }
+                } else {
+                    for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
+                        rootNode.visit { node ->
+                            visit(node, rule, fqRuleId)
+                        }
                     }
                 }
             }
         }
     }
 
-    private fun flatten(ruleSets: Iterable<RuleSet>) = ArrayList<Pair<String, Rule>>().apply {
-        ruleSets.forEach { ruleSet ->
-            val prefix = if (ruleSet.id === "standard") "" else "${ruleSet.id}:"
-            ruleSet.forEach { rule -> add("$prefix${rule.id}" to rule) }
-        }
-    }
-
     private fun calculateLineColByOffset(text: String): (offset: Int) -> Pair<Int, Int> {
-        var i = 0
+        var i = -1
         val e = text.length
         val arr = ArrayList<Int>()
         do {
-            arr.add(i)
-            i = text.indexOf('\n', i) + 1
-        } while (i != 0 && i != e)
-        arr.add(e)
+            arr.add(i + 1)
+            i = text.indexOf('\n', i + 1)
+        } while (i != -1)
+        arr.add(e + if (arr.last() == e) 1 else 0)
         val segmentTree = SegmentTree(arr.toTypedArray())
         return { offset ->
             val line = segmentTree.indexOf(offset)
@@ -199,8 +271,12 @@
     fun format(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError, corrected: Boolean) -> Unit): String =
         format(text, ruleSets, emptyMap<String, String>(), cb, script = false)
 
-    fun format(text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>,
-        cb: (e: LintError, corrected: Boolean) -> Unit): String = format(text, ruleSets, userData, cb, script = false)
+    fun format(
+        text: String,
+        ruleSets: Iterable<RuleSet>,
+        userData: Map<String, String>,
+        cb: (e: LintError, corrected: Boolean) -> Unit
+    ): String = format(text, ruleSets, userData, cb, script = false)
 
     /**
      * Fix style violations.
@@ -215,8 +291,12 @@
     fun formatScript(text: String, ruleSets: Iterable<RuleSet>, cb: (e: LintError, corrected: Boolean) -> Unit): String =
         format(text, ruleSets, emptyMap(), cb, script = true)
 
-    fun formatScript(text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>,
-        cb: (e: LintError, corrected: Boolean) -> Unit): String = format(text, ruleSets, userData, cb, script = true)
+    fun formatScript(
+        text: String,
+        ruleSets: Iterable<RuleSet>,
+        userData: Map<String, String>,
+        cb: (e: LintError, corrected: Boolean) -> Unit
+    ): String = format(text, ruleSets, userData, cb, script = true)
 
     private fun format(
         text: String,
@@ -238,55 +318,57 @@
             throw ParseException(line, col, errorElement.errorDescription)
         }
         val rootNode = psiFile.node
-        rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android"))
+        rootNode.putUserData(EDITOR_CONFIG_USER_DATA_KEY, EditorConfig.fromMap(userData - "android" - "file_path"))
         rootNode.putUserData(ANDROID_USER_DATA_KEY, userData["android"]?.toBoolean() ?: false)
+        rootNode.putUserData(FILE_PATH_USER_DATA_KEY, userData["file_path"])
         var isSuppressed = calculateSuppressedRegions(rootNode)
-        val r = flatten(ruleSets)
-        var autoCorrect = false
-        rootNode.visit { node ->
-            r.forEach { (id, rule) ->
-                if (!isSuppressed(node.startOffset, id)) {
+        var tripped = false
+        var mutated = false
+        visitor(rootNode, ruleSets, concurrent = false)
+            .invoke { node, rule, fqRuleId ->
+                // fixme: enforcing suppression based on node.startOffset is wrong
+                // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
+                if (!isSuppressed(node.startOffset, fqRuleId) || node === rootNode) {
                     try {
-                        rule.visit(node, false) { offset, errorMessage, canBeAutoCorrected ->
+                        rule.visit(node, true) { _, _, canBeAutoCorrected ->
+                            tripped = true
                             if (canBeAutoCorrected) {
-                                autoCorrect = true
-                            }
-                            val (line, col) = positionByOffset(offset)
-                            cb(LintError(line, col, id, errorMessage), canBeAutoCorrected)
-                        }
-                    } catch (e: Exception) {
-                        val (line, col) = positionByOffset(node.startOffset)
-                        throw RuleExecutionException(line, col, id, e)
-                    }
-                }
-            }
-        }
-        if (autoCorrect) {
-            rootNode.visit { node ->
-                r.forEach { (id, rule) ->
-                    if (!isSuppressed(node.startOffset, id)) {
-                        try {
-                            rule.visit(node, true) { _, _, canBeAutoCorrected ->
-                                if (canBeAutoCorrected && isSuppressed !== nullSuppression) {
+                                mutated = true
+                                if (isSuppressed !== nullSuppression) {
                                     isSuppressed = calculateSuppressedRegions(rootNode)
                                 }
                             }
-                        } catch (e: Exception) {
-                            // line/col cannot be reliably mapped as exception might originate from a node not present
-                            // in the original AST
-                            throw RuleExecutionException(0, 0, id, e)
                         }
+                    } catch (e: Exception) {
+                        // line/col cannot be reliably mapped as exception might originate from a node not present
+                        // in the original AST
+                        throw RuleExecutionException(0, 0, fqRuleId, e)
                     }
                 }
             }
-            return rootNode.text.replace("\n", determineLineSeparator(text))
+        if (tripped) {
+            visitor(rootNode, ruleSets).invoke { node, rule, fqRuleId ->
+                // fixme: enforcing suppression based on node.startOffset is wrong
+                // (not just because not all nodes are leaves but because rules are free to emit (and fix!) errors at any position)
+                if (!isSuppressed(node.startOffset, fqRuleId) || node === rootNode) {
+                    try {
+                        rule.visit(node, false) { offset, errorMessage, _ ->
+                            val (line, col) = positionByOffset(offset)
+                            cb(LintError(line, col, fqRuleId, errorMessage), false)
+                        }
+                    } catch (e: Exception) {
+                        val (line, col) = positionByOffset(node.startOffset)
+                        throw RuleExecutionException(line, col, fqRuleId, e)
+                    }
+                }
+            }
         }
-        return text
+        return if (mutated) rootNode.text.replace("\n", determineLineSeparator(text)) else text
     }
 
     private fun calculateLineBreakOffset(fileContent: String): (offset: Int) -> Int {
         val arr = ArrayList<Int>()
-        var i: Int = 0
+        var i = 0
         do {
             arr.add(i)
             i = fileContent.indexOf("\r\n", i + 1)
@@ -296,13 +378,8 @@
             SegmentTree(arr.toTypedArray()).let { return { offset -> it.indexOf(offset) } } else { _ -> 0 }
     }
 
-    private fun determineLineSeparator(fileContent: String): String {
-        val i = fileContent.lastIndexOf('\n')
-        if (i == -1) {
-            return if (fileContent.lastIndexOf('\r') == -1) System.getProperty("line.separator") else "\r"
-        }
-        return if (i != 0 && fileContent[i] == '\r') "\r\n" else "\n"
-    }
+    private fun determineLineSeparator(fileContent: String) =
+        if (fileContent.lastIndexOf('\r') != -1) "\r\n" else "\n"
 
     /**
      * @param range zero-based range of lines where lint errors should be suppressed
@@ -329,8 +406,8 @@
                             val commentText = text.removePrefix("/*").removeSuffix("*/").trim()
                             parseHintArgs(commentText, "ktlint-disable")?.apply {
                                 open.add(SuppressionHint(IntRange(node.startOffset, node.startOffset), HashSet(this)))
-                            } ?:
-                            parseHintArgs(commentText, "ktlint-enable")?.apply {
+                            }
+                            ?: parseHintArgs(commentText, "ktlint-enable")?.apply {
                                 // match open hint
                                 val disabledRules = HashSet(this)
                                 val openHintIndex = open.indexOfLast { it.disabledRules == disabledRules }
@@ -363,7 +440,7 @@
             private fun splitCommentBySpace(comment: String) =
                 comment.replace(Regex("\\s"), " ").replace(" {2,}", " ").split(" ")
 
-            private fun <T>List<T>.tail() = this.subList(1, this.size)
+            private fun <T> List<T>.tail() = this.subList(1, this.size)
         }
     }
 
diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ReporterProvider.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ReporterProvider.kt
index 1cfd3d8..6d7ef31 100644
--- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ReporterProvider.kt
+++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ReporterProvider.kt
@@ -4,7 +4,7 @@
 
 /**
  * `ktlint` uses [ServiceLoader](http://docs.oracle.com/javase/6/docs/api/java/util/ServiceLoader.html) to
- * discover all available `ReporterProvider`s on the classpath and so each `ReporterProvider` must be registered using
+ * discover all available [ReporterProvider]s on the classpath and so each [ReporterProvider] must be registered using
  * `META-INF/services/com.github.shyiko.ktlint.core.ReporterProvider`
  * (see `ktlint-reporter-plain/src/main/resources` for an example).
  */
diff --git a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/Rule.kt b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/Rule.kt
index 3390563..20eddfd 100644
--- a/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/Rule.kt
+++ b/ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/Rule.kt
@@ -1,12 +1,13 @@
 package com.github.shyiko.ktlint.core
 
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
 
 /**
  * A rule contract.
  *
  * Implementation **doesn't** have to be thread-safe or stateless
- * (provided RuleSetProvider creates a new instance on each `get()` call).
+ * (provided [RuleSetProvider] creates a new instance on each `get()` call).
  *
  * @param id must be unique within the ruleset
  *
@@ -25,6 +26,25 @@
      * @param autoCorrect indicates whether rule should attempt auto-correction
      * @param emit a way for rule to notify about a violation (lint error)
      */
-    abstract fun visit(node: ASTNode, autoCorrect: Boolean,
-        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit)
+    abstract fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    )
+
+    object Modifier {
+        /**
+         * Any rule implementing this interface will be given root ([FileASTNode]) node only
+         * (in other words, [visit] will be called on [FileASTNode] but not on [FileASTNode] children).
+         */
+        interface RestrictToRoot
+        /**
+         * Marker interface to indicate that rule must be executed after all other rules (order among multiple
+         * [RestrictToRootLast] rules is not defined and should be assumed to be random).
+         *
+         * Note that [RestrictToRootLast] implements [RestrictToRoot].
+         */
+        interface RestrictToRootLast : RestrictToRoot
+        interface Last
+    }
 }
diff --git a/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/ErrorSuppressionTest.kt b/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/ErrorSuppressionTest.kt
index c85dea7..7354236 100644
--- a/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/ErrorSuppressionTest.kt
+++ b/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/ErrorSuppressionTest.kt
@@ -13,8 +13,11 @@
     @Test
     fun testErrorSuppression() {
         class NoWildcardImportsRule : Rule("no-wildcard-imports") {
-            override fun visit(node: ASTNode, autoCorrect: Boolean,
-                    emit: (offset: Int, errorMessage: String, corrected: Boolean) -> Unit) {
+            override fun visit(
+                node: ASTNode,
+                autoCorrect: Boolean,
+                emit: (offset: Int, errorMessage: String, corrected: Boolean) -> Unit
+            ) {
                 if (node is LeafPsiElement && node.textMatches("*") &&
                         PsiTreeUtil.getNonStrictParentOfType(node, KtImportDirective::class.java) != null) {
                     emit(node.startOffset, "Wildcard import", false)
diff --git a/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/KtLintTest.kt b/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/KtLintTest.kt
new file mode 100644
index 0000000..b56bcb4
--- /dev/null
+++ b/ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/KtLintTest.kt
@@ -0,0 +1,36 @@
+package com.github.shyiko.ktlint.core
+
+import org.assertj.core.api.Assertions.assertThat
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+import org.testng.annotations.Test
+
+class KtLintTest {
+
+    @Test
+    fun testRuleExecutionOrder() {
+        open class R(private val bus: MutableList<String>, id: String) : Rule(id) {
+            private var done = false
+            override fun visit(
+                node: ASTNode,
+                autoCorrect: Boolean,
+                emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+            ) {
+                if (node.elementType == KtStubElementTypes.FILE) {
+                    bus.add("file:$id")
+                } else if (!done) {
+                    bus.add(id)
+                    done = true
+                }
+            }
+        }
+        val bus = mutableListOf<String>()
+        KtLint.lint("fun main() {}", listOf(RuleSet("standard",
+            object : R(bus, "d"), Rule.Modifier.RestrictToRootLast {},
+            R(bus, "b"),
+            object : R(bus, "a"), Rule.Modifier.RestrictToRoot {},
+            R(bus, "c")
+        ))) {}
+        assertThat(bus).isEqualTo(listOf("file:a", "file:b", "file:c", "b", "c", "file:d"))
+    }
+}
diff --git a/ktlint-reporter-checkstyle/build.gradle b/ktlint-reporter-checkstyle/build.gradle
new file mode 100644
index 0000000..27b42f8
--- /dev/null
+++ b/ktlint-reporter-checkstyle/build.gradle
@@ -0,0 +1,11 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+}
+
+dependencies {
+  compile project(":ktlint-core")
+  compile libraries.kotlin_stdlib
+
+  testCompile libraries.testng
+  testCompile libraries.assertj_core
+}
diff --git a/ktlint-reporter-checkstyle/src/test/kotlin/com/github/shyiko/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt b/ktlint-reporter-checkstyle/src/test/kotlin/com/github/shyiko/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt
index 9243d63..a6c09a8 100644
--- a/ktlint-reporter-checkstyle/src/test/kotlin/com/github/shyiko/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt
+++ b/ktlint-reporter-checkstyle/src/test/kotlin/com/github/shyiko/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt
@@ -37,7 +37,7 @@
 		<error line="2" column="20" severity="error" message="A single thin straight line" source="rule-2" />
 	</file>
 </checkstyle>
-""".trimStart()
+""".trimStart().replace("\n", System.lineSeparator())
         )
     }
 }
diff --git a/ktlint-reporter-json/build.gradle b/ktlint-reporter-json/build.gradle
new file mode 100644
index 0000000..951dcf6
--- /dev/null
+++ b/ktlint-reporter-json/build.gradle
@@ -0,0 +1,11 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+}
+
+dependencies {
+  compile project(':ktlint-core')
+  compile libraries.kotlin_stdlib
+
+  testCompile libraries.testng
+  testCompile libraries.assertj_core
+}
diff --git a/ktlint-reporter-json/src/main/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporter.kt b/ktlint-reporter-json/src/main/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporter.kt
index 3d9b209..cb2ff30 100644
--- a/ktlint-reporter-json/src/main/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporter.kt
+++ b/ktlint-reporter-json/src/main/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporter.kt
@@ -18,32 +18,24 @@
 
     override fun afterAll() {
         out.println("[")
-        for ((i, entry) in acc.entries.sortedBy { it.key }.withIndex()) {
+        val indexLast = acc.size - 1
+        for ((index, entry) in acc.entries.sortedBy { it.key }.withIndex()) {
             val (file, errList) = entry
-            out.println(
-                """
-                |	{
-                |		"file": "${file.escapeJsonValue()}",
-                |		"errors": [
-                """.trimMargin()
-            )
-            out.println(
-                errList.map { (line, col, ruleId, detail) ->
-                    """
-                    |			{
-                    |				"line": $line,
-                    |				"column": $col,
-                    |				"message": "${detail.escapeJsonValue()}",
-                    |				"rule": "$ruleId"
-                    |			}
-                    """.trimMargin()
-                }.joinToString(",\n")
-            )
-            out.println(
-                """
-                |		]
-                |	}${if (i < acc.size - 1) "," else ""}
-                """.trimMargin())
+            out.println("""	{""")
+            out.println("""		"file": "${file.escapeJsonValue()}",""")
+            out.println("""		"errors": [""")
+            val errIndexLast = errList.size - 1
+            for ((errIndex, err) in errList.withIndex()) {
+                val (line, col, ruleId, detail) = err
+                out.println("""			{""")
+                out.println("""				"line": $line,""")
+                out.println("""				"column": $col,""")
+                out.println("""				"message": "${detail.escapeJsonValue()}",""")
+                out.println("""				"rule": "$ruleId"""")
+                out.println("""			}${if (errIndex != errIndexLast) "," else ""}""")
+            }
+            out.println("""		]""")
+            out.println("""	}${if (index != indexLast) "," else ""}""")
         }
         out.println("]")
     }
diff --git a/ktlint-reporter-json/src/test/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporterTest.kt b/ktlint-reporter-json/src/test/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporterTest.kt
index d3512e2..02ce58c 100644
--- a/ktlint-reporter-json/src/test/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporterTest.kt
+++ b/ktlint-reporter-json/src/test/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporterTest.kt
@@ -57,7 +57,7 @@
 		]
 	}
 ]
-""".trimStart()
+""".trimStart().replace("\n", System.lineSeparator())
         )
     }
 }
diff --git a/ktlint-reporter-plain/build.gradle b/ktlint-reporter-plain/build.gradle
new file mode 100644
index 0000000..e512d04
--- /dev/null
+++ b/ktlint-reporter-plain/build.gradle
@@ -0,0 +1,12 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+}
+
+dependencies {
+  compile project(':ktlint-core')
+  compile libraries.kotlin_stdlib
+  compile libraries.kolor
+
+  testCompile libraries.testng
+  testCompile libraries.assertj_core
+}
diff --git a/ktlint-reporter-plain/pom.xml b/ktlint-reporter-plain/pom.xml
index 3d79e4d..8287fef 100644
--- a/ktlint-reporter-plain/pom.xml
+++ b/ktlint-reporter-plain/pom.xml
@@ -25,6 +25,17 @@
             <scope>provided</scope>
         </dependency>
         <dependency>
+            <groupId>com.andreapivetta.kolor</groupId>
+            <artifactId>kolor</artifactId>
+            <version>${kolor.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.jetbrains.kotlin</groupId>
+                    <artifactId>kotlin-stdlib-jre8</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
             <groupId>org.testng</groupId>
             <artifactId>testng</artifactId>
             <version>${testng.version}</version>
diff --git a/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporter.kt b/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporter.kt
index 0586cce..7222950 100644
--- a/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporter.kt
+++ b/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporter.kt
@@ -1,12 +1,21 @@
 package com.github.shyiko.ktlint.reporter.plain
 
+import com.andreapivetta.kolor.Color
+import com.andreapivetta.kolor.Kolor
 import com.github.shyiko.ktlint.core.LintError
 import com.github.shyiko.ktlint.core.Reporter
+import java.io.File
 import java.io.PrintStream
 import java.util.ArrayList
 import java.util.concurrent.ConcurrentHashMap
 
-class PlainReporter(val out: PrintStream, val verbose: Boolean = false, val groupByFile: Boolean = false) : Reporter {
+class PlainReporter(
+    val out: PrintStream,
+    val verbose: Boolean = false,
+    val groupByFile: Boolean = false,
+    val color: Boolean = false,
+    val pad: Boolean = false
+) : Reporter {
 
     private val acc = ConcurrentHashMap<String, MutableList<LintError>>()
 
@@ -15,8 +24,9 @@
             if (groupByFile) {
                 acc.getOrPut(file) { ArrayList<LintError>() }.add(err)
             } else {
-                out.println("$file:${err.line}:${err.col}: " +
-                    "${err.detail}${if (verbose) " (${err.ruleId})" else ""}")
+                out.println("${colorFileName(file)}${":".gray()}${err.line}${
+                    ":${"${err.col}:".let { if (pad) String.format("%-4s", it) else it}}".gray()
+                } ${err.detail}${if (verbose) " (${err.ruleId})".gray() else ""}")
             }
         }
     }
@@ -24,11 +34,20 @@
     override fun after(file: String) {
         if (groupByFile) {
             val errList = acc[file] ?: return
-            out.println(file)
+            out.println(colorFileName(file))
             for ((line, col, ruleId, detail) in errList) {
-                out.println("  $line:$col " +
-                    "$detail${if (verbose) " ($ruleId)" else ""}")
+                out.println("  $line${
+                    ":${if (pad) String.format("%-3s", col) else "$col"}".gray()
+                } $detail${if (verbose) " ($ruleId)".gray() else ""}")
             }
         }
     }
+
+    private fun colorFileName(fileName: String): String {
+        val name = fileName.substringAfterLast(File.separator)
+        return fileName.substring(0, fileName.length - name.length).gray() + name
+    }
+
+    private fun String.gray() =
+        if (color) Kolor.foreground(this, Color.DARK_GRAY) else this
 }
diff --git a/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterProvider.kt b/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterProvider.kt
index 9b25444..f4bb01f 100644
--- a/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterProvider.kt
+++ b/ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterProvider.kt
@@ -5,8 +5,17 @@
 import java.io.PrintStream
 
 class PlainReporterProvider : ReporterProvider {
+
     override val id: String = "plain"
-    override fun get(out: PrintStream, opt: Map<String, String>): Reporter = PlainReporter(out,
-        verbose = opt["verbose"]?.emptyOrTrue() ?: false, groupByFile = opt["group_by_file"]?.emptyOrTrue() ?: false)
+
+    override fun get(out: PrintStream, opt: Map<String, String>): Reporter =
+        PlainReporter(
+            out,
+            verbose = opt["verbose"]?.emptyOrTrue() ?: false,
+            groupByFile = opt["group_by_file"]?.emptyOrTrue() ?: false,
+            color = opt["color"]?.emptyOrTrue() ?: false,
+            pad = opt["pad"]?.emptyOrTrue() ?: false
+        )
+
     private fun String.emptyOrTrue() = this == "" || this == "true"
 }
diff --git a/ktlint-reporter-plain/src/test/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterTest.kt b/ktlint-reporter-plain/src/test/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterTest.kt
index 61c896b..10f5d7f 100644
--- a/ktlint-reporter-plain/src/test/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterTest.kt
+++ b/ktlint-reporter-plain/src/test/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterTest.kt
@@ -29,7 +29,7 @@
 /one-fixed-and-one-not.kt:1:1: <"&'>
 /two-not-fixed.kt:1:10: I thought I would again
 /two-not-fixed.kt:2:20: A single thin straight line
-""".trimStart()
+""".trimStart().replace("\n", System.lineSeparator())
         )
     }
 
@@ -59,7 +59,7 @@
 /two-not-fixed.kt
   1:10 I thought I would again
   2:20 A single thin straight line
-""".trimStart()
+""".trimStart().replace("\n", System.lineSeparator())
         )
     }
 }
diff --git a/ktlint-ruleset-standard/build.gradle b/ktlint-ruleset-standard/build.gradle
new file mode 100644
index 0000000..8d2ee51
--- /dev/null
+++ b/ktlint-ruleset-standard/build.gradle
@@ -0,0 +1,12 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+}
+
+dependencies {
+  compile project(':ktlint-core')
+  compile project(':ktlint-test')
+  compile libraries.kotlin_stdlib
+
+  testCompile libraries.testng
+  testCompile libraries.assertj_core
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt
new file mode 100644
index 0000000..3c24344
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt
@@ -0,0 +1,127 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.KtNodeTypes
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiComment
+import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
+import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.lexer.KtTokens.ANDAND
+import org.jetbrains.kotlin.lexer.KtTokens.DIV
+import org.jetbrains.kotlin.lexer.KtTokens.DOT
+import org.jetbrains.kotlin.lexer.KtTokens.ELVIS
+import org.jetbrains.kotlin.lexer.KtTokens.MINUS
+import org.jetbrains.kotlin.lexer.KtTokens.MUL
+import org.jetbrains.kotlin.lexer.KtTokens.OROR
+import org.jetbrains.kotlin.lexer.KtTokens.PERC
+import org.jetbrains.kotlin.lexer.KtTokens.PLUS
+import org.jetbrains.kotlin.lexer.KtTokens.SAFE_ACCESS
+import org.jetbrains.kotlin.psi.psiUtil.nextLeaf
+import org.jetbrains.kotlin.psi.psiUtil.prevLeaf
+
+class ChainWrappingRule : Rule("chain-wrapping") {
+
+    private val sameLineTokens = TokenSet.create(MUL, DIV, PERC, ANDAND, OROR)
+    private val prefixTokens = TokenSet.create(PLUS, MINUS)
+    private val nextLineTokens = TokenSet.create(DOT, SAFE_ACCESS, ELVIS)
+    private val noSpaceAroundTokens = TokenSet.create(DOT, SAFE_ACCESS)
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        /*
+           org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement (DOT) | "."
+           org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl (WHITE_SPACE) | "\n        "
+           org.jetbrains.kotlin.psi.KtCallExpression (CALL_EXPRESSION)
+         */
+        val elementType = node.elementType
+        if (nextLineTokens.contains(elementType)) {
+            if (node.psi.isPartOf(PsiComment::class)) {
+                return
+            }
+            val nextLeaf = node.psi.nextLeafIgnoringWhitespaceAndComments()?.prevLeaf(true)
+            if (nextLeaf is PsiWhiteSpaceImpl && nextLeaf.textContains('\n')) {
+                emit(node.startOffset, "Line must not end with \"${node.text}\"", true)
+                if (autoCorrect) {
+                    // rewriting
+                    // <prevLeaf><node="."><nextLeaf="\n"> to
+                    // <prevLeaf><delete space if any><nextLeaf="\n"><node="."><space if needed>
+                    // (or)
+                    // <prevLeaf><node="."><spaceBeforeComment><comment><nextLeaf="\n"> to
+                    // <prevLeaf><delete space if any><spaceBeforeComment><comment><nextLeaf="\n"><node="."><space if needed>
+                    val prevLeaf = node.psi.prevLeaf(true)
+                    if (prevLeaf is PsiWhiteSpaceImpl) {
+                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+                    }
+                    if (!noSpaceAroundTokens.contains(elementType)) {
+                        nextLeaf.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+                    }
+                    node.treeParent.removeChild(node)
+                    nextLeaf.rawInsertAfterMe(node.psi as LeafPsiElement)
+                }
+            }
+        } else if (sameLineTokens.contains(elementType) || prefixTokens.contains(elementType)) {
+            if (node.psi.isPartOf(PsiComment::class)) {
+                return
+            }
+            val prevLeaf = node.psi.prevLeaf(true)
+            if (
+                prevLeaf is PsiWhiteSpaceImpl &&
+                prevLeaf.textContains('\n') &&
+                // fn(*typedArray<...>()) case
+                (elementType != MUL || !prevLeaf.isPartOfSpread()) &&
+                // unary +/-
+                (!prefixTokens.contains(elementType) || !node.isInPrefixPosition()) &&
+                // LeafPsiElement->KtOperationReferenceExpression->KtPrefixExpression->KtWhenConditionWithExpression
+                !node.isPartOfWhenCondition()
+            ) {
+                emit(node.startOffset, "Line must not begin with \"${node.text}\"", true)
+                if (autoCorrect) {
+                    // rewriting
+                    // <insertionPoint><prevLeaf="\n"><node="&&"><nextLeaf=" "> to
+                    // <insertionPoint><prevLeaf=" "><node="&&"><nextLeaf="\n"><delete node="&&"><delete nextLeaf=" ">
+                    // (or)
+                    // <insertionPoint><spaceBeforeComment><comment><prevLeaf="\n"><node="&&"><nextLeaf=" "> to
+                    // <insertionPoint><space if needed><node="&&"><spaceBeforeComment><comment><prevLeaf="\n"><delete node="&&"><delete nextLeaf=" ">
+                    val nextLeaf = node.psi.nextLeaf(true)
+                    if (nextLeaf is PsiWhiteSpaceImpl) {
+                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
+                    }
+                    val insertionPoint = prevLeaf.prevLeafIgnoringWhitespaceAndComments() as LeafPsiElement
+                    node.treeParent.removeChild(node)
+                    insertionPoint.rawInsertAfterMe(node.psi as LeafPsiElement)
+                    if (!noSpaceAroundTokens.contains(elementType)) {
+                        insertionPoint.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+                    }
+                }
+            }
+        }
+    }
+
+    private fun PsiElement.isPartOfSpread() =
+        prevLeafIgnoringWhitespaceAndComments()?.let { leaf ->
+            val type = leaf.node.elementType
+            type == KtTokens.LPAR ||
+            type == KtTokens.COMMA ||
+            type == KtTokens.LBRACE ||
+            type == KtTokens.ELSE_KEYWORD ||
+            KtTokens.OPERATIONS.contains(type)
+        } == true
+
+    private fun ASTNode.isInPrefixPosition() =
+        treeParent?.treeParent?.elementType == KtNodeTypes.PREFIX_EXPRESSION
+
+    private fun ASTNode.isPartOfWhenCondition() =
+        treeParent?.treeParent?.treeParent?.elementType == KtNodeTypes.WHEN_CONDITION_EXPRESSION
+
+    private fun PsiElement.nextLeafIgnoringWhitespaceAndComments() =
+        this.nextLeaf { it.node.elementType != KtTokens.WHITE_SPACE && !it.isPartOf(PsiComment::class) }
+
+    private fun PsiElement.prevLeafIgnoringWhitespaceAndComments() =
+        this.prevLeaf { it.node.elementType != KtTokens.WHITE_SPACE && !it.isPartOf(PsiComment::class) }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt
new file mode 100644
index 0000000..68ba62b
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt
@@ -0,0 +1,35 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiComment
+import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+
+class CommentSpacingRule : Rule("comment-spacing") {
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        if (node is PsiComment && node is LeafPsiElement && node.getText().startsWith("//")) {
+            val prevLeaf = PsiTreeUtil.prevLeaf(node)
+            if (prevLeaf !is PsiWhiteSpace && prevLeaf is LeafPsiElement) {
+                emit(node.startOffset, "Missing space before //", true)
+                if (autoCorrect) {
+                    node.rawInsertBeforeMe(PsiWhiteSpaceImpl(" "))
+                }
+            }
+            val text = node.getText()
+            if (text.length != 2 && !text.startsWith("// ")) {
+                emit(node.startOffset, "Missing space after //", true)
+                if (autoCorrect) {
+                    node.rawReplaceWithText("// " + text.removePrefix("//"))
+                }
+            }
+        }
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt
new file mode 100644
index 0000000..64a5648
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt
@@ -0,0 +1,40 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.KtLint
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
+
+// http://editorconfig.org/
+internal data class EditorConfig(
+    val indentSize: Int,
+    val continuationIndentSize: Int,
+    val maxLineLength: Int,
+    val insertFinalNewline: Boolean?
+) {
+
+    companion object {
+
+        private const val DEFAULT_INDENT = 4
+
+        // https://android.github.io/kotlin-guides/style.html#line-wrapping
+        private const val ANDROID_MAX_LINE_LENGTH = 100
+
+        fun from(node: FileASTNode): EditorConfig {
+            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
+            val indentSizeRaw = editorConfig.get("indent_size")
+            val indentSize = when {
+                indentSizeRaw?.toLowerCase() == "unset" -> -1
+                else -> indentSizeRaw?.toIntOrNull() ?: DEFAULT_INDENT
+            }
+            val continuationIndentSizeRaw = editorConfig.get("continuation_indent_size")
+            val continuationIndentSize = when {
+                continuationIndentSizeRaw?.toLowerCase() == "unset" -> -1
+                else -> continuationIndentSizeRaw?.toIntOrNull() ?: indentSize
+            }
+            val android = node.getUserData(KtLint.ANDROID_USER_DATA_KEY)!!
+            val maxLineLength = editorConfig.get("max_line_length")?.toIntOrNull()
+                ?: if (android) ANDROID_MAX_LINE_LENGTH else -1
+            val insertFinalNewline = editorConfig.get("insert_final_newline")?.toBoolean()
+            return EditorConfig(indentSize, continuationIndentSize, maxLineLength, insertFinalNewline)
+        }
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt
new file mode 100644
index 0000000..8c7169c
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt
@@ -0,0 +1,63 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.KtLint
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+import java.nio.file.Paths
+
+/**
+ * If there is only one top level class/object/typealias in a given file, then its name should match the file's name.
+ */
+class FilenameRule : Rule("filename"), Rule.Modifier.RestrictToRoot {
+
+    private val ignoreSet = setOf<IElementType>(
+        KtStubElementTypes.FILE_ANNOTATION_LIST,
+        KtStubElementTypes.PACKAGE_DIRECTIVE,
+        KtStubElementTypes.IMPORT_LIST,
+        KtTokens.WHITE_SPACE,
+        KtTokens.EOL_COMMENT,
+        KtTokens.BLOCK_COMMENT,
+        KtTokens.DOC_COMMENT,
+        KtTokens.SHEBANG_COMMENT
+    )
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        val filePath = node.getUserData(KtLint.FILE_PATH_USER_DATA_KEY)
+        if (filePath?.endsWith(".kt") != true) {
+            // ignore all non ".kt" files (including ".kts")
+            return
+        }
+        var type: String? = null
+        var className: String? = null
+        for (el in node.getChildren(null)) {
+            if (el.elementType == KtStubElementTypes.CLASS ||
+                el.elementType == KtStubElementTypes.OBJECT_DECLARATION ||
+                el.elementType == KtStubElementTypes.TYPEALIAS) {
+                if (className != null) {
+                    // more than one class/object/typealias present
+                    return
+                }
+                val id = el.findChildByType(KtTokens.IDENTIFIER)
+                type = id?.psi?.getPrevSiblingIgnoringWhitespaceAndComments(false)?.text
+                className = id?.text
+            } else if (!ignoreSet.contains(el.elementType)) {
+                // https://github.com/android/android-ktx/blob/master/src/main/java/androidx/core/graphics/Path.kt case
+                return
+            }
+        }
+        if (className != null) {
+            val name = Paths.get(filePath).fileName.toString().substringBefore(".")
+            if (name != "package" && name != className) {
+                emit(0, "$type $className should be declared in a file named $className.kt", false)
+            }
+        }
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt
index 67057e6..93e8029 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt
@@ -1,13 +1,13 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
-import com.github.shyiko.ktlint.core.KtLint
 import com.github.shyiko.ktlint.core.Rule
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
 import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
 
-class FinalNewlineRule : Rule("final-newline") {
+class FinalNewlineRule : Rule("final-newline"), Rule.Modifier.RestrictToRoot {
 
     override fun visit(
         node: ASTNode,
@@ -15,9 +15,9 @@
         emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
     ) {
         if (node.elementType == KtStubElementTypes.FILE) {
-            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
-            val insertFinalNewline = editorConfig.get("insert_final_newline")?.toBoolean() ?: return
-            val lastNode = node.lastChildNode
+            val ec = EditorConfig.from(node as FileASTNode)
+            val insertFinalNewline = ec.insertFinalNewline ?: return
+            val lastNode = lastChildNodeOf(node)
             if (insertFinalNewline) {
                 if (lastNode !is PsiWhiteSpace || !lastNode.textContains('\n')) {
                     // (PsiTreeUtil.getDeepestLast(lastNode.psi).node ?: lastNode).startOffset
@@ -36,4 +36,7 @@
             }
         }
     }
+
+    private tailrec fun lastChildNodeOf(node: ASTNode): ASTNode? =
+        if (node.lastChildNode == null) node else lastChildNodeOf(node.lastChildNode)
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt
index 87b833a..d270735 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt
@@ -1,68 +1,74 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
-import com.github.shyiko.ktlint.core.KtLint
 import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.KtNodeTypes
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
-import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
 import org.jetbrains.kotlin.com.intellij.psi.PsiComment
 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
-import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
-import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
-import org.jetbrains.kotlin.psi.KtParameter
 import org.jetbrains.kotlin.psi.KtParameterList
-import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
-import org.jetbrains.kotlin.psi.psiUtil.startOffset
+import org.jetbrains.kotlin.psi.KtTypeConstraintList
 import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
 
 class IndentationRule : Rule("indent") {
 
-    companion object {
-        // indentation size recommended by JetBrains
-        private const val DEFAULT_INDENT = 4
-    }
+    private var indentSize = -1
 
-    private var indent = DEFAULT_INDENT
-
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node.elementType == KtStubElementTypes.FILE) {
-            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
-            val indentSize = editorConfig.get("indent_size")
-            indent = indentSize?.toIntOrNull() ?: if (indentSize?.toLowerCase() == "unset") -1 else indent
+            val ec = EditorConfig.from(node as FileASTNode)
+            indentSize = gcd(maxOf(ec.indentSize, 1), maxOf(ec.continuationIndentSize, 1))
             return
         }
-        if (indent <= 0) {
+        if (indentSize <= 1) {
             return
         }
-        if (node is PsiWhiteSpace && !node.isPartOf(PsiComment::class)) {
+        if (node is PsiWhiteSpace) {
             val lines = node.getText().split("\n")
-            if (lines.size > 1) {
+            if (lines.size > 1 && !node.isPartOf(PsiComment::class) && !node.isPartOf(KtTypeConstraintList::class)) {
                 var offset = node.startOffset + lines.first().length + 1
-                val firstParameterColumn = lazy {
-                    val firstParameter = PsiTreeUtil.findChildOfType(
-                        node.getNonStrictParentOfType(KtParameterList::class.java),
-                        KtParameter::class.java
-                    )
-                    firstParameter?.run {
-                        DiagnosticUtils.getLineAndColumnInPsiFile(node.containingFile,
-                            TextRange(startOffset, startOffset)).column
-                    } ?: 0
-                }
-                lines.tail().forEach { line ->
-                    if (line.length % indent != 0) {
-                        if (node.isPartOf(KtParameterList::class) && firstParameterColumn.value != 0) {
-                            if (firstParameterColumn.value - 1 != line.length) {
-                                emit(offset, "Unexpected indentation (${line.length}) (" +
-                                    "parameters should be either vertically aligned or indented by the multiple of $indent" +
-                                ")", false)
-                            }
-                        } else {
-                            emit(offset, "Unexpected indentation (${line.length}) (it should be multiple of $indent)", false)
+                val previousIndentSize = node.previousIndentSize()
+                lines.tail().forEach { indent ->
+                    if (indent.isNotEmpty() && (indent.length - previousIndentSize) % indentSize != 0) {
+                        if (!node.isPartOf(KtParameterList::class)) { // parameter list wrapping enforced by ParameterListWrappingRule
+                            emit(
+                                offset,
+                                "Unexpected indentation (${indent.length}) (it should be ${previousIndentSize + indentSize})",
+                                false
+                            )
                         }
                     }
-                    offset += line.length + 1
+                    offset += indent.length + 1
                 }
             }
         }
     }
+
+    private fun gcd(a: Int, b: Int): Int = when {
+        a > b -> gcd(a - b, b)
+        a < b -> gcd(a, b - a)
+        else -> a
+    }
+
+    // todo: calculating indent based on the previous line value is wrong (see IndentationRule.testLint)
+    private fun ASTNode.previousIndentSize(): Int {
+        var node = this.treeParent?.psi
+        while (node != null) {
+            val nextNode = node.nextSibling?.node?.elementType
+            if (node is PsiWhiteSpace &&
+                nextNode != KtStubElementTypes.TYPE_REFERENCE &&
+                nextNode != KtStubElementTypes.SUPER_TYPE_LIST &&
+                nextNode != KtNodeTypes.CONSTRUCTOR_DELEGATION_CALL &&
+                node.textContains('\n') &&
+                node.nextLeaf()?.isPartOf(PsiComment::class) != true) {
+                return node.text.length - node.text.lastIndexOf('\n') - 1
+            }
+            node = node.prevSibling ?: node.parent
+        }
+        return 0
+    }
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt
index 58d784e..aa7cca1 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt
@@ -1,16 +1,21 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
-import com.github.shyiko.ktlint.core.KtLint
 import com.github.shyiko.ktlint.core.Rule
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
 import org.jetbrains.kotlin.com.intellij.psi.PsiComment
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
 import org.jetbrains.kotlin.psi.KtImportDirective
 import org.jetbrains.kotlin.psi.KtPackageDirective
 import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments
 import org.jetbrains.kotlin.psi.psiUtil.startOffset
 import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
 
-class MaxLineLengthRule : Rule("max-line-length") {
+class MaxLineLengthRule : Rule("max-line-length"), Rule.Modifier.Last {
+
+    private var maxLineLength: Int = -1
+    private var rangeTree = RangeTree()
 
     override fun visit(
         node: ASTNode,
@@ -18,31 +23,134 @@
         emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
     ) {
         if (node.elementType == KtStubElementTypes.FILE) {
-            val editorConfig = node.getUserData(KtLint.EDITOR_CONFIG_USER_DATA_KEY)!!
-            val maxLineLength = editorConfig.get("max_line_length")?.toIntOrNull() ?: 0
+            val ec = EditorConfig.from(node as FileASTNode)
+            maxLineLength = ec.maxLineLength
             if (maxLineLength <= 0) {
                 return
             }
+            val errorOffset = arrayListOf<Int>()
             val text = node.text
             val lines = text.split("\n")
             var offset = 0
             for (line in lines) {
                 if (line.length > maxLineLength) {
                     val el = node.psi.findElementAt(offset + line.length - 1)!!
-                    if (!el.isPartOf(PsiComment::class)) {
-                        if (!el.isPartOf(KtPackageDirective::class) && !el.isPartOf(KtImportDirective::class)) {
-                            emit(offset, "Exceeded max line length ($maxLineLength)", false)
-                        }
-                    } else {
-                        // if comment is the only thing on the line - fine, otherwise emit an error
-                        val prevLeaf = el.getPrevSiblingIgnoringWhitespaceAndComments(false)
-                        if (prevLeaf != null && prevLeaf.startOffset >= offset) {
-                            emit(offset, "Exceeded max line length ($maxLineLength)", false)
+                    if (!el.isPartOf(KDoc::class)) {
+                        if (!el.isPartOf(PsiComment::class)) {
+                            if (!el.isPartOf(KtPackageDirective::class) && !el.isPartOf(KtImportDirective::class)) {
+                                // fixme:
+                                // normally we would emit here but due to API limitations we need to hold off until
+                                // node spanning the same offset is 'visit'ed
+                                // (for ktlint-disable directive to have effect (when applied))
+                                // this will be rectified in the upcoming release(s)
+                                errorOffset.add(offset)
+                            }
+                        } else {
+                            // if comment is the only thing on the line - fine, otherwise emit an error
+                            val prevLeaf = el.getPrevSiblingIgnoringWhitespaceAndComments(false)
+                            if (prevLeaf != null && prevLeaf.startOffset >= offset) {
+                                // fixme:
+                                // normally we would emit here but due to API limitations we need to hold off until
+                                // node spanning the same offset is 'visit'ed
+                                // (for ktlint-disable directive to have effect (when applied))
+                                // this will be rectified in the upcoming release(s)
+                                errorOffset.add(offset)
+                            }
                         }
                     }
                 }
                 offset += line.length + 1
             }
+            rangeTree = RangeTree(errorOffset)
+        } else if (!rangeTree.isEmpty() && node.psi is LeafPsiElement) {
+            rangeTree
+                .query(node.startOffset, node.startOffset + node.textLength)
+                .forEach { offset ->
+                    emit(offset, "Exceeded max line length ($maxLineLength)", false)
+                }
+        }
+    }
+}
+
+class RangeTree(seq: List<Int> = emptyList()) {
+
+    private var emptyArrayView = ArrayView(0, 0)
+    private var arr: IntArray = seq.toIntArray()
+
+    init {
+        if (arr.isNotEmpty()) {
+            arr.reduce { p, n -> require(p <= n) { "Input must be sorted" }; n }
+        }
+    }
+
+    // runtime: O(log(n)+k), where k is number of matching points
+    // space: O(1)
+    fun query(vmin: Int, vmax: Int): ArrayView {
+        var r = arr.size - 1
+        if (r == -1 || vmax < arr[0] || arr[r] < vmin) {
+            return emptyArrayView
+        }
+        // binary search for min(arr[l] >= vmin)
+        var l = 0
+        while (l < r) {
+            val m = (r + l) / 2
+            if (vmax < arr[m]) {
+                r = m - 1
+            } else if (arr[m] < vmin) {
+                l = m + 1
+            } else {
+                // arr[l] ?<=? vmin <= arr[m] <= vmax ?<=? arr[r]
+                if (vmin <= arr[l]) break else l++ // optimization
+                r = m
+            }
+        }
+        if (l > r || arr[l] < vmin) {
+            return emptyArrayView
+        }
+        // find max(k) such as arr[k] < vmax
+        var k = l
+        while (k < arr.size) {
+            if (arr[k] >= vmax) {
+                break
+            }
+            k++
+        }
+        return ArrayView(l, k)
+    }
+
+    fun isEmpty() = arr.isEmpty()
+
+    inner class ArrayView(private var l: Int, private val r: Int) {
+
+        val size: Int = r - l
+
+        fun get(i: Int): Int {
+            if (i < 0 || i >= size) {
+                throw IndexOutOfBoundsException()
+            }
+            return arr[l + i]
+        }
+
+        inline fun forEach(cb: (v: Int) -> Unit) {
+            var i = 0
+            while (i < size) {
+                cb(get(i++))
+            }
+        }
+
+        override fun toString(): String {
+            if (l == r) {
+                return "[]"
+            }
+            val sb = StringBuilder("[")
+            var i = l
+            while (i < r) {
+                sb.append(arr[i]).append(", ")
+                i++
+            }
+            sb.replace(sb.length - 2, sb.length, "")
+            sb.append("]")
+            return sb.toString()
         }
     }
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt
index 4815c06..3c9c9e5 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt
@@ -2,14 +2,15 @@
 
 import com.github.shyiko.ktlint.core.Rule
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
-import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
 import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet
 import org.jetbrains.kotlin.lexer.KtTokens.ABSTRACT_KEYWORD
+import org.jetbrains.kotlin.lexer.KtTokens.ACTUAL_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.ANNOTATION_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.COMPANION_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.CONST_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.DATA_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.ENUM_KEYWORD
+import org.jetbrains.kotlin.lexer.KtTokens.EXPECT_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.EXTERNAL_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.FINAL_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.INFIX_KEYWORD
@@ -26,25 +27,34 @@
 import org.jetbrains.kotlin.lexer.KtTokens.SEALED_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.SUSPEND_KEYWORD
 import org.jetbrains.kotlin.lexer.KtTokens.TAILREC_KEYWORD
+import org.jetbrains.kotlin.lexer.KtTokens.VARARG_KEYWORD
+import org.jetbrains.kotlin.psi.KtAnnotationEntry
 import org.jetbrains.kotlin.psi.KtDeclarationModifierList
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes.ANNOTATION_ENTRY
 import java.util.Arrays
 
 class ModifierOrderRule : Rule("modifier-order") {
 
-    // subset of KtTokens.MODIFIER_KEYWORDS_ARRAY
+    // subset of KtTokens.MODIFIER_KEYWORDS_ARRAY (+ annotations entries)
     private val order = arrayOf(
+        ANNOTATION_ENTRY,
         PUBLIC_KEYWORD, PROTECTED_KEYWORD, PRIVATE_KEYWORD, INTERNAL_KEYWORD,
-        FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD,
-        SUSPEND_KEYWORD, TAILREC_KEYWORD,
+        EXPECT_KEYWORD, ACTUAL_KEYWORD,
+        FINAL_KEYWORD, OPEN_KEYWORD, ABSTRACT_KEYWORD, SEALED_KEYWORD, CONST_KEYWORD,
+        EXTERNAL_KEYWORD,
         OVERRIDE_KEYWORD,
-        CONST_KEYWORD, LATEINIT_KEYWORD,
-        INNER_KEYWORD, EXTERNAL_KEYWORD,
-        ENUM_KEYWORD, ANNOTATION_KEYWORD, SEALED_KEYWORD, DATA_KEYWORD,
+        LATEINIT_KEYWORD,
+        TAILREC_KEYWORD,
+        VARARG_KEYWORD,
+        SUSPEND_KEYWORD,
+        INNER_KEYWORD,
+        ENUM_KEYWORD, ANNOTATION_KEYWORD,
         COMPANION_KEYWORD,
         INLINE_KEYWORD,
-        // NOINLINE_KEYWORD, CROSSINLINE_KEYWORD, OUT_KEYWORD, IN_KEYWORD, VARARG_KEYWORD, REIFIED_KEYWORD
         INFIX_KEYWORD,
-        OPERATOR_KEYWORD
+        OPERATOR_KEYWORD,
+        DATA_KEYWORD
+        // NOINLINE_KEYWORD, CROSSINLINE_KEYWORD, OUT_KEYWORD, IN_KEYWORD, REIFIED_KEYWORD
         // HEADER_KEYWORD, IMPL_KEYWORD
     )
     private val tokenSet = TokenSet.create(*order)
@@ -58,16 +68,26 @@
             val modifierArr = node.getChildren(tokenSet)
             val sorted = modifierArr.copyOf().apply { sortWith(compareBy { order.indexOf(it.elementType) }) }
             if (!Arrays.equals(modifierArr, sorted)) {
+                // Since annotations can be fairly lengthy and/or span multiple lines we are
+                // squashing them into a single placeholder text to guarantee a single line output
                 emit(node.startOffset, "Incorrect modifier order (should be \"${
-                    sorted.map { it.text }.joinToString(" ")
+                    squashAnnotations(sorted).joinToString(" ")
                 }\")", true)
                 if (autoCorrect) {
                     modifierArr.forEachIndexed { i, n ->
-                        // fixme: find a better way (node type is now potentially out of sync)
-                        (n.psi as LeafPsiElement).replaceWithText(sorted[i].text)
+                        node.replaceChild(n, sorted[i].clone() as ASTNode)
                     }
                 }
             }
         }
     }
+
+    private fun squashAnnotations(sorted: Array<ASTNode>): List<String> {
+        val nonAnnotationModifiers = sorted.filter { it.psi !is KtAnnotationEntry }
+        return if (nonAnnotationModifiers.size != sorted.size) {
+            listOf("@Annotation...") + nonAnnotationModifiers.map { it.text }
+        } else {
+            nonAnnotationModifiers.map { it.text }
+        }
+    }
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt
index ce64489..cd7649e 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt
@@ -9,17 +9,20 @@
 
 class NoBlankLineBeforeRbraceRule : Rule("no-blank-line-before-rbrace") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is PsiWhiteSpace &&
             node.textContains('\n') &&
             PsiTreeUtil.nextLeaf(node, true)?.node?.elementType == KtTokens.RBRACE) {
             val split = node.getText().split("\n")
             if (split.size > 2) {
                 emit(node.startOffset + split[0].length + split[1].length + 1,
-                    "Needless blank line(s)", true)
+                    "Unexpected blank line(s) before \"}\"", true)
                 if (autoCorrect) {
-                    (node as LeafPsiElement).replaceWithText("${split.first()}\n${split.last()}")
+                    (node as LeafPsiElement).rawReplaceWithText("${split.first()}\n${split.last()}")
                 }
             }
         }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt
index 1e6142f..ae7c582 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt
@@ -4,17 +4,22 @@
 import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
 
 class NoConsecutiveBlankLinesRule : Rule("no-consecutive-blank-lines") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is PsiWhiteSpace) {
             val split = node.getText().split("\n")
-            if (split.size > 3) {
+            if (split.size > 3 || split.size == 3 && PsiTreeUtil.nextLeaf(node) == null /* eof */) {
                 emit(node.startOffset + split[0].length + split[1].length + 2, "Needless blank line(s)", true)
                 if (autoCorrect) {
-                    (node as LeafPsiElement).replaceWithText("${split.first()}\n\n${split.last()}")
+                    (node as LeafPsiElement)
+                        .rawReplaceWithText("${split.first()}\n${if (split.size > 3) "\n" else ""}${split.last()}")
                 }
             }
         }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt
index 59a57a3..5e41220 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt
@@ -25,7 +25,7 @@
             if (autoCorrect) {
                 val prevNode = node.psi.prevSibling.node
                 val nextNode = PsiTreeUtil.nextLeaf(node.psi, true)?.node
-                if (prevNode.elementType == KtTokens.WHITE_SPACE && nextNode?.elementType == KtTokens.WHITE_SPACE) {
+                if (prevNode.elementType == KtTokens.WHITE_SPACE && (nextNode == null || nextNode.elementType == KtTokens.WHITE_SPACE)) {
                     // remove space between declaration and block
                     prevNode.treeParent.removeChild(prevNode)
                 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt
new file mode 100644
index 0000000..d28ba5e
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt
@@ -0,0 +1,28 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.lexer.KtTokens
+
+class NoLineBreakAfterElseRule : Rule("no-line-break-after-else") {
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        if (node is PsiWhiteSpace &&
+            node.textContains('\n')) {
+            if (PsiTreeUtil.prevLeaf(node, true)?.node?.elementType == KtTokens.ELSE_KEYWORD &&
+                PsiTreeUtil.nextLeaf(node, true)?.node?.elementType.let { it == KtTokens.IF_KEYWORD || it == KtTokens.LBRACE }) {
+                emit(node.startOffset + 1, "Unexpected line break after \"else\"", true)
+                if (autoCorrect) {
+                    (node as LeafPsiElement).rawReplaceWithText(" ")
+                }
+            }
+        }
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt
new file mode 100644
index 0000000..effbd18
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt
@@ -0,0 +1,23 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.lexer.KtTokens
+
+class NoLineBreakBeforeAssignmentRule : Rule("no-line-break-before-assignment") {
+
+    override fun visit(node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+        if (node.elementType == KtTokens.EQ) {
+            val prevElement = node.treePrev?.psi
+            if (prevElement is PsiWhiteSpace && prevElement.text.contains("\n")) {
+                emit(node.startOffset, "Line break before assignment is not allowed", true)
+                if (autoCorrect) {
+                    (node.treeNext?.psi as LeafPsiElement).rawReplaceWithText(prevElement.text)
+                    (prevElement as LeafPsiElement).rawReplaceWithText(" ")
+                }
+            }
+        }
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt
index 36cf26f..15aa1a2 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt
@@ -33,7 +33,8 @@
                 val psi = node.psi
                 if (psi is PsiComment) { comments.add(psi) }
             }
-            return comments.foldIndexed(mutableMapOf<Offset, CommentRelativeLocation>()) { i, acc, comment ->
+            return comments.foldIndexed(mutableMapOf()) { i, acc, comment ->
+                // todo: get rid of DiagnosticUtils (IndexOutOfBoundsException)
                 val pos = DiagnosticUtils.getLineAndColumnInPsiFile(fileNode.psi as PsiFile,
                     TextRange(comment.startOffset, comment.startOffset))
                 acc.put(comment.startOffset, CommentRelativeLocation(
@@ -46,12 +47,14 @@
             }
         }
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node.elementType == KtStubElementTypes.FILE) {
             fileNode = node
-        } else
-        if (node is PsiWhiteSpace && !node.textContains('\n') && node.getTextLength() > 1) {
+        } else if (node is PsiWhiteSpace && !node.textContains('\n') && node.getTextLength() > 1) {
             val nextLeaf = PsiTreeUtil.nextLeaf(node, true)
             if (nextLeaf is PsiComment) {
                 val positionMap = commentMap
@@ -71,13 +74,8 @@
             }
             emit(node.startOffset + 1, "Unnecessary space(s)", true)
             if (autoCorrect) {
-                (node as LeafPsiElement).replaceWithText(" ")
+                (node as LeafPsiElement).rawReplaceWithText(" ")
             }
         }
     }
-
-    private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
-        cb(this)
-        this.getChildren(null).forEach { it.visit(cb) }
-    }
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt
index fce5ce3..770e409 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt
@@ -10,8 +10,11 @@
 
 class NoSemicolonsRule : Rule("no-semi") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is LeafPsiElement && node.textMatches(";") && !node.isPartOfString() &&
                 !node.isPartOf(KtEnumEntry::class)) {
             val nextLeaf = PsiTreeUtil.nextLeaf(node, true)
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt
index fa3e492..a7e03c8 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt
@@ -8,33 +8,41 @@
 
 class NoTrailingSpacesRule : Rule("no-trailing-spaces") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is PsiWhiteSpace) {
             val lines = node.getText().split("\n")
             if (lines.size > 1) {
-                checkForTrailingSpaces(lines.head(), node.startOffset, emit)
-                if (autoCorrect) {
-                    (node as LeafPsiElement).replaceWithText("\n".repeat(lines.size - 1) + lines.last())
+                val violated = checkForTrailingSpaces(lines.head(), node.startOffset, emit)
+                if (violated && autoCorrect) {
+                    (node as LeafPsiElement).rawReplaceWithText("\n".repeat(lines.size - 1) + lines.last())
                 }
-            } else
-            if (PsiTreeUtil.nextLeaf(node) == null /* eof */) {
-                checkForTrailingSpaces(lines, node.startOffset, emit)
-                if (autoCorrect) {
-                    (node as LeafPsiElement).replaceWithText("\n".repeat(lines.size - 1))
+            } else if (PsiTreeUtil.nextLeaf(node) == null /* eof */) {
+                val violated = checkForTrailingSpaces(lines, node.startOffset, emit)
+                if (violated && autoCorrect) {
+                    (node as LeafPsiElement).rawReplaceWithText("\n".repeat(lines.size - 1))
                 }
             }
         }
     }
 
-    private fun checkForTrailingSpaces(lines: List<String>, offset: Int,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    private fun checkForTrailingSpaces(
+        lines: List<String>,
+        offset: Int,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ): Boolean {
+        var violated = false
         var violationOffset = offset
-        return lines.forEach { line ->
+        lines.forEach { line ->
             if (!line.isEmpty()) {
                 emit(violationOffset, "Trailing space(s)", true)
+                violated = true
             }
             violationOffset += line.length + 1
         }
+        return violated
     }
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt
index 205448a..c911cbb 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt
@@ -13,10 +13,10 @@
         autoCorrect: Boolean,
         emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
     ) {
-        if (node.elementType == KtStubElementTypes.TYPE_REFERENCE
-            && node.treeParent.elementType == KtStubElementTypes.FUNCTION
-            && node.text.contentEquals("Unit")
-            && PsiTreeUtil.nextVisibleLeaf(node.psi)?.node?.elementType == KtTokens.LBRACE) {
+        if (node.elementType == KtStubElementTypes.TYPE_REFERENCE &&
+            node.treeParent.elementType == KtStubElementTypes.FUNCTION &&
+            node.text.contentEquals("Unit") &&
+            PsiTreeUtil.nextVisibleLeaf(node.psi)?.node?.elementType == KtTokens.LBRACE) {
             emit(node.startOffset, "Unnecessary \"Unit\" return type", true)
             if (autoCorrect) {
                 var prevNode = node
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt
index cbb8287..b46f04d 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt
@@ -12,6 +12,8 @@
 
 class NoUnusedImportsRule : Rule("no-unused-imports") {
 
+    private val componentNRegex = Regex("^component\\d+$")
+
     private val operatorSet = setOf(
         // unary
         "unaryPlus", "unaryMinus", "not",
@@ -34,46 +36,45 @@
         // iteration (https://github.com/shyiko/ktlint/issues/40)
         "iterator",
         // by (https://github.com/shyiko/ktlint/issues/54)
-        "getValue", "setValue",
-        // destructuring assignment
-        "component1", "component2", "component3", "component4", "component5"
+        "getValue", "setValue"
     )
-    private val ref = mutableSetOf("*")
+    private val ref = mutableSetOf<String>()
     private var packageName = ""
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node.elementType == KtStubElementTypes.FILE) {
+            ref.clear() // rule can potentially be executed more than once (when formatting)
+            ref.add("*")
             node.visit { vnode ->
                 val psi = vnode.psi
                 val type = vnode.elementType
                 if (type == KDocTokens.MARKDOWN_LINK && psi is KDocLink) {
                     val linkText = psi.getLinkText().replace("`", "")
                     ref.add(linkText.split('.').first())
-                } else
-                if ((type == KtNodeTypes.REFERENCE_EXPRESSION || type == KtNodeTypes.OPERATION_REFERENCE) &&
+                } else if ((type == KtNodeTypes.REFERENCE_EXPRESSION || type == KtNodeTypes.OPERATION_REFERENCE) &&
                     !psi.isPartOf(KtImportDirective::class)) {
                     ref.add(vnode.text.trim('`'))
                 }
             }
-        } else
-        if (node.elementType == KtStubElementTypes.PACKAGE_DIRECTIVE) {
+        } else if (node.elementType == KtStubElementTypes.PACKAGE_DIRECTIVE) {
             val packageDirective = node.psi as KtPackageDirective
             packageName = packageDirective.qualifiedName
-        } else
-        if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
+        } else if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
             val importDirective = node.psi as KtImportDirective
             val name = importDirective.importPath?.importedName?.asString()
             val importPath = importDirective.importPath?.pathStr!!
             if (importDirective.aliasName == null &&
-                importPath.startsWith(packageName) &&
+                (packageName.isEmpty() || importPath.startsWith("$packageName.")) &&
                 importPath.substring(packageName.length + 1).indexOf('.') == -1) {
                 emit(importDirective.startOffset, "Unnecessary import", true)
                 if (autoCorrect) {
                     importDirective.delete()
                 }
-            } else
-            if (name != null && !ref.contains(name) && !operatorSet.contains(name)) {
+            } else if (name != null && !ref.contains(name) && !operatorSet.contains(name) && !name.isComponentN()) {
                 emit(importDirective.startOffset, "Unused import", true)
                 if (autoCorrect) {
                     importDirective.delete()
@@ -82,8 +83,5 @@
         }
     }
 
-    private fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
-        cb(this)
-        this.getChildren(null).forEach { it.visit(cb) }
-    }
+    private fun String.isComponentN() = componentNRegex.matches(this)
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt
index db4e7bb..9cb18aa 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt
@@ -7,8 +7,11 @@
 
 class NoWildcardImportsRule : Rule("no-wildcard-imports") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node.elementType == KtStubElementTypes.IMPORT_DIRECTIVE) {
             val importDirective = node.psi as KtImportDirective
             val path = importDirective.importPath?.pathStr
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt
new file mode 100644
index 0000000..81d0603
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt
@@ -0,0 +1,151 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.KtNodeTypes
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.lang.FileASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
+import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.lexer.KtTokens
+import org.jetbrains.kotlin.psi.psiUtil.children
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+
+class ParameterListWrappingRule : Rule("parameter-list-wrapping") {
+
+    private var indentSize = -1
+    private var maxLineLength = -1
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        if (node.elementType == KtStubElementTypes.FILE) {
+            val ec = EditorConfig.from(node as FileASTNode)
+            indentSize = ec.indentSize
+            maxLineLength = ec.maxLineLength
+            return
+        }
+        if (indentSize <= 0) {
+            return
+        }
+        if (node.elementType == KtStubElementTypes.VALUE_PARAMETER_LIST &&
+            // skip lambda parameters
+            node.treeParent?.elementType != KtNodeTypes.FUNCTION_LITERAL) {
+            // each parameter should be on a separate line if
+            // - at least one of the parameters is
+            // - maxLineLength exceeded (and separating parameters with \n would actually help)
+            // in addition, "(" and ")" must be on separates line if any of the parameters are (otherwise on the same)
+            val putParametersOnSeparateLines = node.textContains('\n') ||
+                // max_line_length exceeded
+                maxLineLength > -1 && (node.psi.column - 1 + node.textLength) > maxLineLength
+            if (putParametersOnSeparateLines) {
+                // aiming for
+                // ... LPAR
+                // <line indent + indentSize> VALUE_PARAMETER...
+                // <line indent> RPAR
+                val indent = "\n" + node.psi.lineIndent()
+                val paramIndent = indent + " ".repeat(indentSize) // single indent as recommended by Jetbrains/Google
+                nextChild@ for (child in node.children()) {
+                    when (child.elementType) {
+                        KtTokens.LPAR -> {
+                            val prevLeaf = child.psi.prevLeaf()
+                            if (prevLeaf is PsiWhiteSpace && prevLeaf.textContains('\n')) {
+                                emit(child.startOffset, errorMessage(child), true)
+                                if (autoCorrect) {
+                                    prevLeaf.delete()
+                                }
+                            }
+                        }
+                        KtStubElementTypes.VALUE_PARAMETER,
+                        KtTokens.RPAR -> {
+                            var paramInnerIndentAdjustment = 0
+                            val prevLeaf = child.psi.prevLeaf()
+                            val intendedIndent = if (child.elementType == KtStubElementTypes.VALUE_PARAMETER)
+                                paramIndent else indent
+                            if (prevLeaf is PsiWhiteSpace) {
+                                val spacing = prevLeaf.text
+                                val cut = spacing.lastIndexOf("\n")
+                                if (cut > -1) {
+                                    val childIndent = spacing.substring(cut)
+                                    if (childIndent == intendedIndent) {
+                                        continue@nextChild
+                                    }
+                                    emit(child.startOffset, "Unexpected indentation" +
+                                        " (expected ${intendedIndent.length - 1}, actual ${childIndent.length - 1})", true)
+                                } else {
+                                    emit(child.startOffset, errorMessage(child), true)
+                                }
+                                if (autoCorrect) {
+                                    val adjustedIndent = (if (cut > -1) spacing.substring(0, cut) else "") + intendedIndent
+                                    paramInnerIndentAdjustment = adjustedIndent.length - prevLeaf.textLength
+                                    (prevLeaf as LeafPsiElement).rawReplaceWithText(adjustedIndent)
+                                }
+                            } else {
+                                emit(child.startOffset, errorMessage(child), true)
+                                if (autoCorrect) {
+                                    paramInnerIndentAdjustment = intendedIndent.length - child.psi.column
+                                    node.addChild(PsiWhiteSpaceImpl(intendedIndent), child)
+                                }
+                            }
+                            if (paramInnerIndentAdjustment != 0 &&
+                                child.elementType == KtStubElementTypes.VALUE_PARAMETER) {
+                                child.visit { n ->
+                                    if (n.elementType == KtTokens.WHITE_SPACE && n.textContains('\n')) {
+                                        val split = n.text.split("\n")
+                                        (n.psi as LeafElement).rawReplaceWithText(split.joinToString("\n") {
+                                            if (paramInnerIndentAdjustment > 0) {
+                                                it + " ".repeat(paramInnerIndentAdjustment)
+                                            } else {
+                                                it.substring(0, Math.max(it.length + paramInnerIndentAdjustment, 0))
+                                            }
+                                        })
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private val PsiElement.column: Int
+        get() {
+            var leaf = PsiTreeUtil.prevLeaf(this)
+            var offsetToTheLeft = 0
+            while (leaf != null) {
+                if (leaf.node.elementType == KtTokens.WHITE_SPACE && leaf.textContains('\n')) {
+                    offsetToTheLeft += leaf.textLength - 1 - leaf.text.lastIndexOf('\n')
+                    break
+                }
+                offsetToTheLeft += leaf.textLength
+                leaf = PsiTreeUtil.prevLeaf(leaf)
+            }
+            return offsetToTheLeft + 1
+        }
+
+    private fun errorMessage(node: ASTNode) =
+        when (node.elementType) {
+            KtTokens.LPAR -> """Unnecessary newline before "(""""
+            KtStubElementTypes.VALUE_PARAMETER ->
+                "Parameter should be on a separate line (unless all parameters can fit a single line)"
+            KtTokens.RPAR -> """Missing newline before ")""""
+            else -> throw UnsupportedOperationException()
+        }
+
+    private fun PsiElement.lineIndent(): String {
+        var leaf = PsiTreeUtil.prevLeaf(this)
+        while (leaf != null) {
+            if (leaf.node.elementType == KtTokens.WHITE_SPACE && leaf.textContains('\n')) {
+                return leaf.text.substring(leaf.text.lastIndexOf('\n') + 1)
+            }
+            leaf = PsiTreeUtil.prevLeaf(leaf)
+        }
+        return ""
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt
index bb9aca8..e2d6b6a 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt
@@ -14,8 +14,11 @@
 
 class SpacingAroundColonRule : Rule("colon-spacing") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is LeafPsiElement && node.textMatches(":") && !node.isPartOfString()) {
             if (node.isPartOf(KtAnnotation::class) || node.isPartOf(KtAnnotationEntry::class)) {
                 // todo: enfore "no spacing"
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
index 2ebca75..2be2935 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt
@@ -6,16 +6,28 @@
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement
 import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl
 import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
 
 class SpacingAroundCommaRule : Rule("comma-spacing") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
-        if (node is LeafPsiElement && node.textMatches(",") && !node.isPartOfString() &&
-            PsiTreeUtil.nextLeaf(node) !is PsiWhiteSpace) {
-            emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
-            if (autoCorrect) {
-                node.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        if (node is LeafPsiElement && node.textMatches(",") && !node.isPartOfString()) {
+            val prevLeaf = PsiTreeUtil.prevLeaf(node, true)
+            if (prevLeaf is PsiWhiteSpace) {
+                emit(prevLeaf.startOffset, "Unexpected spacing before \"${node.text}\"", true)
+                if (autoCorrect) {
+                    prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+                }
+            }
+            if (PsiTreeUtil.nextLeaf(node) !is PsiWhiteSpace) {
+                emit(node.startOffset + 1, "Missing spacing after \"${node.text}\"", true)
+                if (autoCorrect) {
+                    node.rawInsertAfterMe(PsiWhiteSpaceImpl(" "))
+                }
             }
         }
     }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt
index 2f6d226..2bce695 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt
@@ -13,20 +13,25 @@
 
 class SpacingAroundCurlyRule : Rule("curly-spacing") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is LeafPsiElement && !node.isPartOfString()) {
             val prevLeaf = PsiTreeUtil.prevLeaf(node, true)
             val nextLeaf = PsiTreeUtil.nextLeaf(node, true)
             val spacingBefore: Boolean
             val spacingAfter: Boolean
             if (node.textMatches("{")) {
-                spacingBefore = prevLeaf is PsiWhiteSpace || (prevLeaf?.node?.elementType == KtTokens.LPAR &&
+                spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.node?.elementType == KtTokens.AT || (prevLeaf?.node?.elementType == KtTokens.LPAR &&
                     (node.parent is KtLambdaExpression || node.parent.parent is KtLambdaExpression))
                 spacingAfter = nextLeaf is PsiWhiteSpace || nextLeaf?.node?.elementType == KtTokens.RBRACE
                 if (prevLeaf is PsiWhiteSpace &&
-                        !prevLeaf.textContains('\n') &&
-                        PsiTreeUtil.prevLeaf(prevLeaf, true)?.node?.elementType == KtTokens.LPAR) {
+                    !prevLeaf.textContains('\n') &&
+                    PsiTreeUtil.prevLeaf(prevLeaf, true)?.node?.let {
+                        it.elementType == KtTokens.LPAR || it.elementType == KtTokens.AT
+                    } == true) {
                     emit(node.startOffset, "Unexpected space before \"${node.text}\"", true)
                     if (autoCorrect) {
                         prevLeaf.node.treeParent.removeChild(prevLeaf.node)
@@ -41,11 +46,10 @@
                     node.parent.node.elementType == KtNodeTypes.CLASS_BODY)) {
                     emit(node.startOffset, "Unexpected newline before \"${node.text}\"", true)
                     if (autoCorrect) {
-                        (prevLeaf.node as LeafPsiElement).replaceWithText(" ")
+                        (prevLeaf.node as LeafPsiElement).rawReplaceWithText(" ")
                     }
                 }
-            } else
-            if (node.textMatches("}")) {
+            } else if (node.textMatches("}")) {
                 spacingBefore = prevLeaf is PsiWhiteSpace || prevLeaf?.node?.elementType == KtTokens.LBRACE
                 spacingAfter = nextLeaf == null || nextLeaf is PsiWhiteSpace || shouldNotToBeSeparatedBySpace(nextLeaf)
                 if (nextLeaf is PsiWhiteSpace && !nextLeaf.textContains('\n') &&
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt
index 15bb559..ec31bf9 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt
@@ -29,8 +29,11 @@
 
     private val keywordsWithoutSpaces = TokenSet.create(KtTokens.GET_KEYWORD, KtTokens.SET_KEYWORD)
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-                       emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
 
         if (node is LeafPsiElement) {
             if (tokenSet.contains(node.elementType) && node.nextLeaf() !is PsiWhiteSpace) {
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt
index 4bc9448..06dd72e 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt
@@ -36,24 +36,36 @@
 import org.jetbrains.kotlin.psi.KtTypeArgumentList
 import org.jetbrains.kotlin.psi.KtTypeParameterList
 import org.jetbrains.kotlin.psi.KtValueArgument
+import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
 
 class SpacingAroundOperatorsRule : Rule("op-spacing") {
 
     private val tokenSet = TokenSet.create(MUL, PLUS, MINUS, DIV, PERC, LT, GT, LTEQ, GTEQ, EQEQEQ, EXCLEQEQEQ, EQEQ,
         EXCLEQ, ANDAND, OROR, ELVIS, EQ, MULTEQ, DIVEQ, PERCEQ, PLUSEQ, MINUSEQ, ARROW)
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-            emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (tokenSet.contains(node.elementType) && node is LeafPsiElement &&
             !node.isPartOf(KtPrefixExpression::class) && // not unary
-            !node.isPartOf(KtTypeParameterList::class) && // fun <T>fn(): T {}
             !node.isPartOf(KtTypeArgumentList::class) && // C<T>
-            !node.isPartOf(KtValueArgument::class) && // fn(*array)
+            !(node.elementType == MUL && node.isPartOf(KtValueArgument::class)) && // fn(*array)
             !node.isPartOf(KtImportDirective::class) && // import *
             !node.isPartOf(KtSuperExpression::class) // super<T>
         ) {
-            val spacingBefore = PsiTreeUtil.prevLeaf(node, true) is PsiWhiteSpace
-            val spacingAfter = PsiTreeUtil.nextLeaf(node, true) is PsiWhiteSpace
+            if ((node.elementType == GT || node.elementType == LT) &&
+                // fun <T>fn(): T {}
+                node.getNonStrictParentOfType(KtTypeParameterList::class.java)?.parent?.node?.elementType !=
+                    KtStubElementTypes.FUNCTION) {
+                return
+            }
+            val spacingBefore = PsiTreeUtil.prevLeaf(node, true) is PsiWhiteSpace ||
+                node.elementType == GT
+            val spacingAfter = PsiTreeUtil.nextLeaf(node, true) is PsiWhiteSpace ||
+                node.elementType == LT
             when {
                 !spacingBefore && !spacingAfter -> {
                     emit(node.startOffset, "Missing spacing around \"${node.text}\"", true)
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt
new file mode 100644
index 0000000..d7bedbe
--- /dev/null
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt
@@ -0,0 +1,42 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.psi.PsiWhiteSpace
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.lexer.KtTokens
+
+class SpacingAroundRangeOperatorRule : Rule("range-spacing") {
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
+        if (node.elementType == KtTokens.RANGE) {
+            val prevLeaf = PsiTreeUtil.prevLeaf(node.psi, true)
+            val nextLeaf = PsiTreeUtil.nextLeaf(node.psi, true)
+            when {
+                prevLeaf is PsiWhiteSpace && nextLeaf is PsiWhiteSpace -> {
+                    emit(node.startOffset, "Unexpected spacing around \"..\"", true)
+                    if (autoCorrect) {
+                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
+                    }
+                }
+                prevLeaf is PsiWhiteSpace -> {
+                    emit(prevLeaf.node.startOffset, "Unexpected spacing before \"..\"", true)
+                    if (autoCorrect) {
+                        prevLeaf.node.treeParent.removeChild(prevLeaf.node)
+                    }
+                }
+                nextLeaf is PsiWhiteSpace -> {
+                    emit(nextLeaf.node.startOffset, "Unexpected spacing after \"..\"", true)
+                    if (autoCorrect) {
+                        nextLeaf.node.treeParent.removeChild(nextLeaf.node)
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt
index 9fd9190..87f06b4 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt
@@ -6,6 +6,9 @@
 class StandardRuleSetProvider : RuleSetProvider {
 
     override fun get(): RuleSet = RuleSet("standard",
+        ChainWrappingRule(),
+        CommentSpacingRule(),
+        FilenameRule(),
         FinalNewlineRule(),
         // disabled until it's clear how to reconcile difference in Intellij & Android Studio import layout
         // ImportOrderingRule(),
@@ -17,17 +20,21 @@
         NoEmptyClassBodyRule(),
         // disabled until it's clear what to do in case of `import _.it`
         // NoItParamInMultilineLambdaRule(),
+        NoLineBreakAfterElseRule(),
+        NoLineBreakBeforeAssignmentRule(),
         NoMultipleSpacesRule(),
         NoSemicolonsRule(),
         NoTrailingSpacesRule(),
         NoUnitReturnRule(),
         NoUnusedImportsRule(),
         NoWildcardImportsRule(),
+        ParameterListWrappingRule(),
         SpacingAroundColonRule(),
         SpacingAroundCommaRule(),
         SpacingAroundCurlyRule(),
         SpacingAroundKeywordRule(),
         SpacingAroundOperatorsRule(),
+        SpacingAroundRangeOperatorRule(),
         StringTemplateRule()
     )
 }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt
index f58a4bf..e63d792 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt
@@ -32,26 +32,27 @@
             if (dotQualifiedExpression?.node?.elementType == KtStubElementTypes.DOT_QUALIFIED_EXPRESSION) {
                 val callExpression = dotQualifiedExpression!!.lastChild
                 val dot = callExpression.prevSibling
-                if (dot.node.elementType == KtTokens.DOT && callExpression.text == "toString()" &&
-                    dotQualifiedExpression.firstChild.node.elementType != KtNodeTypes.SUPER_EXPRESSION) {
-                    emit(dot.node.startOffset, "Redundant 'toString()' call in string template", true)
+                if (dot?.node?.elementType == KtTokens.DOT &&
+                    callExpression.text == "toString()" &&
+                    dotQualifiedExpression.firstChild?.node?.elementType != KtNodeTypes.SUPER_EXPRESSION) {
+                    emit(dot.node.startOffset, "Redundant \"toString()\" call in string template", true)
                     if (autoCorrect) {
                         node.removeChild(dot.node)
                         node.removeChild(callExpression.node)
                     }
                 }
             }
-        }
-        if (elementType == KtNodeTypes.LONG_STRING_TEMPLATE_ENTRY &&
-            node.text.let { it.substring(2, it.length - 1) }.all { it.isPartOfIdentifier() } &&
-            (node.treeNext.elementType == KtTokens.CLOSING_QUOTE ||
-            (node.psi.nextSibling.node.elementType == KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY &&
-            !node.psi.nextSibling.text[0].isPartOfIdentifier()))) {
-            emit(node.treePrev.startOffset + 2, "Redundant curly braces", true)
-            if (autoCorrect) {
-                // fixme: a proper way would be to downcast to SHORT_STRING_TEMPLATE_ENTRY
-                (node.psi.firstChild as LeafPsiElement).rawReplaceWithText("$") // entry start
-                (node.psi.lastChild as LeafPsiElement).rawReplaceWithText("") // entry end
+            if (node.text.startsWith("${'$'}{") &&
+                node.text.let { it.substring(2, it.length - 1) }.all { it.isPartOfIdentifier() } &&
+                (node.treeNext.elementType == KtTokens.CLOSING_QUOTE ||
+                    (node.psi.nextSibling.node.elementType == KtNodeTypes.LITERAL_STRING_TEMPLATE_ENTRY &&
+                        !node.psi.nextSibling.text[0].isPartOfIdentifier()))) {
+                emit(node.treePrev.startOffset + 2, "Redundant curly braces", true)
+                if (autoCorrect) {
+                    // fixme: a proper way would be to downcast to SHORT_STRING_TEMPLATE_ENTRY
+                    (node.psi.firstChild as LeafPsiElement).rawReplaceWithText("$") // entry start
+                    (node.psi.lastChild as LeafPsiElement).rawReplaceWithText("") // entry end
+                }
             }
         }
     }
diff --git a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt
index 2acc5c2..d9e03b1 100644
--- a/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt
+++ b/ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt
@@ -1,12 +1,20 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
 import org.jetbrains.kotlin.com.intellij.psi.PsiElement
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
 import org.jetbrains.kotlin.psi.KtStringTemplateEntry
 import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
 import kotlin.reflect.KClass
 
 internal fun PsiElement.isPartOf(clazz: KClass<out PsiElement>) = getNonStrictParentOfType(clazz.java) != null
 internal fun PsiElement.isPartOfString() = isPartOf(KtStringTemplateEntry::class)
+internal fun PsiElement.prevLeaf(): PsiElement? = PsiTreeUtil.prevLeaf(this)
+internal fun PsiElement.nextLeaf(): PsiElement? = PsiTreeUtil.nextLeaf(this)
+internal fun ASTNode.visit(cb: (node: ASTNode) -> Unit) {
+    cb(this)
+    this.getChildren(null).forEach { it.visit(cb) }
+}
 
-internal fun <T>List<T>.head() = this.subList(0, this.size - 1)
-internal fun <T>List<T>.tail() = this.subList(1, this.size)
+internal fun <T> List<T>.head() = this.subList(0, this.size - 1)
+internal fun <T> List<T>.tail() = this.subList(1, this.size)
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt
new file mode 100644
index 0000000..b96cf10
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt
@@ -0,0 +1,14 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import org.testng.annotations.Test
+
+class ChainWrappingRuleTest {
+
+    @Test
+    fun testLint() =
+        testLintUsingResource(ChainWrappingRule())
+
+    @Test
+    fun testFormat() =
+        testFormatUsingResource(ChainWrappingRule())
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRuleTest.kt
new file mode 100644
index 0000000..1b8894f
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRuleTest.kt
@@ -0,0 +1,64 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.format
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+
+class CommentSpacingRuleTest {
+
+    @Test
+    fun testLintValidCommentSpacing() {
+        assertThat(CommentSpacingRule().lint(
+            """
+                //
+                // comment
+                var debugging = false // comment
+                var debugging = false // comment//word
+                    // comment
+            """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testLintInvalidCommentSpacing() {
+        assertThat(CommentSpacingRule().lint(
+            """
+                //comment
+                var debugging = false// comment
+                var debugging = false //comment
+                var debugging = false//comment
+                    //comment
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(1, 1, "comment-spacing", "Missing space after //"),
+            LintError(2, 22, "comment-spacing", "Missing space before //"),
+            LintError(3, 23, "comment-spacing", "Missing space after //"),
+            LintError(4, 22, "comment-spacing", "Missing space before //"),
+            LintError(4, 22, "comment-spacing", "Missing space after //"),
+            LintError(5, 5, "comment-spacing", "Missing space after //")
+        ))
+    }
+
+    @Test
+    fun testFormatInvalidCommentSpacing() {
+        assertThat(CommentSpacingRule().format(
+            """
+                //comment
+                var debugging = false// comment
+                var debugging = false //comment
+                var debugging = false//comment
+                    //comment
+            """.trimIndent()
+        )).isEqualTo(
+            """
+                // comment
+                var debugging = false // comment
+                var debugging = false // comment
+                var debugging = false // comment
+                    // comment
+            """.trimIndent()
+        )
+    }
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt
new file mode 100644
index 0000000..84e7ad4
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt
@@ -0,0 +1,129 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+
+class FilenameRuleTest {
+
+    @Test
+    fun testMatchingSingleClassName() {
+        for (src in listOf(
+            "class A",
+            "data class A(val v: Int)",
+            "sealed class A",
+            "interface A",
+            "object A",
+            "enum class A {A}",
+            "typealias A = Set<Network.Node>",
+            // >1 declaration case
+            "class B\nfun A.f() {}"
+        )) {
+            assertThat(FilenameRule().lint(
+                """
+                /*
+                 * license
+                 */
+                @file:JvmName("Foo")
+                package x
+                import y.Z
+                $src
+                //
+                """.trimIndent(),
+                fileName("/some/path/A.kt")
+            )).isEmpty()
+        }
+    }
+
+    @Test
+    fun testNonMatchingSingleClassName() {
+        for (src in mapOf(
+            "class A" to "class",
+            "data class A(val v: Int)" to "class",
+            "sealed class A" to "class",
+            "interface A" to "interface",
+            "object A" to "object",
+            "enum class A {A}" to "class",
+            "typealias A = Set<Network.Node>" to "typealias"
+        )) {
+            assertThat(FilenameRule().lint(
+                """
+                /*
+                 * license
+                 */
+                @file:JvmName("Foo")
+                package x
+                import y.Z
+                ${src.key}
+                //
+                """.trimIndent(),
+                fileName("/some/path/B.kt")
+            )).isEqualTo(listOf(
+                LintError(1, 1, "filename", "${src.value} A should be declared in a file named A.kt")
+            ))
+        }
+    }
+
+    @Test
+    fun testFileWithoutTopLevelDeclarations() {
+        assertThat(FilenameRule().lint(
+            """
+            /*
+             * copyright
+             */
+            """.trimIndent(),
+            fileName("A.kt")
+        )).isEmpty()
+    }
+
+    @Test
+    fun testMultipleTopLevelClasses() {
+        assertThat(FilenameRule().lint(
+            """
+            class B
+            class C
+            """.trimIndent(),
+            fileName("A.kt")
+        )).isEmpty()
+    }
+
+    @Test
+    fun testMultipleNonTopLevelClasses() {
+        assertThat(FilenameRule().lint(
+            """
+            class B {
+                class C
+                class D
+            }
+            """.trimIndent(),
+            fileName("A.kt")
+        )).isEqualTo(listOf(
+            LintError(1, 1, "filename", "class B should be declared in a file named B.kt")
+        ))
+    }
+
+    @Test
+    fun testCaseSensitiveMatching() {
+        assertThat(FilenameRule().lint(
+            """
+            interface Woohoo
+            """.trimIndent(),
+            fileName("woohoo.kt")
+        )).isEqualTo(listOf(
+            LintError(1, 1, "filename", "interface Woohoo should be declared in a file named Woohoo.kt")
+        ))
+    }
+
+    @Test
+    fun testIgnoreKotlinScriptFiles() {
+        assertThat(FilenameRule().lint(
+            """
+            class B
+            """.trimIndent(),
+            fileName("A.kts")
+        )).isEmpty()
+    }
+
+    private fun fileName(fileName: String) = mapOf("file_path" to fileName)
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRuleTest.kt
index 2f91199..bea860f 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRuleTest.kt
@@ -30,6 +30,11 @@
             "fun name() {\n}\n",
             mapOf("insert_final_newline" to "true")
         )).isEmpty()
+        assertThat(FinalNewlineRule().lint(
+            "fun main() {\n}\n\n\n",
+            mapOf("insert_final_newline" to "true"),
+            script = true
+        )).isEmpty()
         // false
         assertThat(FinalNewlineRule().lint(
             "fun name() {\n}",
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt
index 539b45a..636f812 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt
@@ -8,7 +8,7 @@
 class IndentationRuleTest {
 
     @Test
-    fun testRule() {
+    fun testLint() {
         assertThat(IndentationRule().lint(
             """
             /**
@@ -23,7 +23,7 @@
                 val b = builder().setX().setY()
                     .build()
                val c = builder("long_string" +
-                    "")
+                     "")
             }
 
             class A {
@@ -33,52 +33,29 @@
             }
             """.trimIndent()
         )).isEqualTo(listOf(
-            LintError(12, 1, "indent", "Unexpected indentation (3) (it should be multiple of 4)")
+            LintError(12, 1, "indent", "Unexpected indentation (3) (it should be 4)"),
+            // fixme: expected indent should not depend on the "previous" line value
+            LintError(13, 1, "indent", "Unexpected indentation (9) (it should be 7)")
         ))
     }
 
     @Test
-    fun testVerticallyAlignedParametersDoNotTriggerAnError() {
+    fun testLintCustomIndentSize() {
         assertThat(IndentationRule().lint(
             """
-            data class D(val a: Any,
-                         @Test val b: Any,
-                         val c: Any = 0) {
+            fun main() {
+               val v = ""
+                println(v)
             }
-
-            data class D2(
-                val a: Any,
-                val b: Any,
-                val c: Any
-            ) {
-            }
-
-            fun f(val a: Any,
-                  val b: Any,
-                  val c: Any) {
-            }
-
-            fun f2(
-                val a: Any,
-                val b: Any,
-                val c: Any
-            ) {
-            }
-            """.trimIndent()
-        )).isEmpty()
-        assertThat(IndentationRule().lint(
-            """
-            class A(
-               //
-            ) {}
-            """.trimIndent()
+            """.trimIndent(),
+            mapOf("indent_size" to "3")
         )).isEqualTo(listOf(
-            LintError(2, 1, "indent", "Unexpected indentation (3) (it should be multiple of 4)")
+            LintError(3, 1, "indent", "Unexpected indentation (4) (it should be 3)")
         ))
     }
 
     @Test
-    fun testWithCustomIndentSize() {
+    fun testLintCustomIndentSizeValid() {
         assertThat(IndentationRule().lint(
             """
             /**
@@ -100,22 +77,7 @@
     }
 
     @Test
-    fun testErrorWithCustomIndentSize() {
-        assertThat(IndentationRule().lint(
-            """
-            fun main() {
-               val v = ""
-                println(v)
-            }
-            """.trimIndent(),
-            mapOf("indent_size" to "3")
-        )).isEqualTo(listOf(
-            LintError(3, 1, "indent", "Unexpected indentation (4) (it should be multiple of 3)")
-        ))
-    }
-
-    @Test
-    fun testErrorWithIndentSizeUnset() {
+    fun testLintIndentSizeUnset() {
         assertThat(IndentationRule().lint(
             """
             fun main() {
@@ -126,4 +88,99 @@
             mapOf("indent_size" to "unset")
         )).isEmpty()
     }
+
+    @Test
+    fun testLintWithContinuationIndentSizeSet() {
+        // gcd(indent_size, continuation_indent_size) == 2
+        assertThat(IndentationRule().lint(
+            """
+            fun main() {
+                val v = ""
+                      .call()
+                 call()
+            }
+            """.trimIndent(),
+            mapOf("indent_size" to "4", "continuation_indent_size" to "6")
+        )).isEqualTo(listOf(
+            LintError(4, 1, "indent", "Unexpected indentation (5) (it should be 2)")
+        ))
+        assertThat(IndentationRule().lint(
+            """
+            fun main() {
+                val v = ""
+                      .call()
+                 call()
+            }
+            """.trimIndent(),
+            mapOf("indent_size" to "4", "continuation_indent_size" to "2")
+        )).isEqualTo(listOf(
+            LintError(4, 1, "indent", "Unexpected indentation (5) (it should be 2)")
+        ))
+        // gcd(indent_size, continuation_indent_size) == 1 equals no indent check
+        assertThat(IndentationRule().lint(
+            """
+            fun main() {
+                val v = ""
+                    .call()
+                     .call()
+                      .call()
+            }
+            """.trimIndent(),
+            mapOf("indent_size" to "4", "continuation_indent_size" to "3")
+        )).isEmpty()
+    }
+
+    // https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting
+    @Test
+    fun testLintMultilineFunctionCall() {
+        assertThat(IndentationRule().lint(
+            """
+            fun main() {
+                fn(a,
+                   b,
+                   c)
+            }
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(3, 1, "indent", "Unexpected indentation (7) (it should be 8)"),
+            LintError(4, 1, "indent", "Unexpected indentation (7) (it should be 8)")
+        ))
+    }
+
+    @Test
+    fun testLintCommentsAreIgnored() {
+        assertThat(IndentationRule().lint(
+            """
+            fun funA(argA: String) =
+                // comment
+            // comment
+                call(argA)
+            fun main() {
+                addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
+             // comment
+                    override fun onLayoutChange(
+                    )
+                })
+            }
+            """.trimIndent(),
+            mapOf("indent_size" to "4")
+        )).isEqualTo(listOf(
+            LintError(7, 1, "indent", "Unexpected indentation (1) (it should be 8)")
+        ))
+    }
+
+    @Test(description = "https://github.com/shyiko/ktlint/issues/180")
+    fun testLintWhereClause() {
+        assertThat(IndentationRule().lint(
+            """
+            class BiAdapter<C : RecyclerView.ViewHolder, V1 : C, V2 : C, out A1, out A2>(
+                val adapter1: A1,
+                val adapter2: A2
+            ) : RecyclerView.Adapter<C>()
+                where A1 : RecyclerView.Adapter<V1>, A1 : ComposableAdapter.ViewTypeProvider,
+                      A2 : RecyclerView.Adapter<V2>, A2 : ComposableAdapter.ViewTypeProvider {
+            }
+            """.trimIndent()
+        )).isEmpty()
+    }
 }
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRuleTest.kt
index 723b4d3..52659f8 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRuleTest.kt
@@ -1,5 +1,8 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
 import org.testng.annotations.Test
 
 class MaxLineLengthRuleTest {
@@ -10,7 +13,37 @@
     }
 
     @Test
+    fun testErrorSupression() {
+        assertThat(MaxLineLengthRule().lint(
+            """
+            fun main(vaaaaaaaaaaaaaaaaaaaaaaar: String) { // ktlint-disable max-line-length
+                println("teeeeeeeeeeeeeeeeeeeeeeeeeeeeeeext")
+            /* ktlint-disable max-line-length */
+                println("teeeeeeeeeeeeeeeeeeeeeeeeeeeeeeext")
+            }
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "40")
+        )).isEqualTo(listOf(
+            LintError(2, 1, "max-line-length", "Exceeded max line length (40)")
+        ))
+    }
+
+    @Test
     fun testLintOff() {
         testLintUsingResource(MaxLineLengthRule(), userData = mapOf("max_line_length" to "off"), qualifier = "off")
     }
+
+    @Test
+    fun testRangeSearch() {
+        for (i in 0 until 10) {
+            assertThat(RangeTree((0..i).asSequence().toList()).query(Int.MIN_VALUE, Int.MAX_VALUE).toString())
+                .isEqualTo((0..i).asSequence().toList().toString())
+        }
+        assertThat(RangeTree(emptyList()).query(1, 5).toString()).isEqualTo("[]")
+        assertThat(RangeTree((5 until 10).asSequence().toList()).query(1, 5).toString()).isEqualTo("[]")
+        assertThat(RangeTree((5 until 10).asSequence().toList()).query(3, 7).toString()).isEqualTo("[5, 6]")
+        assertThat(RangeTree((5 until 10).asSequence().toList()).query(7, 12).toString()).isEqualTo("[7, 8, 9]")
+        assertThat(RangeTree((5 until 10).asSequence().toList()).query(10, 15).toString()).isEqualTo("[]")
+        assertThat(RangeTree(listOf(1, 5, 10)).query(3, 4).toString()).isEqualTo("[]")
+    }
 }
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRuleTest.kt
index 3c2d1fb..24372bb 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRuleTest.kt
@@ -13,7 +13,7 @@
         // pretty much every line below should trip an error
         assertThat(ModifierOrderRule().lint(
             """
-            abstract open class A { // open is here for test purposes only, otherwise it's redundant
+            abstract @Deprecated open class A { // open is here for test purposes only, otherwise it's redundant
                 open protected val v = ""
                 open suspend internal fun f(v: Any): Any = ""
                 lateinit public var lv: String
@@ -22,9 +22,19 @@
 
             class B : A() {
                 override public val v = ""
-                override suspend fun f(v: Any): Any = ""
-                override tailrec fun findFixPoint(x: Double): Double
+                suspend override fun f(v: Any): Any = ""
+                tailrec override fun findFixPoint(x: Double): Double
                     = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
+                override @Annotation fun getSomething() = ""
+                override @Annotation suspend public @Woohoo(data = "woohoo") fun doSomething() = ""
+                @A
+                @B(v = [
+                    "foo",
+                    "baz",
+                    "bar"
+                ])
+                @C
+                suspend public fun returnsSomething() = ""
 
                 companion object {
                    const internal val V = ""
@@ -32,15 +42,18 @@
             }
             """.trimIndent()
         )).isEqualTo(listOf(
-            LintError(1, 1, "modifier-order", "Incorrect modifier order (should be \"open abstract\")"),
+            LintError(1, 1, "modifier-order", "Incorrect modifier order (should be \"@Annotation... open abstract\")"),
             LintError(2, 5, "modifier-order", "Incorrect modifier order (should be \"protected open\")"),
             LintError(3, 5, "modifier-order", "Incorrect modifier order (should be \"internal open suspend\")"),
             LintError(4, 5, "modifier-order", "Incorrect modifier order (should be \"public lateinit\")"),
             LintError(5, 5, "modifier-order", "Incorrect modifier order (should be \"abstract tailrec\")"),
             LintError(9, 5, "modifier-order", "Incorrect modifier order (should be \"public override\")"),
-            LintError(10, 5, "modifier-order", "Incorrect modifier order (should be \"suspend override\")"),
-            LintError(11, 5, "modifier-order", "Incorrect modifier order (should be \"tailrec override\")"),
-            LintError(15, 8, "modifier-order", "Incorrect modifier order (should be \"internal const\")")
+            LintError(10, 5, "modifier-order", "Incorrect modifier order (should be \"override suspend\")"),
+            LintError(11, 5, "modifier-order", "Incorrect modifier order (should be \"override tailrec\")"),
+            LintError(13, 5, "modifier-order", "Incorrect modifier order (should be \"@Annotation... override\")"),
+            LintError(14, 5, "modifier-order", "Incorrect modifier order (should be \"@Annotation... public override suspend\")"),
+            LintError(15, 5, "modifier-order", "Incorrect modifier order (should be \"@Annotation... public suspend\")"),
+            LintError(25, 8, "modifier-order", "Incorrect modifier order (should be \"internal const\")")
         ))
     }
 
@@ -48,7 +61,7 @@
     fun testFormat() {
         assertThat(ModifierOrderRule().format(
             """
-            abstract open class A { // open is here for test purposes only, otherwise it's redundant
+            abstract @Deprecated open class A { // open is here for test purposes only, otherwise it's redundant
                 open protected val v = ""
                 open suspend internal fun f(v: Any): Any = ""
                 lateinit public var lv: String
@@ -57,9 +70,19 @@
 
             class B : A() {
                 override public val v = ""
-                override suspend fun f(v: Any): Any = ""
-                override tailrec fun findFixPoint(x: Double): Double
+                suspend override fun f(v: Any): Any = ""
+                tailrec override fun findFixPoint(x: Double): Double
                     = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
+                override @Annotation fun getSomething() = ""
+                suspend @Annotation override public @Woohoo(data = "woohoo") fun doSomething() = ""
+                @A
+                @B(v = [
+                    "foo",
+                    "baz",
+                    "bar"
+                ])
+                @C
+                suspend public fun returnsSomething() = ""
 
                 companion object {
                    const internal val V = ""
@@ -68,7 +91,7 @@
             """
         )).isEqualTo(
             """
-            open abstract class A { // open is here for test purposes only, otherwise it's redundant
+            @Deprecated open abstract class A { // open is here for test purposes only, otherwise it's redundant
                 protected open val v = ""
                 internal open suspend fun f(v: Any): Any = ""
                 public lateinit var lv: String
@@ -77,9 +100,19 @@
 
             class B : A() {
                 public override val v = ""
-                suspend override fun f(v: Any): Any = ""
-                tailrec override fun findFixPoint(x: Double): Double
+                override suspend fun f(v: Any): Any = ""
+                override tailrec fun findFixPoint(x: Double): Double
                     = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
+                @Annotation override fun getSomething() = ""
+                @Annotation @Woohoo(data = "woohoo") public override suspend fun doSomething() = ""
+                @A
+                @B(v = [
+                    "foo",
+                    "baz",
+                    "bar"
+                ])
+                @C
+                public suspend fun returnsSomething() = ""
 
                 companion object {
                    internal const val V = ""
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRuleTest.kt
index 60da450..5054d99 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRuleTest.kt
@@ -38,6 +38,19 @@
     }
 
     @Test
+    fun testLintAtTheEndOfFile() {
+        assertThat(NoConsecutiveBlankLinesRule().lint(
+            """
+            fun main() {
+            }
+
+
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(4, 1, "no-consecutive-blank-lines", "Needless blank line(s)")))
+    }
+
+    @Test
     fun testLintInString() {
         assertThat(NoConsecutiveBlankLinesRule().lint(
             "fun main() {println(\"\"\"\n\n\n\"\"\")}")).isEmpty()
@@ -94,4 +107,23 @@
             """
         )
     }
+
+    @Test
+    fun testFormatAtTheEndOfFile() {
+        assertThat(NoConsecutiveBlankLinesRule().format(
+            """
+            fun main() {
+            }
+
+
+            """,
+            script = true
+        )).isEqualTo(
+            """
+            fun main() {
+            }
+
+            """
+        )
+    }
 }
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRuleTest.kt
index e115bd0..3bcd180 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRuleTest.kt
@@ -1,5 +1,7 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
+import com.github.shyiko.ktlint.test.format
+import org.assertj.core.api.Assertions.assertThat
 import org.testng.annotations.Test
 
 class NoEmptyClassBodyRuleTest {
@@ -13,4 +15,10 @@
     fun testFormat() {
         testFormatUsingResource(NoEmptyClassBodyRule())
     }
+
+    @Test
+    fun testFormatEmptyClassBodyAtTheEndOfFile() {
+        assertThat(NoEmptyClassBodyRule().format("class A {}\n")).isEqualTo("class A\n")
+        assertThat(NoEmptyClassBodyRule().format("class A {}")).isEqualTo("class A")
+    }
 }
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRuleTest.kt
new file mode 100644
index 0000000..8b645ec
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRuleTest.kt
@@ -0,0 +1,127 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.format
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions
+import org.testng.annotations.Test
+
+class NoLineBreakAfterElseRuleTest {
+
+    @Test
+    fun testViolationForLineBreakBetweenElseAndIf() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().lint(
+            """
+            fun funA() {
+                if (conditionA()) {
+                    doSomething()
+                } else
+                if (conditionB()) {
+                    doAnotherThing()
+                }
+            }
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(5, 1, "no-line-break-after-else", "Unexpected line break after \"else\"")
+        ))
+    }
+
+    @Test
+    fun testFixViolationForLineBreakBetweenElseAndIf() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().format(
+            """
+            fun funA() {
+                if (conditionA()) {
+                    doSomething()
+                } else
+                if (conditionB()) {
+                    doAnotherThing()
+                }
+            }
+            """.trimIndent()
+        )).isEqualTo(
+            """
+            fun funA() {
+                if (conditionA()) {
+                    doSomething()
+                } else if (conditionB()) {
+                    doAnotherThing()
+                }
+            }
+                """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testValidElseIf() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().lint(
+            """
+            fun funA() {
+                if (conditionA()) {
+                    doSomething()
+                } else if (conditionB()) {
+                    doAnotherThing()
+                }
+            }
+            """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testValidSimpleElse() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().lint(
+            """
+            fun funA() {
+                if (conditionA()) {
+                    doSomething()
+                } else {
+                    doAnotherThing()
+                }
+            }
+            """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testViolationForLineBreakBetweenElseAndBracket() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().lint(
+            """
+            fun funA() {
+                if (conditionA()) {
+                    doSomething()
+                } else
+                {
+                    doAnotherThing()
+                }
+            }
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(5, 1, "no-line-break-after-else", "Unexpected line break after \"else\"")
+        ))
+    }
+
+    @Test
+    fun testViolationWhenBracketOmitted() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().lint(
+            """
+            fun funA() {
+                if (conditionA())
+                    doSomething()
+                else
+                    doAnotherThing()
+            }
+            """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testValidWhenBracketOmitted() {
+        Assertions.assertThat(NoLineBreakAfterElseRule().lint(
+            """
+            fun funA() {
+                if (conditionA()) doSomething() else doAnotherThing()
+            }
+            """.trimIndent()
+        )).isEmpty()
+    }
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRuleTest.kt
new file mode 100644
index 0000000..55ac3e3
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRuleTest.kt
@@ -0,0 +1,69 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.format
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+
+const val ruleId = "no-line-break-before-assignment"
+
+class NoLineBreakBeforeAssignmentRuleTest {
+    @Test
+    fun testAllPartsOnSameLineIsValid() {
+        assertThat(NoLineBreakBeforeAssignmentRule().lint(
+            """
+              val valA = ""
+              """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testLineBreakAfterAssignmentIsValid() {
+        assertThat(NoLineBreakBeforeAssignmentRule().lint(
+            """
+                val valA =
+                      ""
+              """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testLineBreakBeforeAssignmentIsViolation() {
+        assertThat(NoLineBreakBeforeAssignmentRule().lint(
+            """
+              val valA
+                    = ""
+              """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(2, 7, ruleId, "Line break before assignment is not allowed")
+        ))
+    }
+
+    @Test
+    fun testViolationInFunction() {
+        assertThat(NoLineBreakBeforeAssignmentRule().lint(
+            """
+              fun funA()
+                    = ""
+              """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(2, 7, ruleId, "Line break before assignment is not allowed")
+        ))
+    }
+
+    @Test
+    fun testFixViolationByRemovingLineBreakFromLeftAndPutItOnRightSide() {
+        assertThat(NoLineBreakBeforeAssignmentRule().format(
+            """
+              fun funA()
+                    = ""
+              """.trimIndent()
+        )).isEqualTo(
+            """
+              fun funA() =
+                    ""
+              """.trimIndent()
+        )
+    }
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRuleTest.kt
index 2384ffa..ad5f6cc 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRuleTest.kt
@@ -48,6 +48,50 @@
     }
 
     @Test
+    fun testLintIssue204() {
+        assertThat(NoUnusedImportsRule().lint(
+            """
+            package com.example.another
+
+            import com.example.anotherThing
+
+            class Foo {
+                val bar = anotherThing
+            }
+            """.trimIndent()
+        )).isEmpty()
+    }
+
+    @Test
+    fun testLintDestructuringAssignment() {
+        assertThat(NoUnusedImportsRule().lint(
+            """
+            import p.component6
+
+            fun main() {
+                val (one, two, three, four, five, six) = someList
+            }
+            """.trimIndent()
+        )).isEmpty()
+        assertThat(NoUnusedImportsRule().lint(
+            """
+            import p.component6
+            import p.component2
+            import p.component100
+            import p.component
+            import p.component12woohoo
+
+            fun main() {
+                val (one, two, three, four, five, six) = someList
+            }
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(4, 1, "no-unused-imports", "Unused import"),
+            LintError(5, 1, "no-unused-imports", "Unused import")
+        ))
+    }
+
+    @Test
     fun testLintKDocLinkImport() {
         assertThat(NoUnusedImportsRule().lint(
             """
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt
new file mode 100644
index 0000000..24c048e
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt
@@ -0,0 +1,498 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import com.github.shyiko.ktlint.core.LintError
+import com.github.shyiko.ktlint.test.format
+import com.github.shyiko.ktlint.test.lint
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+
+class ParameterListWrappingRuleTest {
+
+    @Test
+    fun testLintClassParameterList() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            class ClassA(paramA: String, paramB: String,
+                         paramC: String)
+            """.trimIndent()
+            )
+        ).isEqualTo(
+            listOf(
+                LintError(1, 14, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 30, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(2, 14, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 13)"),
+                LintError(2, 28, "parameter-list-wrapping", """Missing newline before ")"""")
+            )
+        )
+    }
+
+    @Test
+    fun testLintClassParameterListWhenMaxLineLengthExceeded() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            class ClassA(paramA: String, paramB: String, paramC: String)
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "10")
+            )
+        ).isEqualTo(
+            listOf(
+                LintError(1, 14, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 30, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 46, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 60, "parameter-list-wrapping", """Missing newline before ")"""")
+            )
+        )
+        // corner case
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            class ClassA(paramA: String)
+             class ClassA(paramA: String)
+            class ClassA(paramA: String)
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "28")
+            )
+        ).isEqualTo(
+            listOf(
+                LintError(2, 15, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(2, 29, "parameter-list-wrapping", "Missing newline before \")\"")
+            )
+        )
+    }
+
+    @Test
+    fun testLintClassParameterListValid() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            class ClassA(paramA: String, paramB: String, paramC: String)
+            """.trimIndent()
+            )
+        ).isEmpty()
+    }
+
+    @Test
+    fun testLintClassParameterListValidMultiLine() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            class ClassA(
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+            )
+        ).isEmpty()
+    }
+
+    @Test
+    fun testFormatClassParameterList() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            class ClassA(paramA: String, paramB: String,
+                         paramC: String)
+            """.trimIndent()
+            )
+        ).isEqualTo(
+            """
+            class ClassA(
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatClassParameterListWhenMaxLineLengthExceeded() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            class ClassA(paramA: String, paramB: String, paramC: String)
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "10")
+            )
+        ).isEqualTo(
+            """
+            class ClassA(
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testLintFunctionParameterList() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            fun f(a: Any,
+                  b: Any,
+                  c: Any) {
+            }
+            """.trimIndent()
+            )).isEqualTo(
+            listOf(
+                LintError(1, 7, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(2, 7, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 6)"),
+                LintError(3, 7, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 6)"),
+                LintError(3, 13, "parameter-list-wrapping", """Missing newline before ")"""")
+            )
+        )
+    }
+
+    @Test
+    fun testLintFunctionParameterListWhenMaxLineLengthExceeded() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            fun f(a: Any, b: Any, c: Any) {
+            }
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "10")
+            )).isEqualTo(
+            listOf(
+                LintError(1, 7, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 15, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 23, "parameter-list-wrapping", "Parameter should be on a separate line (unless all parameters can fit a single line)"),
+                LintError(1, 29, "parameter-list-wrapping", """Missing newline before ")"""")
+            )
+        )
+    }
+
+    @Test
+    fun testFormatFunctionParameterList() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            fun f(a: Any,
+                  b: Any,
+                  c: Any) {
+            }
+            """.trimIndent()
+            )).isEqualTo(
+            """
+            fun f(
+                a: Any,
+                b: Any,
+                c: Any
+            ) {
+            }
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatFunctionParameterListWhenMaxLineLengthExceeded() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            fun f(a: Any, b: Any, c: Any) {
+            }
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "10")
+            )
+        ).isEqualTo(
+            """
+            fun f(
+                a: Any,
+                b: Any,
+                c: Any
+            ) {
+            }
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testLambdaParametersAreIgnored() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            val fieldExample =
+                  LongNameClass { paramA,
+                                  paramB,
+                                  paramC ->
+                      ClassB(paramA, paramB, paramC)
+                  }
+            """.trimIndent()
+            )).isEmpty()
+    }
+
+    @Test
+    fun testFormatPreservesIndent() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            class A {
+                fun f(a: Any,
+                      b: Any,
+                      c: Any) {
+                }
+            }
+            """.trimIndent()
+            )).isEqualTo(
+            """
+            class A {
+                fun f(
+                    a: Any,
+                    b: Any,
+                    c: Any
+                ) {
+                }
+            }
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatPreservesIndentWithAnnotations() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            class A {
+                fun f(@Annotation
+                      a: Any,
+                      @Annotation([
+                          "v1",
+                          "v2"
+                      ])
+                      b: Any,
+                      c: Any =
+                          false,
+                      @Annotation d: Any) {
+                }
+            }
+            """.trimIndent()
+            )).isEqualTo(
+            """
+            class A {
+                fun f(
+                    @Annotation
+                    a: Any,
+                    @Annotation([
+                        "v1",
+                        "v2"
+                    ])
+                    b: Any,
+                    c: Any =
+                        false,
+                    @Annotation d: Any
+                ) {
+                }
+            }
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatCorrectsRPARIndentIfNeeded() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            class A {
+                fun f(a: Any,
+                      b: Any,
+                      c: Any
+                   ) {
+                }
+            }
+            """.trimIndent()
+            )).isEqualTo(
+            """
+            class A {
+                fun f(
+                    a: Any,
+                    b: Any,
+                    c: Any
+                ) {
+                }
+            }
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatNestedDeclarations() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            fun visit(
+                node: ASTNode,
+                    autoCorrect: Boolean,
+                emit: (offset: Int, errorMessage: String,
+                canBeAutoCorrected: Boolean) -> Unit
+            ) {}
+            """.trimIndent()
+            )).isEqualTo(
+            """
+            fun visit(
+                node: ASTNode,
+                autoCorrect: Boolean,
+                emit: (
+                    offset: Int,
+                    errorMessage: String,
+                    canBeAutoCorrected: Boolean
+                ) -> Unit
+            ) {}
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatNestedDeclarationsWhenMaxLineLengthExceeded() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            fun visit(node: ASTNode, autoCorrect: Boolean, emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {}
+            """.trimIndent(),
+            userData = mapOf("max_line_length" to "10")
+            )).isEqualTo(
+            """
+            fun visit(
+                node: ASTNode,
+                autoCorrect: Boolean,
+                emit: (
+                    offset: Int,
+                    errorMessage: String,
+                    canBeAutoCorrected: Boolean
+                ) -> Unit
+            ) {}
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatNestedDeclarationsValid() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            fun visit(
+                node: ASTNode,
+                autoCorrect: Boolean,
+                emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+            ) {}
+            """.trimIndent()
+            )).isEqualTo(
+            """
+            fun visit(
+                node: ASTNode,
+                autoCorrect: Boolean,
+                emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+            ) {}
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testCommentsAreIgnored() {
+        assertThat(ParameterListWrappingRule().lint(
+            """
+            data class A(
+               /*
+                * comment
+                */
+               //
+               var v: String
+            )
+            """.trimIndent()
+        )).isEqualTo(listOf(
+            LintError(6, 4, "parameter-list-wrapping", "Unexpected indentation (expected 4, actual 3)")
+        ))
+    }
+
+    @Test
+    fun testLintClassDanglingLeftParen() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            class ClassA
+            (
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+            )
+        ).isEqualTo(
+            listOf(
+                LintError(2, 1, "parameter-list-wrapping", """Unnecessary newline before "("""")
+            )
+        )
+    }
+
+    @Test
+    fun testLintFunctionDanglingLeftParen() {
+        assertThat(
+            ParameterListWrappingRule().lint(
+            """
+            fun doSomething
+            (
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+            )
+        ).isEqualTo(
+            listOf(
+                LintError(2, 1, "parameter-list-wrapping", """Unnecessary newline before "("""")
+            )
+        )
+    }
+
+    @Test
+    fun testFormatClassDanglingLeftParen() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            class ClassA constructor
+            (
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+            )
+        ).isEqualTo(
+            """
+            class ClassA constructor(
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+        )
+    }
+
+    @Test
+    fun testFormatFunctionDanglingLeftParen() {
+        assertThat(
+            ParameterListWrappingRule().format(
+            """
+            fun doSomething
+            (
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+            )
+        ).isEqualTo(
+            """
+            fun doSomething(
+                paramA: String,
+                paramB: String,
+                paramC: String
+            )
+            """.trimIndent()
+        )
+    }
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt
index cc71781..e0bcb9c 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt
@@ -23,12 +23,38 @@
         )).isEqualTo(listOf(
             LintError(2, 10, "comma-spacing", "Missing spacing after \",\"")
         ))
+        assertThat(SpacingAroundCommaRule().lint(
+            """
+            some.method(1 , 2)
+            """.trimIndent(),
+            script = true
+        )).isEqualTo(listOf(
+            LintError(1, 14, "comma-spacing", "Unexpected spacing before \",\"")
+        ))
     }
 
     @Test
     fun testFormat() {
         assertThat(SpacingAroundCommaRule().format("fun main() { x(1,3); x(1, 3) }"))
             .isEqualTo("fun main() { x(1, 3); x(1, 3) }")
+        assertThat(SpacingAroundCommaRule().format(
+            """
+            fun fn(
+                arg1: Int ,
+                arg2: Int
+                ,
+
+                arg3: Int
+            ) = Unit
+            """.trimIndent()
+        )).isEqualTo(
+            """
+            fun fn(
+                arg1: Int,
+                arg2: Int,
+
+                arg3: Int
+            ) = Unit
+            """.trimIndent())
     }
 }
-
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRuleTest.kt
index f5d2ad2..4d50c68 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRuleTest.kt
@@ -11,6 +11,7 @@
     @Test
     fun testLint() {
         assertThat(SpacingAroundCurlyRule().lint("fun emit() { }")).isEmpty()
+        assertThat(SpacingAroundCurlyRule().lint("fun emit() { val a = a@{ } }")).isEmpty()
         assertThat(SpacingAroundCurlyRule().lint("fun emit() {}")).isEmpty()
         assertThat(SpacingAroundCurlyRule().lint("fun main() { val v = if (true){return 0} }"))
             .isEqualTo(listOf(
@@ -88,6 +89,7 @@
                 val f =
                     { true }
             }
+            class A { private val shouldEjectBlock = block@ { (pathProgress ?: return@block false) >= 0.85 } }
             """.trimIndent()
         )).isEqualTo(
             """
@@ -129,6 +131,7 @@
                 val f =
                     { true }
             }
+            class A { private val shouldEjectBlock = block@{ (pathProgress ?: return@block false) >= 0.85 } }
             """.trimIndent()
         )
     }
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRuleTest.kt
index b8d25bd..dad897e 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRuleTest.kt
@@ -141,4 +141,3 @@
         ))
     }
 }
-
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRuleTest.kt
index a64b4e3..4bb3591 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRuleTest.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRuleTest.kt
@@ -1,73 +1,16 @@
 package com.github.shyiko.ktlint.ruleset.standard
 
-import com.github.shyiko.ktlint.core.LintError
-import com.github.shyiko.ktlint.test.format
-import com.github.shyiko.ktlint.test.lint
-import org.assertj.core.api.Assertions.assertThat
 import org.testng.annotations.Test
 
 class SpacingAroundOperatorsRuleTest {
 
     @Test
     fun testLint() {
-        assertThat(SpacingAroundOperatorsRule().lint(
-            """
-            import a.b.*
-            fun main() {
-                val v = 0 - 1 * 2
-                val v1 = 0-1*2
-                val v2 = -0 - 1
-                val v3 = v * 2
-                i++
-                val y = +1
-                var x = 1 in 3..4
-                val b = 1 < 2
-                fun(a = true)
-                val res = ArrayList<LintError>()
-                fn(*arrayOfNulls<Any>(0 * 1))
-                fun <T>List<T>.head() {}
-                val a= ""
-                d *= 1
-                call(*v)
-                open class A<T> {
-                    open fun x() {}
-                }
-                class B<T> : A<T>() {
-                    override fun x() = super<A>.x()
-                }
-            }
-            """.trimIndent()
-        )).isEqualTo(listOf(
-            LintError(4, 15, "op-spacing", "Missing spacing around \"-\""),
-            LintError(4, 17, "op-spacing", "Missing spacing around \"*\""),
-            LintError(15, 10, "op-spacing", "Missing spacing before \"=\"")
-        ))
+        testLintUsingResource(SpacingAroundOperatorsRule())
     }
 
     @Test
     fun testFormat() {
-        assertThat(SpacingAroundOperatorsRule().format(
-            """
-            fun main() {
-                val v1 = 0-1*2
-                val v2 = -0-1
-                val v3 = v*2
-                i++
-                val y = +1
-                var x = 1 in 3..4
-            }
-            """.trimIndent()
-        )).isEqualTo(
-            """
-            fun main() {
-                val v1 = 0 - 1 * 2
-                val v2 = -0 - 1
-                val v3 = v * 2
-                i++
-                val y = +1
-                var x = 1 in 3..4
-            }
-            """.trimIndent()
-        )
+        testFormatUsingResource(SpacingAroundOperatorsRule())
     }
 }
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRuleTest.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRuleTest.kt
new file mode 100644
index 0000000..e836e32
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRuleTest.kt
@@ -0,0 +1,16 @@
+package com.github.shyiko.ktlint.ruleset.standard
+
+import org.testng.annotations.Test
+
+class SpacingAroundRangeOperatorRuleTest {
+
+    @Test
+    fun testLint() {
+        testLintUsingResource(SpacingAroundRangeOperatorRule())
+    }
+
+    @Test
+    fun testFormat() {
+        testFormatUsingResource(SpacingAroundRangeOperatorRule())
+    }
+}
diff --git a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/package-test.kt b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/package-test.kt
index 016cbd4..3c42d4b 100644
--- a/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/package-test.kt
+++ b/ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/package-test.kt
@@ -20,14 +20,17 @@
     }
     val input = resourceText.substring(0, dividerIndex)
     val errors = resourceText.substring(dividerIndex + 1).split('\n').mapNotNull { line ->
-        if (line.isBlank() || line == "// expect") null else
+        if (line.isBlank() || line == "// expect") {
+            null
+        } else {
             line.trimMargin("// ").split(':', limit = 3).let { expectation ->
                 if (expectation.size != 3) {
                     throw RuntimeException("$resource expectation must be a triple <line>:<column>:<message>")
-                        // " (<message> is not allowed to contain \":\")")
+                    // " (<message> is not allowed to contain \":\")")
                 }
                 LintError(expectation[0].toInt(), expectation[1].toInt(), rule.id, expectation[2])
             }
+        }
     }
     assertThat(rule.lint(input, userData)).isEqualTo(errors)
 }
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec
new file mode 100644
index 0000000..481c7b2
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec
@@ -0,0 +1,66 @@
+fun main() {
+    val anchor = owner.firstChild!!
+        .siblings(forward = true)
+        .dropWhile { it is PsiComment || it is PsiWhiteSpace }
+    val s = foo()
+        ?: bar
+    val s = foo()
+        ?.bar
+    val s = 1
+        + 2
+    val s = true &&
+        false
+    val s = b.equals(o.b) &&
+        g == o.g
+    val d = 1 +
+        -1
+    val d = 1
+        + -1
+    when (foo){
+        0 -> {
+        }
+        1 -> {
+        }
+        -2 -> {
+        }
+    }
+    if (
+      -3 == a()
+    ) {}
+    if (
+      // comment
+      -3 == a()
+    ) {}
+    if (
+      /* comment */
+      -3 == a()
+    ) {}
+    if (c)
+      -7
+    else
+      -8
+    try {
+      fn()
+    } catch(e: Exception) {
+      -9
+    }
+    var x =
+        -2 >
+        (2 + 2)
+    -3
+    // https://github.com/shyiko/ktlint/pull/193
+    var x = false && // comment
+        false
+    x = false &&
+        /* comment */
+        // comment
+        false
+    var y = false // comment
+        .call()
+    y = false
+        // comment
+        .call()
+    y = false // comment
+        /* comment */
+        .call()
+}
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec
new file mode 100644
index 0000000..82af6da
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec
@@ -0,0 +1,66 @@
+fun main() {
+    val anchor = owner.firstChild!!.
+        siblings(forward = true).
+        dropWhile { it is PsiComment || it is PsiWhiteSpace }
+    val s = foo() ?:
+        bar
+    val s = foo()?.
+        bar
+    val s = 1
+        + 2
+    val s = true
+        && false
+    val s = b.equals(o.b)
+        && g == o.g
+    val d = 1 +
+        -1
+    val d = 1
+        + -1
+    when (foo){
+        0 -> {
+        }
+        1 -> {
+        }
+        -2 -> {
+        }
+    }
+    if (
+      -3 == a()
+    ) {}
+    if (
+      // comment
+      -3 == a()
+    ) {}
+    if (
+      /* comment */
+      -3 == a()
+    ) {}
+    if (c)
+      -7
+    else
+      -8
+    try {
+      fn()
+    } catch(e: Exception) {
+      -9
+    }
+    var x =
+        -2 >
+        (2 + 2)
+    -3
+    // https://github.com/shyiko/ktlint/pull/193
+    var x = false // comment
+        && false
+    x = false
+        /* comment */
+        // comment
+        && false
+    var y = false. // comment
+        call()
+    y = false.
+        // comment
+        call()
+    y = false. // comment
+        /* comment */
+        call()
+}
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec
new file mode 100644
index 0000000..2770cde
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec
@@ -0,0 +1,46 @@
+fun main() {
+    val anchor = owner.firstChild!!.
+        siblings(forward = true).
+        dropWhile { it is PsiComment || it is PsiWhiteSpace }
+    val s = foo() ?:
+        bar
+    val s = foo()?.
+        bar
+    val d = 1
+        + 1
+    val s = true
+        && false
+    val s = b.equals(o.b)
+        && g == o.g
+    val s = ((1 + 2)
+        / 3)
+    val d = 1 +
+        -1
+    val d = 1
+        + -1
+    val d = (1
+        + 1)
+    fn(1,
+    -1)
+    fn(
+        *typedArray<EventListener>(),
+        -0,
+        *typedArray<EventListener>()
+    )
+}
+
+/**
+ * @see KtLint.EDITOR_CONFIG_USER_DATA_KEY
+ * @see KtLint.ANDROID_USER_DATA_KEY
+ */
+fun get(key: String): String?
+
+// expect
+// 2:36:Line must not end with "."
+// 3:33:Line must not end with "."
+// 5:19:Line must not end with "?:"
+// 7:18:Line must not end with "?."
+// 12:9:Line must not begin with "&&"
+// 14:9:Line must not begin with "&&"
+// 16:9:Line must not begin with "/"
+// 22:9:Line must not begin with "+"
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/max-line-length/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/max-line-length/lint.kt.spec
index c55b697..de3ebd6 100644
--- a/ktlint-ruleset-standard/src/test/resources/spec/max-line-length/lint.kt.spec
+++ b/ktlint-ruleset-standard/src/test/resources/spec/max-line-length/lint.kt.spec
@@ -7,6 +7,10 @@
     println("") // too looooooooooooooooooooooooooooooooooooooooooooooooooooooong
 }
 
+/**
+ * "https://www.google.com/search?q=ktlint&rlz=1C5CHFA_enMD736MD737&oq=ktlint+&aqs=chrome..69i57j69i60l4j69i59.1286j0j4&sourceid=chrome&ie=UTF-8"
+ */
+
 // expect
 // 6:1:Exceeded max line length (80)
 // 7:1:Exceeded max line length (80)
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/no-blank-line-before-rbrace/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/no-blank-line-before-rbrace/lint.kt.spec
index 7113f94..d53a105 100644
--- a/ktlint-ruleset-standard/src/test/resources/spec/no-blank-line-before-rbrace/lint.kt.spec
+++ b/ktlint-ruleset-standard/src/test/resources/spec/no-blank-line-before-rbrace/lint.kt.spec
@@ -16,6 +16,6 @@
 }""")}
 
 // expect
-// 3:1:Needless blank line(s)
-// 6:1:Needless blank line(s)
-// 10:1:Needless blank line(s)
+// 3:1:Unexpected blank line(s) before "}"
+// 6:1:Unexpected blank line(s) before "}"
+// 10:1:Unexpected blank line(s) before "}"
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format-expected.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format-expected.kt.spec
new file mode 100644
index 0000000..8e47d2a
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format-expected.kt.spec
@@ -0,0 +1,12 @@
+@O(name = "--debug", usage = "Turn on debug output")
+fun main() {
+    val v1 = 0 - 1 * 2
+    val v2 = -0 - 1
+    val v3 = v * 2
+    i++
+    val y = +1
+    var x = 1 in 3..4
+    fun <T> fn(): T {}
+    fun <T> List<T>.head() {}
+    fun List<String>.head() {}
+}
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format.kt.spec
new file mode 100644
index 0000000..407afc7
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format.kt.spec
@@ -0,0 +1,12 @@
+@O(name="--debug", usage = "Turn on debug output")
+fun main() {
+    val v1 = 0-1*2
+    val v2 = -0-1
+    val v3 = v*2
+    i++
+    val y = +1
+    var x = 1 in 3..4
+    fun <T>fn(): T {}
+    fun <T>List<T>.head() {}
+    fun List<String>.head() {}
+}
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/lint.kt.spec
new file mode 100644
index 0000000..430456f
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/op-spacing/lint.kt.spec
@@ -0,0 +1,28 @@
+import a.b.*
+fun main() {
+    val v = 0 - 1 * 2
+    val v1 = 0-1*2
+    val v2 = -0 - 1
+    val v3 = v * 2
+    i++
+    val y = +1
+    var x = 1 in 3..4
+    val b = 1 < 2
+    fun(a = true)
+    val res = ArrayList<LintError>()
+    fn(*arrayOfNulls<Any>(0 * 1))
+    val a= ""
+    d *= 1
+    call(*v)
+    open class A<T> {
+        open fun x() {}
+    }
+    class B<T> : A<T>() {
+        override fun x() = super<A>.x()
+    }
+}
+
+// expect
+// 4:15:Missing spacing around "-"
+// 4:17:Missing spacing around "*"
+// 14:10:Missing spacing before "="
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format-expected.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format-expected.kt.spec
new file mode 100644
index 0000000..137f422
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format-expected.kt.spec
@@ -0,0 +1,8 @@
+fun main() {
+  (1..12 step 2).last == 11
+  (1..12 step 2).last == 11
+  (1..12 step 2).last == 11
+
+  (1..12 step 2).last == 11
+  for (i in 1..4) print(i)
+}
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format.kt.spec
new file mode 100644
index 0000000..20136c2
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format.kt.spec
@@ -0,0 +1,8 @@
+fun main() {
+  (1 ..12 step 2).last == 11
+  (1.. 12 step 2).last == 11
+  (1 .. 12 step 2).last == 11
+
+  (1..12 step 2).last == 11
+  for (i in 1..4) print(i)
+}
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/lint.kt.spec
new file mode 100644
index 0000000..b233634
--- /dev/null
+++ b/ktlint-ruleset-standard/src/test/resources/spec/range-spacing/lint.kt.spec
@@ -0,0 +1,13 @@
+fun main() {
+  (1 ..12 step 2).last == 11
+  (1.. 12 step 2).last == 11
+  (1 .. 12 step 2).last == 11
+
+  (1..12 step 2).last == 11
+  for (i in 1..4) print(i)
+}
+
+// expect
+// 2:5:Unexpected spacing before ".."
+// 3:7:Unexpected spacing after ".."
+// 4:6:Unexpected spacing around ".."
diff --git a/ktlint-ruleset-standard/src/test/resources/spec/string-template/lint.kt.spec b/ktlint-ruleset-standard/src/test/resources/spec/string-template/lint.kt.spec
index 3866ac1..20e0990 100644
--- a/ktlint-ruleset-standard/src/test/resources/spec/string-template/lint.kt.spec
+++ b/ktlint-ruleset-standard/src/test/resources/spec/string-template/lint.kt.spec
@@ -22,8 +22,8 @@
 }
 
 // expect
-// 2:29:Redundant 'toString()' call in string template
-// 3:28:Redundant 'toString()' call in string template
+// 2:29:Redundant "toString()" call in string template
+// 3:28:Redundant "toString()" call in string template
 // 6:15:Redundant curly braces
 // 7:15:Redundant curly braces
-// 21:79:Redundant 'toString()' call in string template
+// 21:79:Redundant "toString()" call in string template
diff --git a/ktlint-ruleset-template/build.gradle b/ktlint-ruleset-template/build.gradle
index 209230f..3a7cbce 100644
--- a/ktlint-ruleset-template/build.gradle
+++ b/ktlint-ruleset-template/build.gradle
@@ -1,5 +1,5 @@
 buildscript {
-    ext.kotlin_version = '1.1.51'
+    ext.kotlin_version = '1.2.40'
     repositories {
         mavenCentral()
         maven { url 'http://repo.spring.io/plugins-release' }
@@ -7,7 +7,7 @@
     dependencies {
         classpath 'org.springframework.build.gradle:propdeps-plugin:0.0.7'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
-        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0-M2'
+        classpath 'org.junit.platform:junit-platform-gradle-plugin:1.0.0'
     }
 }
 
@@ -24,7 +24,7 @@
 targetCompatibility = 1.8
 
 repositories {
-    mavenCentral()
+    jcenter()
 }
 
 task sourcesJar(type: Jar, dependsOn: classes) {
@@ -48,14 +48,14 @@
 
 dependencies {
     compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
-    provided 'com.github.shyiko.ktlint:ktlint-core:0.10.0'
+    provided 'com.github.shyiko.ktlint:ktlint-core:0.22.0'
 
-    testCompile 'org.jetbrains.spek:spek-api:1.0.89'
-    testRuntime 'org.jetbrains.spek:spek-junit-platform-engine:1.0.89'
+    testCompile 'org.jetbrains.spek:spek-api:1.1.5'
+    testRuntime 'org.jetbrains.spek:spek-junit-platform-engine:1.1.5'
     testCompile 'org.assertj:assertj-core:3.5.2'
-    testCompile 'com.github.shyiko.ktlint:ktlint-test:0.9.0'
+    testCompile 'com.github.shyiko.ktlint:ktlint-test:0.22.0'
 
-    ktlint 'com.github.shyiko:ktlint:0.10.0'
+    ktlint 'com.github.shyiko:ktlint:0.22.0'
 }
 
 task ktlint(type: JavaExec, dependsOn: classes) {
diff --git a/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt b/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt
index 0c6b81f..9eee6ec 100644
--- a/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt
+++ b/ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt
@@ -8,8 +8,11 @@
 
 class NoVarRule : Rule("no-var") {
 
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) {
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
+    ) {
         if (node is LeafPsiElement && node.textMatches("var") &&
                 getNonStrictParentOfType(node, KtStringTemplateEntry::class.java) == null) {
             emit(node.startOffset, "Unexpected var, use val instead", false)
diff --git a/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt b/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt
index 2692a3e..633977d 100644
--- a/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt
+++ b/ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt
@@ -25,4 +25,3 @@
         }
     }
 })
-
diff --git a/ktlint-test/build.gradle b/ktlint-test/build.gradle
new file mode 100644
index 0000000..c9d2ae3
--- /dev/null
+++ b/ktlint-test/build.gradle
@@ -0,0 +1,9 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+}
+
+dependencies {
+  compile project(':ktlint-core')
+  compile libraries.kotlin_stdlib
+  compile libraries.kolor
+}
diff --git a/ktlint-test/pom.xml b/ktlint-test/pom.xml
index 5a41ded..c212142 100644
--- a/ktlint-test/pom.xml
+++ b/ktlint-test/pom.xml
@@ -24,6 +24,17 @@
             <version>0.0.0-SNAPSHOT</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>com.andreapivetta.kolor</groupId>
+            <artifactId>kolor</artifactId>
+            <version>${kolor.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.jetbrains.kotlin</groupId>
+                    <artifactId>kotlin-stdlib-jre8</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/DumpAST.kt b/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/DumpAST.kt
new file mode 100644
index 0000000..a22223e
--- /dev/null
+++ b/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/DumpAST.kt
@@ -0,0 +1,94 @@
+package com.github.shyiko.ktlint.test
+
+import com.andreapivetta.kolor.Color
+import com.andreapivetta.kolor.Kolor
+import com.github.shyiko.ktlint.core.Rule
+import org.jetbrains.kotlin.com.intellij.lang.ASTNode
+import org.jetbrains.kotlin.com.intellij.openapi.util.TextRange
+import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
+import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
+import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
+import java.io.PrintStream
+
+val debugAST = {
+    (System.getProperty("ktlintDebug") ?: System.getenv("KTLINT_DEBUG") ?: "")
+        .toLowerCase().split(",").contains("ast")
+}
+
+class DumpAST @JvmOverloads constructor(
+    private val out: PrintStream = System.err,
+    private val color: Boolean = false
+) : Rule("dump") {
+
+    private var lineNumberColumnLength: Int = 0
+    private var lastNode: ASTNode? = null
+
+    override fun visit(
+        node: ASTNode,
+        autoCorrect: Boolean,
+        emit: (offset: Int, errorMessage: String, corrected: Boolean) -> Unit
+    ) {
+        if (node.elementType == KtStubElementTypes.FILE) {
+            lineNumberColumnLength = (location(PsiTreeUtil.getDeepestLast(node.psi).node)?.line ?: 1)
+                .let { var v = it; var c = 0; while (v > 0) { c++; v /= 10 }; c }
+            lastNode = lastChildNodeOf(node)
+        }
+        var level = -1
+        var parent: ASTNode? = node
+        do {
+            level++
+            parent = parent?.treeParent
+        } while (parent != null)
+        out.println((
+            location(node)
+                ?.let { String.format("%${lineNumberColumnLength}s: ", it.line).gray() }
+                // should only happen when autoCorrect=true and other rules mutate AST in a way that changes text length
+                ?: String.format("%${lineNumberColumnLength}s: ", "?").gray()
+            ) +
+            "  ".repeat(level).gray() +
+            colorClassName(node.psi.className) +
+            " (".gray() + colorClassName(node.elementType.className) + "." + node.elementType + ")".gray() +
+            if (node.getChildren(null).isEmpty()) " \"" + node.text.escape().yellow() + "\"" else "")
+        if (lastNode == node) {
+            out.println()
+            out.println(" ".repeat(lineNumberColumnLength) +
+                "  format: <line_number:> <node.psi::class> (<node.elementType>) \"<node.text>\"".gray())
+            out.println(" ".repeat(lineNumberColumnLength) +
+                "  legend: ~ = org.jetbrains.kotlin, c.i.p = com.intellij.psi".gray())
+            out.println()
+        }
+    }
+
+    private tailrec fun lastChildNodeOf(node: ASTNode): ASTNode? =
+        if (node.lastChildNode == null) node else lastChildNodeOf(node.lastChildNode)
+
+    private fun location(node: ASTNode) =
+        node.psi.containingFile?.let { psiFile ->
+            try {
+                DiagnosticUtils.getLineAndColumnInPsiFile(
+                    psiFile,
+                    TextRange(node.startOffset, node.startOffset)
+                )
+            } catch (e: Exception) {
+                null // DiagnosticUtils has no knowledge of mutated AST
+            }
+        }
+
+    private fun colorClassName(className: String): String {
+        val name = className.substringAfterLast(".")
+        return className.substring(0, className.length - name.length).gray() + name
+    }
+
+    private fun String.yellow() =
+        if (color) Kolor.foreground(this, Color.YELLOW) else this
+    private fun String.gray() =
+        if (color) Kolor.foreground(this, Color.DARK_GRAY) else this
+
+    private val Any.className
+        get() = this.javaClass.name
+            .replace("org.jetbrains.kotlin.", "~.")
+            .replace("com.intellij.psi.", "c.i.p.")
+
+    private fun String.escape() =
+        this.replace("\\", "\\\\").replace("\n", "\\n").replace("\t", "\\t").replace("\r", "\\r")
+}
diff --git a/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/RuleExtension.kt b/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/RuleExtension.kt
index 2911790..3914705 100644
--- a/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/RuleExtension.kt
+++ b/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/RuleExtension.kt
@@ -6,10 +6,11 @@
 import com.github.shyiko.ktlint.core.RuleSet
 import java.util.ArrayList
 
-fun Rule.lint(text: String, userData: Map<String, String> = emptyMap()): List<LintError> {
+fun Rule.lint(text: String, userData: Map<String, String> = emptyMap(), script: Boolean = false): List<LintError> {
     val res = ArrayList<LintError>()
     val debug = debugAST()
-    KtLint.lint(text, (if (debug) listOf(RuleSet("debug", DumpAST())) else emptyList()) +
+    val f: L = if (script) KtLint::lintScript else KtLint::lint
+    f(text, (if (debug) listOf(RuleSet("debug", DumpAST())) else emptyList()) +
             listOf(RuleSet("standard", this@lint)), userData) { e ->
         if (debug) {
             System.err.println("^^ lint error")
@@ -19,10 +20,27 @@
     return res
 }
 
+private typealias L = (
+    text: String,
+    ruleSets: Iterable<RuleSet>,
+    userData: Map<String, String>,
+    cb: (e: LintError) -> Unit
+) -> Unit
+
 fun Rule.format(
     text: String,
     userData: Map<String, String> = emptyMap(),
-    cb: (e: LintError, corrected: Boolean) -> Unit = { _, _ -> }
-): String =
-    KtLint.format(text, (if (debugAST()) listOf(RuleSet("debug", DumpAST())) else emptyList()) +
+    cb: (e: LintError, corrected: Boolean) -> Unit = { _, _ -> },
+    script: Boolean = false
+): String {
+    val f: F = if (script) KtLint::formatScript else KtLint::format
+    return f(text, (if (debugAST()) listOf(RuleSet("debug", DumpAST())) else emptyList()) +
         listOf(RuleSet("standard", this@format)), userData, cb)
+}
+
+private typealias F = (
+    text: String,
+    ruleSets: Iterable<RuleSet>,
+    userData: Map<String, String>,
+    cb: (e: LintError, corrected: Boolean) -> Unit
+) -> String
diff --git a/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/package.kt b/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/package.kt
deleted file mode 100644
index a3c6b83..0000000
--- a/ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/package.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.github.shyiko.ktlint.test
-
-import com.github.shyiko.ktlint.core.Rule
-import org.jetbrains.kotlin.com.intellij.lang.ASTNode
-
-val debugAST = {
-    (System.getProperty("ktlintDebug") ?: System.getenv("KTLINT_DEBUG") ?: "")
-        .toLowerCase().split(",").contains("ast")
-}
-
-class DumpAST : Rule("dump") {
-
-    override fun visit(node: ASTNode, autoCorrect: Boolean,
-        emit: (offset: Int, errorMessage: String, corrected: Boolean) -> Unit) {
-        var level = -1
-        var parent: ASTNode? = node
-        do {
-            level++
-            parent = parent?.treeParent
-        } while (parent != null)
-        System.err.println("  ".repeat(level) + node.psi.javaClass.name + " (${node.elementType})" +
-            (if (node.getChildren(null).isEmpty()) " | \"" + node.text.escape() + "\"" else ""))
-    }
-
-    private fun String.escape() =
-        this.replace("\\", "\\\\").replace("\n", "\\n").replace("\t", "\\t").replace("\r", "\\r")
-}
diff --git a/ktlint/build.gradle b/ktlint/build.gradle
new file mode 100644
index 0000000..c5fd4c2
--- /dev/null
+++ b/ktlint/build.gradle
@@ -0,0 +1,42 @@
+plugins {
+  id "org.jetbrains.kotlin.jvm"
+  id "application"
+  // applied after mainClassName per https://github.com/johnrengelman/shadow/issues/336
+  id "com.github.johnrengelman.shadow" version "2.0.2" apply false
+}
+
+mainClassName = 'com.github.shyiko.ktlint.Main'
+apply plugin: 'com.github.johnrengelman.shadow'
+
+dependencies {
+  compile project(":ktlint-core")
+  compile project(":ktlint-ruleset-standard")
+  compile project(":ktlint-reporter-plain")
+  compile project(":ktlint-reporter-json")
+  compile project(":ktlint-reporter-checkstyle")
+  compile libraries.kotlin_stdlib
+  compile libraries.klob
+  compile libraries.aether_api
+  compile libraries.aether_spi
+  compile libraries.aether_util
+  compile libraries.aether_impl
+  compile libraries.aether_connector_basic
+  compile libraries.aether_transport_file
+  compile libraries.aether_transport_http
+  compile libraries.slf4j_nop
+  compile libraries.maven_aether_provider
+  compile libraries.picocli
+
+  testCompile libraries.testng
+  testCompile libraries.assertj_core
+  testCompile libraries.jimfs
+}
+
+compileKotlin {
+  sourceCompatibility = JavaVersion.VERSION_1_8
+  targetCompatibility = JavaVersion.VERSION_1_8
+
+  kotlinOptions {
+    jvmTarget = "1.8"
+  }
+}
diff --git a/ktlint/pom.xml b/ktlint/pom.xml
index a0f9509..9b86761 100644
--- a/ktlint/pom.xml
+++ b/ktlint/pom.xml
@@ -24,7 +24,15 @@
             <groupId>org.jetbrains.kotlin</groupId>
             <artifactId>kotlin-stdlib</artifactId>
             <version>${kotlin.version}</version>
+            <!-- no longer included in kotlin-compiler-embeddable (>=1.1.60) -->
+            <!--<scope>provided</scope>-->
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>13.0</version>
             <scope>provided</scope>
+            <!-- included (at least org.jetbrains.annotations.*) in kotlin-compiler-embeddable -->
         </dependency>
         <dependency>
             <groupId>com.github.shyiko.ktlint</groupId>
@@ -52,16 +60,16 @@
             <version>0.0.0-SNAPSHOT</version>
         </dependency>
         <dependency>
+            <groupId>com.github.shyiko.ktlint</groupId>
+            <artifactId>ktlint-test</artifactId>
+            <version>0.0.0-SNAPSHOT</version>
+        </dependency>
+        <dependency>
             <groupId>com.github.shyiko.klob</groupId>
             <artifactId>klob</artifactId>
             <version>0.2.0</version>
         </dependency>
         <dependency>
-            <groupId>org.ini4j</groupId>
-            <artifactId>ini4j</artifactId>
-            <version>0.5.4</version>
-        </dependency>
-        <dependency>
             <groupId>org.apache.maven</groupId>
             <artifactId>maven-aether-provider</artifactId>
             <version>${aether.maven.provider.version}</version>
@@ -86,6 +94,21 @@
         </dependency>
         <!-- maven-aether-provider's transitive dependency -->
         <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-model</artifactId>
+            <version>${aether.maven.provider.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-model-builder</artifactId>
+            <version>${aether.maven.provider.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-repository-metadata</artifactId>
+            <version>${aether.maven.provider.version}</version>
+        </dependency>
+        <dependency>
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
             <version>18.0</version>
@@ -132,9 +155,9 @@
             <version>1.6.2</version>
         </dependency>
         <dependency>
-            <groupId>args4j</groupId>
-            <artifactId>args4j</artifactId>
-            <version>${args4j.version}</version>
+            <groupId>info.picocli</groupId>
+            <artifactId>picocli</artifactId>
+            <version>${picocli.version}</version>
         </dependency>
         <dependency>
             <groupId>org.testng</groupId>
@@ -148,6 +171,12 @@
             <version>${assertj.version}</version>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.jimfs</groupId>
+            <artifactId>jimfs</artifactId>
+            <version>${jimfs.version}</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -157,7 +186,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-antrun-plugin</artifactId>
-                <version>1.7</version>
+                <version>1.8</version>
                 <executions>
                     <execution>
                         <id>ktlint</id>
@@ -259,7 +288,7 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-shade-plugin</artifactId>
-                        <version>2.4.3</version>
+                        <version>3.1.0</version>
                         <executions>
                             <execution>
                                 <phase>package</phase>
@@ -335,12 +364,13 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-antrun-plugin</artifactId>
-                        <version>1.7</version>
+                        <version>1.8</version>
                         <executions>
                             <execution>
                                 <id>ktlint</id>
                                 <phase>verify</phase>
                                 <configuration>
+                                    <skip>${gpg.skip}</skip>
                                     <target name="gpg-sign">
                                         <exec executable="gpg" dir="${basedir}" failonerror="true">
                                             <arg value="-ab"/>
@@ -357,12 +387,13 @@
                     <plugin>
                         <groupId>de.jutzig</groupId>
                         <artifactId>github-release-plugin</artifactId>
-                        <version>1.1.1</version>
+                        <version>1.2.0</version>
                         <configuration>
-                            <description>${project.version}</description>
+                            <description>${github.description}</description>
                             <releaseName>${project.version}</releaseName>
                             <tag>${project.version}</tag>
                             <artifact>${project.build.directory}/${project.artifactId}</artifact>
+                            <overwriteArtifact>true</overwriteArtifact>
                             <fileSets>
                                 <fileSet>
                                     <directory>${project.build.directory}</directory>
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
index ce80563..9541cdc 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt
@@ -12,6 +12,7 @@
 import com.github.shyiko.ktlint.internal.EditorConfig
 import com.github.shyiko.ktlint.internal.IntellijIDEAIntegration
 import com.github.shyiko.ktlint.internal.MavenDependencyResolver
+import com.github.shyiko.ktlint.test.DumpAST
 import org.eclipse.aether.RepositoryException
 import org.eclipse.aether.artifact.DefaultArtifact
 import org.eclipse.aether.repository.RemoteRepository
@@ -19,19 +20,14 @@
 import org.eclipse.aether.repository.RepositoryPolicy.CHECKSUM_POLICY_IGNORE
 import org.eclipse.aether.repository.RepositoryPolicy.UPDATE_POLICY_NEVER
 import org.jetbrains.kotlin.preprocessor.mkdirsOrFail
-import org.kohsuke.args4j.Argument
-import org.kohsuke.args4j.CmdLineException
-import org.kohsuke.args4j.CmdLineParser
-import org.kohsuke.args4j.NamedOptionDef
-import org.kohsuke.args4j.Option
-import org.kohsuke.args4j.OptionHandlerFilter
-import org.kohsuke.args4j.ParserProperties
-import org.kohsuke.args4j.spi.OptionHandler
+import picocli.CommandLine
+import picocli.CommandLine.Command
+import picocli.CommandLine.Option
+import picocli.CommandLine.Parameters
 import java.io.ByteArrayOutputStream
 import java.io.File
 import java.io.FileNotFoundException
 import java.io.PrintStream
-import java.io.PrintWriter
 import java.math.BigInteger
 import java.net.URLDecoder
 import java.nio.file.Path
@@ -39,8 +35,8 @@
 import java.security.MessageDigest
 import java.util.ArrayList
 import java.util.Arrays
+import java.util.LinkedHashMap
 import java.util.NoSuchElementException
-import java.util.ResourceBundle
 import java.util.Scanner
 import java.util.ServiceLoader
 import java.util.concurrent.ArrayBlockingQueue
@@ -50,8 +46,41 @@
 import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicBoolean
 import java.util.concurrent.atomic.AtomicInteger
+import java.util.jar.Manifest
 import kotlin.system.exitProcess
 
+@Command(
+    headerHeading = """An anti-bikeshedding Kotlin linter with built-in formatter
+(https://github.com/shyiko/ktlint).
+
+Usage:
+  ktlint <flags> [patterns]
+  java -jar ktlint <flags> [patterns]
+
+Examples:
+  # check the style of all Kotlin files inside the current dir (recursively)
+  # (hidden folders will be skipped)
+  ktlint
+
+  # check only certain locations (prepend ! to negate the pattern)
+  ktlint "src/**/*.kt" "!src/**/*Test.kt"
+
+  # auto-correct style violations
+  ktlint -F "src/**/*.kt"
+
+  # custom reporter
+  ktlint --reporter=plain?group_by_file
+  # multiple reporters can be specified like this
+  ktlint --reporter=plain \
+    --reporter=checkstyle,output=ktlint-checkstyle-report.xml
+  # 3rd-party reporter
+  ktlint --reporter=html,artifact=com.gihub.user:repo:master-SNAPSHOT
+
+Flags:""",
+    synopsisHeading = "",
+    customSynopsis = arrayOf(""),
+    sortOptions = false
+)
 object Main {
 
     private val DEPRECATED_FLAGS = mapOf(
@@ -64,103 +93,127 @@
         "--reporter-update" to
             "--repository-update"
     )
-    private val CLI_MAX_LINE_LENGTH_REGEX = Regex("(.{0,120})(?:\\s|$)")
-
-    // todo: this should have been a command, not a flag (consider changing in 1.0.0)
-    @Option(name="--format", aliases = arrayOf("-F"), usage = "Fix any deviations from the code style")
-    private var format: Boolean = false
-
-    @Option(name="--android", aliases = arrayOf("-a"), usage = "Turn on Android Kotlin Style Guide compatibility")
+    @Option(names = arrayOf("--android", "-a"), description = arrayOf("Turn on Android Kotlin Style Guide compatibility"))
     private var android: Boolean = false
 
-    @Option(name="--reporter",
-        usage = "A reporter to use (built-in: plain (default), plain?group_by_file, json, checkstyle). " +
-            "To use a third-party reporter specify either a path to a JAR file on the filesystem or a" +
-            "<groupId>:<artifactId>:<version> triple pointing to a remote artifact (in which case ktlint will first " +
-            "check local cache (~/.m2/repository) and then, if not found, attempt downloading it from " +
-            "Maven Central/JCenter/JitPack/user-provided repository)")
-    private var reporters = ArrayList<String>()
-
-    @Option(name="--ruleset", aliases = arrayOf("-R"),
-        usage = "A path to a JAR file containing additional ruleset(s) or a " +
-            "<groupId>:<artifactId>:<version> triple pointing to a remote artifact (in which case ktlint will first " +
-            "check local cache (~/.m2/repository) and then, if not found, attempt downloading it from " +
-            "Maven Central/JCenter/JitPack/user-provided repository)")
-    private var rulesets = ArrayList<String>()
-
-    @Option(name="--repository", aliases = arrayOf("--ruleset-repository", "--reporter-repository"),
-        usage = "An additional Maven repository (Maven Central/JCenter/JitPack are active by default) " +
-            "(value format: <id>=<url>)")
-    private var repositories = ArrayList<String>()
-
-    @Option(name="--repository-update", aliases = arrayOf("-U", "--ruleset-update", "--reporter-update"),
-        usage = "Check remote repositories for updated snapshots")
-    private var forceUpdate: Boolean = false
-
-    @Option(name="--limit", usage = "Maximum number of errors to show (default: show all)")
-    private var limit: Int = -1
-
-    @Option(name="--relative", usage = "Print files relative to the working directory " +
-        "(e.g. dir/file.kt instead of /home/user/project/dir/file.kt)")
-    private var relative: Boolean = false
-
-    @Option(name="--verbose", aliases = arrayOf("-v"), usage = "Show error codes")
-    private var verbose: Boolean = false
-
-    @Option(name="--stdin", usage = "Read file from stdin")
-    private var stdin: Boolean = false
-
-    @Option(name="--version", usage = "Version", help = true)
-    private var version: Boolean = false
-
-    @Option(name="--help", aliases = arrayOf("-h"), help = true)
-    private var help: Boolean = false
-
-    @Option(name="--debug", usage = "Turn on debug output")
-    private var debug: Boolean = false
+    // todo: make it a command in 1.0.0 (it's too late now as we might interfere with valid "lint" patterns)
+    @Option(names = arrayOf("--apply-to-idea"), description = arrayOf("Update Intellij IDEA settings (global)"))
+    private var apply: Boolean = false
 
     // todo: make it a command in 1.0.0 (it's too late now as we might interfere with valid "lint" patterns)
-    @Option(name="--apply-to-idea", usage = "Update Intellij IDEA project settings")
-    private var apply: Boolean = false
-    @Option(name="--install-git-pre-commit-hook", usage = "Install git hook to automatically check files for style violations on commit")
+    @Option(names = arrayOf("--apply-to-idea-project"), description = arrayOf("Update Intellij IDEA project settings"))
+    private var applyToProject: Boolean = false
+
+    @Option(names = arrayOf("--color"), description = arrayOf("Make output colorful"))
+    private var color: Boolean = false
+
+    @Option(names = arrayOf("--debug"), description = arrayOf("Turn on debug output"))
+    private var debug: Boolean = false
+
+    // todo: this should have been a command, not a flag (consider changing in 1.0.0)
+    @Option(names = arrayOf("--format", "-F"), description = arrayOf("Fix any deviations from the code style"))
+    private var format: Boolean = false
+
+    @Option(names = arrayOf("--install-git-pre-commit-hook"), description = arrayOf(
+        "Install git hook to automatically check files for style violations on commit"
+    ))
     private var installGitPreCommitHook: Boolean = false
-    @Option(name="-y", hidden = true)
+
+    @Option(names = arrayOf("--limit"), description = arrayOf(
+        "Maximum number of errors to show (default: show all)"
+    ))
+    private var limit: Int = -1
+        get() = if (field < 0) Int.MAX_VALUE else field
+
+    @Option(names = arrayOf("--print-ast"), description = arrayOf(
+        "Print AST (useful when writing/debugging rules)"
+    ))
+    private var printAST: Boolean = false
+
+    @Option(names = arrayOf("--relative"), description = arrayOf(
+        "Print files relative to the working directory " +
+            "(e.g. dir/file.kt instead of /home/user/project/dir/file.kt)"
+    ))
+    private var relative: Boolean = false
+
+    @Option(names = arrayOf("--reporter"), description = arrayOf(
+        "A reporter to use (built-in: plain (default), plain?group_by_file, json, checkstyle). " +
+        "To use a third-party reporter specify either a path to a JAR file on the filesystem or a" +
+        "<groupId>:<artifactId>:<version> triple pointing to a remote artifact (in which case ktlint will first " +
+        "check local cache (~/.m2/repository) and then, if not found, attempt downloading it from " +
+        "Maven Central/JCenter/JitPack/user-provided repository)\n" +
+        "e.g. \"html,artifact=com.github.username:ktlint-reporter-html:master-SNAPSHOT\""
+    ))
+    private var reporters = ArrayList<String>()
+
+    @Option(names = arrayOf("--repository"), description = arrayOf(
+        "An additional Maven repository (Maven Central/JCenter/JitPack are active by default) " +
+            "(value format: <id>=<url>)"
+    ))
+    private var repositories = ArrayList<String>()
+    @Option(names = arrayOf("--ruleset-repository", "--reporter-repository"), hidden = true)
+    private var repositoriesDeprecated = ArrayList<String>()
+
+    @Option(names = arrayOf("--repository-update", "-U"), description = arrayOf(
+        "Check remote repositories for updated snapshots"
+    ))
+    private var forceUpdate: Boolean? = null
+    @Option(names = arrayOf("--ruleset-update", "--reporter-update"), hidden = true)
+    private var forceUpdateDeprecated: Boolean? = null
+
+    @Option(names = arrayOf("--ruleset", "-R"), description = arrayOf(
+        "A path to a JAR file containing additional ruleset(s) or a " +
+        "<groupId>:<artifactId>:<version> triple pointing to a remote artifact (in which case ktlint will first " +
+        "check local cache (~/.m2/repository) and then, if not found, attempt downloading it from " +
+        "Maven Central/JCenter/JitPack/user-provided repository)"
+    ))
+    private var rulesets = ArrayList<String>()
+
+    @Option(names = arrayOf("--skip-classpath-check"), description = arrayOf("Do not check classpath for pottential conflicts"))
+    private var skipClasspathCheck: Boolean = false
+
+    @Option(names = arrayOf("--stdin"), description = arrayOf("Read file from stdin"))
+    private var stdin: Boolean = false
+
+    @Option(names = arrayOf("--verbose", "-v"), description = arrayOf("Show error codes"))
+    private var verbose: Boolean = false
+
+    @Option(names = arrayOf("--version"), description = arrayOf("Print version information"))
+    private var version: Boolean = false
+
+    @Option(names = arrayOf("--help", "-h"), help = true, hidden = true)
+    private var help: Boolean = false
+
+    @Option(names = arrayOf("-y"), hidden = true)
     private var forceApply: Boolean = false
 
-    @Argument
+    @Parameters(hidden = true)
     private var patterns = ArrayList<String>()
 
-    private fun CmdLineParser.usage(): String =
-        """
-        An anti-bikeshedding Kotlin linter with built-in formatter (https://github.com/shyiko/ktlint).
+    private val workDir = File(".").canonicalPath
+    private fun File.location() = if (relative) this.toRelativeString(File(workDir)) else this.path
 
-        Usage:
-          ktlint <flags> [patterns]
-          java -jar ktlint <flags> [patterns]
+    private fun usage() =
+        ByteArrayOutputStream()
+            .also { CommandLine.usage(this, PrintStream(it), CommandLine.Help.Ansi.OFF) }
+            .toString()
+            .replace(" ".repeat(32), " ".repeat(30))
 
-        Examples:
-          # check the style of all Kotlin files inside the current dir (recursively)
-          # (hidden folders will be skipped)
-          ktlint
-
-          # check only certain locations (prepend ! to negate the pattern)
-          ktlint "src/**/*.kt" "!src/**/*Test.kt"
-
-          # auto-correct style violations
-          ktlint -F "src/**/*.kt"
-
-          # use custom reporter
-          ktlint --reporter=plain?group_by_file
-          # multiple reporters can be specified like this
-          ktlint --reporter=plain --reporter=checkstyle,output=ktlint-checkstyle-report.xml
-
-        Flags:
-${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().split("\n")
-            .map { line -> ("  " + line).replace(CLI_MAX_LINE_LENGTH_REGEX, "        $1\n").trimEnd() }
-            .joinToString("\n")}
-        """.trimIndent()
-
-    fun parseCmdLine(args: Array<String>) {
+    private fun parseCmdLine(args: Array<String>) {
+        try {
+            CommandLine.populateCommand(this, *args)
+            repositories.addAll(repositoriesDeprecated)
+            if (forceUpdateDeprecated != null && forceUpdate == null) {
+                forceUpdate = forceUpdateDeprecated
+            }
+        } catch (e: Exception) {
+            System.err.println("Error: ${e.message}\n\n${usage()}")
+            exitProcess(1)
+        }
+        if (help) {
+            println(usage())
+            exitProcess(0)
+        }
         args.forEach { arg ->
             if (arg.startsWith("--") && arg.contains("=")) {
                 val flag = arg.substringBefore("=")
@@ -170,43 +223,13 @@
                 }
             }
         }
-        val parser = object : CmdLineParser(this, ParserProperties.defaults()
-            .withShowDefaults(false)
-            .withUsageWidth(512)
-            .withOptionSorter { l, r ->
-                l.option.toString().replace("-", "").compareTo(r.option.toString().replace("-", ""))
-            }) {
-
-            override fun printOption(out: PrintWriter, handler: OptionHandler<*>, len: Int, rb: ResourceBundle?, filter: OptionHandlerFilter?) {
-                handler.defaultMetaVariable
-                val opt = handler.option as? NamedOptionDef ?: return
-                if (opt.hidden() || opt.help()) {
-                    return
-                }
-                val maxNameLength = options.map { h ->
-                    val o = h.option
-                    (o as? NamedOptionDef)?.let { it.name().length + 1 + (h.defaultMetaVariable ?: "").length } ?: 0
-                }.max()!!
-                val shorthand = opt.aliases().find { it.startsWith("-") && !it.startsWith("--") }
-                val line = (if (shorthand != null) "$shorthand, " else "    ") +
-                    (opt.name() + " " + (handler.defaultMetaVariable ?: "")).padEnd(maxNameLength, ' ') + "  " + opt.usage()
-                out.println(line)
-            }
-        }
-        try {
-            parser.parseArgument(*args)
-        } catch (err: CmdLineException) {
-            System.err.println("Error: ${err.message}\n\n${parser.usage()}")
-            exitProcess(1)
-        }
-        if (help) { println(parser.usage()); exitProcess(0) }
     }
 
     @JvmStatic
     fun main(args: Array<String>) {
         parseCmdLine(args)
         if (version) {
-            println(javaClass.`package`.implementationVersion)
+            println(getImplementationVersion())
             exitProcess(0)
         }
         if (installGitPreCommitHook) {
@@ -215,116 +238,59 @@
                 exitProcess(0)
             }
         }
-        if (apply) {
+        if (apply || applyToProject) {
             applyToIDEA()
             exitProcess(0)
         }
-        val workDir = File(".").canonicalPath
+        if (printAST) {
+            printAST()
+            exitProcess(0)
+        }
         val start = System.currentTimeMillis()
         // load 3rd party ruleset(s) (if any)
-        val dependencyResolver by lazy { buildDependencyResolver() }
+        val dependencyResolver = lazy(LazyThreadSafetyMode.NONE) { buildDependencyResolver() }
         if (!rulesets.isEmpty()) {
             loadJARs(dependencyResolver, rulesets)
         }
         // standard should go first
-        val rp = ServiceLoader.load(RuleSetProvider::class.java)
+        val ruleSetProviders = ServiceLoader.load(RuleSetProvider::class.java)
             .map { it.get().id to it }
             .sortedBy { if (it.first == "standard") "\u0000${it.first}" else it.first }
         if (debug) {
-            rp.forEach { System.err.println("[DEBUG] Discovered ruleset \"${it.first}\"") }
+            ruleSetProviders.forEach { System.err.println("[DEBUG] Discovered ruleset \"${it.first}\"") }
         }
-        data class R(val id: String, val config: Map<String, String>, var output: String?)
-        if (reporters.isEmpty()) {
-            reporters.add("plain")
-        }
-        val rr = this.reporters.map { reporter ->
-            val split = reporter.split(",")
-            val (reporterId, rawReporterConfig) = split[0].split("?", limit = 2) + listOf("")
-            R(reporterId, mapOf("verbose" to verbose.toString()) + parseQuery(rawReporterConfig),
-                split.getOrNull(1)?.let { if (it.startsWith("output=")) it.split("=")[1] else null })
-        }.distinct()
-        // load reporter
-        val reporterLoader = ServiceLoader.load(ReporterProvider::class.java)
-        val reporterProviderById = reporterLoader.associate { it.id to it }.let { map ->
-            val missingReporters = rr.map { it.id }.distinct().filter { !map.containsKey(it) }
-            if (!missingReporters.isEmpty()) {
-                loadJARs(dependencyResolver, missingReporters)
-                reporterLoader.reload()
-                reporterLoader.associate { it.id to it }
-            } else map
-        }
-        if (debug) {
-            reporterProviderById.forEach { (id) -> System.err.println("[DEBUG] Discovered reporter \"$id\"") }
-        }
-        val reporter = Reporter.from(*rr.map { r ->
-            val reporterProvider = reporterProviderById[r.id]
-            if (reporterProvider == null) {
-                System.err.println("Error: reporter \"${r.id}\" wasn't found (available: ${
-                    reporterProviderById.keys.sorted().joinToString(",")})")
-                exitProcess(1)
-            }
-            if (debug) {
-                System.err.println("[DEBUG] Initializing \"${r.id}\" reporter with ${r.config}" +
-                    (r.output?.let { ", output=$it" } ?: ""))
-            }
-            val output = if (r.output != null) { File(r.output).parentFile?.mkdirsOrFail(); PrintStream(r.output) } else
-                if (stdin) System.err else System.out
-            reporterProvider.get(output, r.config).let { reporter ->
-                if (r.output != null)
-                    object : Reporter by reporter {
-                        override fun afterAll() {
-                            reporter.afterAll()
-                            output.close()
-                        }
-                    }
-                else
-                    reporter
-            }
-        }.toTypedArray())
+        val reporter = loadReporter(dependencyResolver)
         // load .editorconfig
         val userData = (
             EditorConfig.of(workDir)
                 ?.also { editorConfig ->
                     if (debug) {
-                        System.err.println("[DEBUG] Discovered .editorconfig (${editorConfig.path.parent})")
+                        System.err.println("[DEBUG] Discovered .editorconfig (${
+                            generateSequence(editorConfig) { it.parent }.map { it.path.parent.toFile().location() }.joinToString()
+                        })")
                         System.err.println("[DEBUG] ${editorConfig.mapKeys { it.key }} loaded from .editorconfig")
                     }
                 }
                 ?: emptyMap<String, String>()
             ) + mapOf("android" to android.toString())
-        data class LintErrorWithCorrectionInfo(val err: LintError, val corrected: Boolean)
-        fun lintErrorFrom(e: Exception): LintError = when (e) {
-            is ParseException ->
-                LintError(e.line, e.col, "",
-                    "Not a valid Kotlin file (${e.message?.toLowerCase()})")
-            is RuleExecutionException -> {
-                if (debug) {
-                    System.err.println("[DEBUG] Internal Error (${e.ruleId})")
-                    e.printStackTrace(System.err)
-                }
-                LintError(e.line, e.col, "", "Internal Error (${e.ruleId}). " +
-                    "Please create a ticket at https://github.com/shyiko/ktlint/issue " +
-                    "(if possible, provide the source code that triggered an error)")
-            }
-            else -> throw e
-        }
         val tripped = AtomicBoolean()
+        data class LintErrorWithCorrectionInfo(val err: LintError, val corrected: Boolean)
         fun process(fileName: String, fileContent: String): List<LintErrorWithCorrectionInfo> {
             if (debug) {
-                System.err.println("[DEBUG] Checking ${
-                    if (relative && fileName != "<text>") File(fileName).toRelativeString(File(workDir)) else fileName
-                }")
+                System.err.println("[DEBUG] Checking ${if (fileName != "<text>") File(fileName).location() else fileName}")
             }
             val result = ArrayList<LintErrorWithCorrectionInfo>()
+            val localUserData = if (fileName != "<text>") userData + ("file_path" to fileName) else userData
             if (format) {
                 val formattedFileContent = try {
-                    format(fileName, fileContent, rp.map { it.second.get() }, userData) { err, corrected ->
+                    format(fileName, fileContent, ruleSetProviders.map { it.second.get() }, localUserData) { err, corrected ->
                         if (!corrected) {
                             result.add(LintErrorWithCorrectionInfo(err, corrected))
+                            tripped.set(true)
                         }
                     }
                 } catch (e: Exception) {
-                    result.add(LintErrorWithCorrectionInfo(lintErrorFrom(e), false))
+                    result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
                     tripped.set(true)
                     fileContent // making sure `cat file | ktlint --stdint > file` is (relatively) safe
                 }
@@ -337,19 +303,17 @@
                 }
             } else {
                 try {
-                    lint(fileName, fileContent, rp.map { it.second.get() }, userData) { err ->
-                        tripped.set(true)
+                    lint(fileName, fileContent, ruleSetProviders.map { it.second.get() }, localUserData) { err ->
                         result.add(LintErrorWithCorrectionInfo(err, false))
+                        tripped.set(true)
                     }
                 } catch (e: Exception) {
-                    result.add(LintErrorWithCorrectionInfo(lintErrorFrom(e), false))
+                    result.add(LintErrorWithCorrectionInfo(e.toLintError(), false))
+                    tripped.set(true)
                 }
             }
             return result
         }
-        if (limit < 0) {
-            limit = Int.MAX_VALUE
-        }
         val (fileNumber, errorNumber) = Pair(AtomicInteger(), AtomicInteger())
         fun report(fileName: String, errList: List<LintErrorWithCorrectionInfo>) {
             fileNumber.incrementAndGet()
@@ -365,35 +329,141 @@
         if (stdin) {
             report("<text>", process("<text>", String(System.`in`.readBytes())))
         } else {
-            val pathIterator = when {
-                patterns.isEmpty() ->
-                    Glob.from("**/*.kt", "**/*.kts")
-                        .iterate(Paths.get(workDir), Glob.IterationOption.SKIP_HIDDEN)
-                else ->
-                    Glob.from(*patterns.map { expandTilde(it) }.toTypedArray())
-                        .iterate(Paths.get(workDir))
-            }
-            pathIterator
-                .asSequence()
+            fileSequence()
                 .takeWhile { errorNumber.get() < limit }
-                .map(Path::toFile)
-                .map { file ->
-                    Callable { file to process(file.path, file.readText()) }
-                }
-                .parallel({ (file, errList) ->
-                    report(if (relative) file.toRelativeString(File(workDir)) else file.path, errList) })
+                .map { file -> Callable { file to process(file.path, file.readText()) } }
+                .parallel({ (file, errList) -> report(file.location(), errList) })
         }
         reporter.afterAll()
         if (debug) {
-            System.err.println("[DEBUG] ${(System.currentTimeMillis() - start)
-                }ms / $fileNumber file(s) / $errorNumber error(s)")
+            System.err.println("[DEBUG] ${
+                System.currentTimeMillis() - start
+            }ms / $fileNumber file(s) / $errorNumber error(s)")
         }
         if (tripped.get()) {
             exitProcess(1)
         }
     }
 
-    fun installGitPreCommitHook() {
+    private fun getImplementationVersion() = javaClass.`package`.implementationVersion
+        // JDK 9 regression workaround (https://bugs.openjdk.java.net/browse/JDK-8190987, fixed in JDK 10)
+        // (note that version reported by the fallback might not be null if META-INF/MANIFEST.MF is
+        // loaded from another JAR on the classpath (e.g. if META-INF/MANIFEST.MF wasn't created as part of the build))
+        ?: javaClass.getResourceAsStream("/META-INF/MANIFEST.MF")
+            ?.let { stream ->
+                Manifest(stream).mainAttributes.getValue("Implementation-Version")
+            }
+
+    private fun loadReporter(dependencyResolver: Lazy<MavenDependencyResolver>): Reporter {
+        data class ReporterTemplate(val id: String, val artifact: String?, val config: Map<String, String>, var output: String?)
+        val tpls = (if (reporters.isEmpty()) listOf("plain") else reporters)
+            .map { reporter ->
+                val split = reporter.split(",")
+                val (reporterId, rawReporterConfig) = split[0].split("?", limit = 2) + listOf("")
+                ReporterTemplate(
+                    reporterId,
+                    split.lastOrNull { it.startsWith("artifact=") }?.let { it.split("=")[1] },
+                    mapOf("verbose" to verbose.toString(), "color" to color.toString()) + parseQuery(rawReporterConfig),
+                    split.lastOrNull { it.startsWith("output=") }?.let { it.split("=")[1] }
+                )
+            }
+            .distinct()
+        val reporterLoader = ServiceLoader.load(ReporterProvider::class.java)
+        val reporterProviderById = reporterLoader.associate { it.id to it }.let { map ->
+            val missingReporters = tpls.filter { !map.containsKey(it.id) }.mapNotNull { it.artifact }.distinct()
+            if (!missingReporters.isEmpty()) {
+                loadJARs(dependencyResolver, missingReporters)
+                reporterLoader.reload()
+                reporterLoader.associate { it.id to it }
+            } else map
+        }
+        if (debug) {
+            reporterProviderById.forEach { (id) -> System.err.println("[DEBUG] Discovered reporter \"$id\"") }
+        }
+        fun ReporterTemplate.toReporter(): Reporter {
+            val reporterProvider = reporterProviderById[id]
+            if (reporterProvider == null) {
+                System.err.println("Error: reporter \"$id\" wasn't found (available: ${
+                    reporterProviderById.keys.sorted().joinToString(",")
+                })")
+                exitProcess(1)
+            }
+            if (debug) {
+                System.err.println("[DEBUG] Initializing \"$id\" reporter with $config" +
+                    (output?.let { ", output=$it" } ?: ""))
+            }
+            val stream = if (output != null) {
+                File(output).parentFile?.mkdirsOrFail(); PrintStream(output, "UTF-8")
+            } else if (stdin) System.err else System.out
+            return reporterProvider.get(stream, config)
+                .let { reporter ->
+                    if (output != null)
+                        object : Reporter by reporter {
+                            override fun afterAll() {
+                                reporter.afterAll()
+                                stream.close()
+                            }
+                        }
+                    else reporter
+                }
+        }
+        return Reporter.from(*tpls.map { it.toReporter() }.toTypedArray())
+    }
+
+    private fun Exception.toLintError(): LintError = this.let { e ->
+        when (e) {
+            is ParseException ->
+                LintError(e.line, e.col, "",
+                    "Not a valid Kotlin file (${e.message?.toLowerCase()})")
+            is RuleExecutionException -> {
+                if (debug) {
+                    System.err.println("[DEBUG] Internal Error (${e.ruleId})")
+                    e.printStackTrace(System.err)
+                }
+                LintError(e.line, e.col, "", "Internal Error (${e.ruleId}). " +
+                    "Please create a ticket at https://github.com/shyiko/ktlint/issue " +
+                    "(if possible, provide the source code that triggered an error)")
+            }
+            else -> throw e
+        }
+    }
+
+    private fun printAST() {
+        fun process(fileName: String, fileContent: String) {
+            if (debug) {
+                System.err.println("[DEBUG] Analyzing ${if (fileName != "<text>") File(fileName).location() else fileName}")
+            }
+            try {
+                lint(fileName, fileContent, listOf(RuleSet("debug", DumpAST(System.out, color))), emptyMap()) {}
+            } catch (e: Exception) {
+                if (e is ParseException) {
+                    throw ParseException(e.line, e.col, "Not a valid Kotlin file (${e.message?.toLowerCase()})")
+                }
+                throw e
+            }
+        }
+        if (stdin) {
+            process("<text>", String(System.`in`.readBytes()))
+        } else {
+            for (file in fileSequence()) {
+                process(file.path, file.readText())
+            }
+        }
+    }
+
+    private fun fileSequence() =
+        when {
+            patterns.isEmpty() ->
+                Glob.from("**/*.kt", "**/*.kts")
+                    .iterate(Paths.get(workDir), Glob.IterationOption.SKIP_HIDDEN)
+            else ->
+                Glob.from(*patterns.map { expandTilde(it) }.toTypedArray())
+                    .iterate(Paths.get(workDir))
+        }
+            .asSequence()
+            .map(Path::toFile)
+
+    private fun installGitPreCommitHook() {
         if (!File(".git").isDirectory) {
             System.err.println(".git directory not found. " +
                 "Are you sure you are inside project root directory?")
@@ -417,17 +487,16 @@
         System.err.println(".git/hooks/pre-commit installed")
     }
 
-    fun applyToIDEA() {
+    private fun applyToIDEA() {
         try {
             val workDir = Paths.get(".")
             if (!forceApply) {
-                val fileList = IntellijIDEAIntegration.apply(workDir, true, android)
+                val fileList = IntellijIDEAIntegration.apply(workDir, true, android, applyToProject)
                 System.err.println("The following files are going to be updated:\n\n\t" +
                     fileList.joinToString("\n\t") +
                     "\n\nDo you wish to proceed? [y/n]\n" +
                     "(in future, use -y flag if you wish to skip confirmation)")
                 val scanner = Scanner(System.`in`)
-
                 val res = generateSequence {
                         try { scanner.next() } catch (e: NoSuchElementException) { null }
                     }
@@ -438,7 +507,7 @@
                     exitProcess(1)
                 }
             }
-            IntellijIDEAIntegration.apply(workDir, false, android)
+            IntellijIDEAIntegration.apply(workDir, false, android, applyToProject)
         } catch (e: IntellijIDEAIntegration.ProjectNotFoundException) {
             System.err.println(".idea directory not found. " +
                 "Are you sure you are inside project root directory?")
@@ -449,15 +518,15 @@
         System.err.println("(if you experience any issues please report them at https://github.com/shyiko/ktlint)")
     }
 
-    fun hex(input: ByteArray) = BigInteger(MessageDigest.getInstance("SHA-256").digest(input)).toString(16)
+    private fun hex(input: ByteArray) = BigInteger(MessageDigest.getInstance("SHA-256").digest(input)).toString(16)
 
     // a complete solution would be to implement https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
     // this implementation takes care only of the most commonly used case (~/)
-    fun expandTilde(path: String) = path.replaceFirst(Regex("^~"), System.getProperty("user.home"))
+    private fun expandTilde(path: String) = path.replaceFirst(Regex("^~"), System.getProperty("user.home"))
 
-    fun <T> List<T>.head(limit: Int) = if (limit == size) this else this.subList(0, limit)
+    private fun <T> List<T>.head(limit: Int) = if (limit == size) this else this.subList(0, limit)
 
-    fun buildDependencyResolver(): MavenDependencyResolver {
+    private fun buildDependencyResolver(): MavenDependencyResolver {
         val mavenLocal = File(File(System.getProperty("user.home"), ".m2"), "repository")
         mavenLocal.mkdirsOrFail()
         val dependencyResolver = MavenDependencyResolver(
@@ -482,7 +551,7 @@
                 val url = repository.substring(colon + 1)
                 RemoteRepository.Builder(id, "default", url).build()
             },
-            forceUpdate
+            forceUpdate == true
         )
         if (debug) {
             dependencyResolver.setTransferEventListener { e ->
@@ -493,14 +562,15 @@
         return dependencyResolver
     }
 
-    fun loadJARs(dependencyResolver: MavenDependencyResolver, artifacts: List<String>) {
+    // fixme: isn't going to work on JDK 9
+    private fun loadJARs(dependencyResolver: Lazy<MavenDependencyResolver>, artifacts: List<String>) {
         (ClassLoader.getSystemClassLoader() as java.net.URLClassLoader)
             .addURLs(artifacts.flatMap { artifact ->
                 if (debug) {
                     System.err.println("[DEBUG] Resolving $artifact")
                 }
                 val result = try {
-                    dependencyResolver.resolve(DefaultArtifact(artifact)).map { it.toURI().toURL() }
+                    dependencyResolver.value.resolve(DefaultArtifact(artifact)).map { it.toURI().toURL() }
                 } catch (e: IllegalArgumentException) {
                     val file = File(expandTilde(artifact))
                     if (!file.exists()) {
@@ -518,11 +588,25 @@
                 if (debug) {
                     result.forEach { url -> System.err.println("[DEBUG] Loading $url") }
                 }
+                if (!skipClasspathCheck) {
+                    if (result.any { it.toString().substringAfterLast("/").startsWith("ktlint-core-") }) {
+                        System.err.println("\"$artifact\" appears to have a runtime/compile dependency on \"ktlint-core\".\n" +
+                            "Please inform the author that \"com.github.shyiko:ktlint*\" should be marked " +
+                            "compileOnly (Gradle) / provided (Maven).\n" +
+                            "(to suppress this warning use --skip-classpath-check)")
+                    }
+                    if (result.any { it.toString().substringAfterLast("/").startsWith("kotlin-stdlib-") }) {
+                        System.err.println("\"$artifact\" appears to have a runtime/compile dependency on \"kotlin-stdlib\".\n" +
+                            "Please inform the author that \"org.jetbrains.kotlin:kotlin-stdlib*\" should be marked " +
+                            "compileOnly (Gradle) / provided (Maven).\n" +
+                            "(to suppress this warning use --skip-classpath-check)")
+                    }
+                }
                 result
             })
     }
 
-    fun parseQuery(query: String) = query.split("&")
+    private fun parseQuery(query: String) = query.split("&")
         .fold(LinkedHashMap<String, String>()) { map, s ->
             if (!s.isEmpty()) {
                 s.split("=", limit = 2).let { e -> map.put(e[0],
@@ -531,24 +615,42 @@
             map
         }
 
-    fun lint(fileName: String, text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>,
-            cb: (e: LintError) -> Unit) =
-        if (fileName.endsWith(".kt", ignoreCase = true)) KtLint.lint(text, ruleSets, userData, cb) else
+    private fun lint(
+        fileName: String,
+        text: String,
+        ruleSets: Iterable<RuleSet>,
+        userData: Map<String, String>,
+        cb: (e: LintError) -> Unit
+    ) =
+        if (fileName.endsWith(".kt", ignoreCase = true)) {
+            KtLint.lint(text, ruleSets, userData, cb)
+        } else {
             KtLint.lintScript(text, ruleSets, userData, cb)
+        }
 
-    fun format(fileName: String, text: String, ruleSets: Iterable<RuleSet>, userData: Map<String, String>,
-            cb: (e: LintError, corrected: Boolean) -> Unit): String =
-        if (fileName.endsWith(".kt", ignoreCase = true)) KtLint.format(text, ruleSets, userData, cb) else
+    private fun format(
+        fileName: String,
+        text: String,
+        ruleSets: Iterable<RuleSet>,
+        userData: Map<String, String>,
+        cb: (e: LintError, corrected: Boolean) -> Unit
+    ): String =
+        if (fileName.endsWith(".kt", ignoreCase = true)) {
+            KtLint.format(text, ruleSets, userData, cb)
+        } else {
             KtLint.formatScript(text, ruleSets, userData, cb)
+        }
 
-    fun java.net.URLClassLoader.addURLs(url: Iterable<java.net.URL>) {
+    private fun java.net.URLClassLoader.addURLs(url: Iterable<java.net.URL>) {
         val method = java.net.URLClassLoader::class.java.getDeclaredMethod("addURL", java.net.URL::class.java)
         method.isAccessible = true
         url.forEach { method.invoke(this, it) }
     }
 
-    fun <T>Sequence<Callable<T>>.parallel(cb: (T) -> Unit,
-        numberOfThreads: Int = Runtime.getRuntime().availableProcessors()) {
+    private fun <T> Sequence<Callable<T>>.parallel(
+        cb: (T) -> Unit,
+        numberOfThreads: Int = Runtime.getRuntime().availableProcessors()
+    ) {
         val q = ArrayBlockingQueue<Future<T>>(numberOfThreads)
         val pill = object : Future<T> {
 
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt
index 519f6a5..bce07c5 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt
@@ -1,12 +1,13 @@
 package com.github.shyiko.ktlint.internal
 
-import org.ini4j.Wini
 import java.io.ByteArrayInputStream
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.Paths
+import java.util.Properties
 
 class EditorConfig private constructor (
+    val parent: EditorConfig?,
     val path: Path,
     private val data: Map<String, String>
 ) : Map<String, String> by data {
@@ -16,21 +17,113 @@
         fun of(dir: String) =
             of(Paths.get(dir))
         fun of(dir: Path) =
-            locate(dir)?.let { EditorConfig(it, load(it)) }
+            generateSequence(locate(dir)) { seed -> locate(seed.parent.parent) } // seed.parent == .editorconfig dir
+                .map { it to lazy { load(it) } }
+                .let { seq ->
+                    // stop when .editorconfig with "root = true" is found, go deeper otherwise
+                    var prev: Pair<Path, Lazy<Map<String, Map<String, String>>>>? = null
+                    seq.takeWhile { pair ->
+                        (prev?.second?.value?.get("")?.get("root")?.toBoolean()?.not() ?: true).also { prev = pair }
+                    }
+                }
+                .toList()
+                .asReversed()
+                .fold(null as EditorConfig?) { parent, (path, data) ->
+                    EditorConfig(parent, path, (parent?.data ?: emptyMap()) + flatten(data.value))
+                }
 
         private fun locate(dir: Path?): Path? = when (dir) {
             null -> null
-            else -> Paths.get(dir.toString(), ".editorconfig").let {
+            else -> dir.resolve(".editorconfig").let {
                 if (Files.exists(it)) it else locate(dir.parent)
             }
         }
 
-        private fun load(path: Path): Map<String, String> {
-            val editorConfig = Wini(ByteArrayInputStream(Files.readAllBytes(path)))
-            // right now ktlint requires explicit [*.{kt,kts}] section
-            // (this way we can be sure that users want .editorconfig to be recognized by ktlint)
-            val section = editorConfig["*.{kt,kts}"]
-            return section?.toSortedMap() ?: emptyMap()
+        private fun flatten(data: LinkedHashMap<String, Map<String, String>>): Map<String, String> {
+            val map = mutableMapOf<String, String>()
+            val patternsToSearchFor = arrayOf("*", "*.kt", "*.kts")
+            for ((sectionName, section) in data) {
+                if (sectionName == "") {
+                    continue
+                }
+                val patterns = try {
+                    parseSection(sectionName.substring(1, sectionName.length - 1))
+                } catch (e: Exception) {
+                    throw RuntimeException("ktlint failed to parse .editorconfig section \"$sectionName\"" +
+                        " (please report at https://github.com/shyiko/ktlint)", e)
+                }
+                if (patternsToSearchFor.any { patterns.contains(it) }) {
+                    map.putAll(section.toMap())
+                }
+            }
+            return map.toSortedMap()
+        }
+
+        private fun load(path: Path) =
+            linkedMapOf<String, Map<String, String>>().also { map ->
+                object : Properties() {
+
+                    private var section: MutableMap<String, String>? = null
+
+                    override fun put(key: Any, value: Any): Any? {
+                        val sectionName = (key as String).trim()
+                        if (sectionName.startsWith('[') && sectionName.endsWith(']') && value == "") {
+                            section = mutableMapOf<String, String>().also { map.put(sectionName, it) }
+                        } else {
+                            val section = section
+                                ?: mutableMapOf<String, String>().also { section = it; map.put("", it) }
+                            section[key] = value.toString()
+                        }
+                        return null
+                    }
+                }.load(ByteArrayInputStream(Files.readAllBytes(path)))
+            }
+
+        internal fun parseSection(sectionName: String): List<String> {
+            val result = mutableListOf<String>()
+            fun List<List<String>>.collect0(i: Int = 0, str: Array<String?>, acc: MutableList<String>) {
+                if (i == str.size) {
+                    acc.add(str.joinToString(""))
+                    return
+                }
+                for (k in 0 until this[i].size) {
+                    str[i] = this[i][k]
+                    collect0(i + 1, str, acc)
+                }
+            }
+            // [["*.kt"], ["", "s"], ["~"]] -> [*.kt~, *.kts~]
+            fun List<List<String>>.collect(): List<String> =
+                mutableListOf<String>().also { this.collect0(0, arrayOfNulls<String>(this.size), it) }
+            val chunks: MutableList<MutableList<String>> = mutableListOf()
+            chunks.add(mutableListOf())
+            var l = 0
+            var r = 0
+            var partOfBraceExpansion = false
+            for (c in sectionName) {
+                when (c) {
+                    ',' -> {
+                        chunks.last().add(sectionName.substring(l, r))
+                        l = r + 1
+                        if (!partOfBraceExpansion) {
+                            result += chunks.collect()
+                            chunks.clear()
+                            chunks.add(mutableListOf())
+                        }
+                    }
+                    '{', '}' -> {
+                        if (partOfBraceExpansion == (c == '}')) {
+                            chunks.last().add(sectionName.substring(l, r))
+                            l = r + 1
+                            chunks.add(mutableListOf())
+                            partOfBraceExpansion = !partOfBraceExpansion
+                        }
+                    }
+                }
+                r++
+            }
+            chunks.last().add(sectionName.substring(l, r))
+            result += chunks.collect()
+            return result
         }
     }
 }
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt
index 7c7b23f..0e3cc9f 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt
@@ -20,72 +20,103 @@
 
 object IntellijIDEAIntegration {
 
+    @Suppress("UNUSED_PARAMETER")
     @Throws(IOException::class)
-    fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false): Array<Path> {
+    fun apply(workDir: Path, dryRun: Boolean, android: Boolean = false, local: Boolean = false): Array<Path> {
         if (!Files.isDirectory(workDir.resolve(".idea"))) {
             throw ProjectNotFoundException()
         }
-        val home = System.getProperty("user.home")
         val editorConfig: Map<String, String> = EditorConfig.of(".") ?: emptyMap()
-        val continuationIndentSize = editorConfig["continuation_indent_size"]?.toIntOrNull() ?: if (android) 8 else 4
         val indentSize = editorConfig["indent_size"]?.toIntOrNull() ?: 4
-        val codeStyleName = "ktlint${
-            if (continuationIndentSize == 4) "" else "-cis$continuationIndentSize"
-        }${
-            if (indentSize == 4) "" else "-is$indentSize"
-        }"
-        val paths =
-            // macOS
-            Glob.from("IntelliJIdea*", "IdeaIC*", "AndroidStudio*")
-                .iterate(Paths.get(home, "Library", "Preferences"),
-                    Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence() +
-            // linux/windows
-            Glob.from(".IntelliJIdea*/config", ".IdeaIC*/config", ".AndroidStudio*/config")
-                .iterate(Paths.get(home),
-                    Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence()
-        val updates = (paths.flatMap { dir ->
-            sequenceOf(
-                Paths.get(dir.toString(), "codestyles", "$codeStyleName.xml") to
-                    overwriteWithResource("/config/codestyles/ktlint.xml") { resource ->
+        val continuationIndentSize = editorConfig["continuation_indent_size"]?.toIntOrNull() ?: 4
+        val updates = if (local) {
+            listOf(
+                Paths.get(workDir.toString(), ".idea", "codeStyles", "codeStyleConfig.xml") to
+                    overwriteWithResource("/project-config/.idea/codeStyles/codeStyleConfig.xml"),
+                Paths.get(workDir.toString(), ".idea", "codeStyles", "Project.xml") to
+                    overwriteWithResource("/project-config/.idea/codeStyles/Project.xml") { resource ->
                         resource
-                            .replace("code_scheme name=\"ktlint\"",
-                                "code_scheme name=\"$codeStyleName\"")
                             .replace("option name=\"INDENT_SIZE\" value=\"4\"",
                                 "option name=\"INDENT_SIZE\" value=\"$indentSize\"")
                             .replace("option name=\"CONTINUATION_INDENT_SIZE\" value=\"8\"",
                                 "option name=\"CONTINUATION_INDENT_SIZE\" value=\"$continuationIndentSize\"")
                     },
-                Paths.get(dir.toString(), "options", "code.style.schemes.xml") to
-                    overwriteWithResource("/config/options/code.style.schemes.xml") { content ->
-                        content
-                            .replace("option name=\"CURRENT_SCHEME_NAME\" value=\"ktlint\"",
-                                "option name=\"CURRENT_SCHEME_NAME\" value=\"$codeStyleName\"")
-                    },
-                Paths.get(dir.toString(), "inspection", "ktlint.xml") to
-                    overwriteWithResource("/config/inspection/ktlint.xml"),
-                Paths.get(dir.toString(), "options", "editor.codeinsight.xml") to {
-                    var arr = "<application></application>".toByteArray()
+                Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to
+                    overwriteWithResource("/project-config/.idea/inspectionProfiles/profiles_settings.xml"),
+                Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "ktlint.xml") to
+                    overwriteWithResource("/project-config/.idea/inspectionProfiles/ktlint.xml"),
+                Paths.get(workDir.toString(), ".idea", "workspace.xml") to {
+                    var arr = "<project version=\"4\"></project>".toByteArray()
                     try {
-                        arr = Files.readAllBytes(Paths.get(dir.toString(), "options", "editor.codeinsight.xml"))
+                        arr = Files.readAllBytes(Paths.get(workDir.toString(), ".idea", "workspace.xml"))
                     } catch (e: IOException) {
                         if (e !is NoSuchFileException) {
                             throw e
                         }
                     }
-                    enableOptimizeImportsOnTheFly(arr)
+                    enableOptimizeImportsOnTheFlyInsideWorkspace(arr)
                 }
             )
-        } + sequenceOf(
-            Paths.get(workDir.toString(), ".idea", "codeStyleSettings.xml") to
-                overwriteWithResource("/config/.idea/codeStyleSettings.xml") { content ->
-                    content.replace(
-                        "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"ktlint\"",
-                        "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"$codeStyleName\""
-                    )
-                },
-            Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to
-                overwriteWithResource("/config/.idea/inspectionProfiles/profiles_settings.xml")
-        )).toList()
+        } else {
+            val home = System.getProperty("user.home")
+            val codeStyleName = "ktlint${
+                if (continuationIndentSize == 4) "" else "-cis$continuationIndentSize"
+            }${
+                if (indentSize == 4) "" else "-is$indentSize"
+            }"
+            val paths =
+                // macOS
+                Glob.from("IntelliJIdea*", "IdeaIC*", "AndroidStudio*")
+                    .iterate(Paths.get(home, "Library", "Preferences"),
+                        Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence() +
+                // linux/windows
+                Glob.from(".IntelliJIdea*/config", ".IdeaIC*/config", ".AndroidStudio*/config")
+                    .iterate(Paths.get(home),
+                        Glob.IterationOption.SKIP_CHILDREN, Glob.IterationOption.DIRECTORY).asSequence()
+            (paths.flatMap { dir ->
+                sequenceOf(
+                    Paths.get(dir.toString(), "codestyles", "$codeStyleName.xml") to
+                        overwriteWithResource("/config/codestyles/ktlint.xml") { resource ->
+                            resource
+                                .replace("code_scheme name=\"ktlint\"",
+                                    "code_scheme name=\"$codeStyleName\"")
+                                .replace("option name=\"INDENT_SIZE\" value=\"4\"",
+                                    "option name=\"INDENT_SIZE\" value=\"$indentSize\"")
+                                .replace("option name=\"CONTINUATION_INDENT_SIZE\" value=\"8\"",
+                                    "option name=\"CONTINUATION_INDENT_SIZE\" value=\"$continuationIndentSize\"")
+                        },
+                    Paths.get(dir.toString(), "options", "code.style.schemes.xml") to
+                        overwriteWithResource("/config/options/code.style.schemes.xml") { content ->
+                            content
+                                .replace("option name=\"CURRENT_SCHEME_NAME\" value=\"ktlint\"",
+                                    "option name=\"CURRENT_SCHEME_NAME\" value=\"$codeStyleName\"")
+                        },
+                    Paths.get(dir.toString(), "inspection", "ktlint.xml") to
+                        overwriteWithResource("/config/inspection/ktlint.xml"),
+                    Paths.get(dir.toString(), "options", "editor.codeinsight.xml") to {
+                        var arr = "<application></application>".toByteArray()
+                        try {
+                            arr = Files.readAllBytes(Paths.get(dir.toString(), "options", "editor.codeinsight.xml"))
+                        } catch (e: IOException) {
+                            if (e !is NoSuchFileException) {
+                                throw e
+                            }
+                        }
+                        enableOptimizeImportsOnTheFly(arr)
+                    }
+                )
+            } + sequenceOf(
+                Paths.get(workDir.toString(), ".idea", "codeStyleSettings.xml") to
+                    overwriteWithResource("/config/.idea/codeStyleSettings.xml") { content ->
+                        content.replace(
+                            "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"ktlint\"",
+                            "option name=\"PREFERRED_PROJECT_CODE_STYLE\" value=\"$codeStyleName\""
+                        )
+                    },
+                Paths.get(workDir.toString(), ".idea", "inspectionProfiles", "profiles_settings.xml") to
+                    overwriteWithResource("/config/.idea/inspectionProfiles/profiles_settings.xml")
+            )).toList()
+        }
         if (!dryRun) {
             updates.forEach { (path, contentSupplier) ->
                 Files.createDirectories(path.parent)
@@ -133,6 +164,39 @@
         return out.toByteArray()
     }
 
+    private fun enableOptimizeImportsOnTheFlyInsideWorkspace(arr: ByteArray): ByteArray {
+        /*
+        <project>
+          <component name="CodeInsightWorkspaceSettings">
+            <option name="optimizeImportsOnTheFly" value="false" />
+            ...
+          </component>
+          ...
+        </project>
+        */
+        val doc = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(ByteArrayInputStream(arr))
+        val xpath = XPathFactory.newInstance().newXPath()
+        var cis = xpath.evaluate("//component[@name='CodeInsightWorkspaceSettings']",
+            doc, XPathConstants.NODE) as Element?
+        if (cis == null) {
+            cis = doc.createElement("component")
+            cis.setAttribute("name", "CodeInsightWorkspaceSettings")
+            cis = doc.documentElement.appendChild(cis) as Element
+        }
+        var oiotf = xpath.evaluate("//option[@name='optimizeImportsOnTheFly']",
+            cis, XPathConstants.NODE) as Element?
+        if (oiotf == null) {
+            oiotf = doc.createElement("option")
+            oiotf.setAttribute("name", "optimizeImportsOnTheFly")
+            oiotf = cis.appendChild(oiotf) as Element
+        }
+        oiotf.setAttribute("value", "true")
+        val transformer = TransformerFactory.newInstance().newTransformer()
+        val out = ByteArrayOutputStream()
+        transformer.transform(DOMSource(doc), StreamResult(out))
+        return out.toByteArray()
+    }
+
     private fun getResourceText(name: String) =
         this::class.java.getResourceAsStream(name).readBytes().toString(Charset.forName("UTF-8"))
 
diff --git a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt
index a01f015..1760279 100644
--- a/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt
+++ b/ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt
@@ -8,6 +8,7 @@
 import org.eclipse.aether.collection.CollectRequest
 import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
 import org.eclipse.aether.graph.Dependency
+import org.eclipse.aether.impl.DefaultServiceLocator
 import org.eclipse.aether.repository.LocalRepository
 import org.eclipse.aether.repository.RemoteRepository
 import org.eclipse.aether.repository.RepositoryPolicy
@@ -21,8 +22,11 @@
 import org.eclipse.aether.util.graph.visitor.PreorderNodeListGenerator
 import java.io.File
 
-class MavenDependencyResolver(baseDir: File, val repositories: Iterable<RemoteRepository>,
-    forceUpdate: Boolean) {
+class MavenDependencyResolver(
+    baseDir: File,
+    val repositories: Iterable<RemoteRepository>,
+    forceUpdate: Boolean
+) {
 
     private val repoSystem: RepositorySystem
     private val session: RepositorySystemSession
@@ -32,11 +36,19 @@
         locator.addService(RepositoryConnectorFactory::class.java, BasicRepositoryConnectorFactory::class.java)
         locator.addService(TransporterFactory::class.java, FileTransporterFactory::class.java)
         locator.addService(TransporterFactory::class.java, HttpTransporterFactory::class.java)
+        locator.setErrorHandler(object : DefaultServiceLocator.ErrorHandler() {
+            override fun serviceCreationFailed(type: Class<*>?, impl: Class<*>?, ex: Throwable) {
+                throw ex
+            }
+        })
         repoSystem = locator.getService(RepositorySystem::class.java)
         session = MavenRepositorySystemUtils.newSession()
         session.localRepositoryManager = repoSystem.newLocalRepositoryManager(session, LocalRepository(baseDir))
-        session.updatePolicy = if (forceUpdate) RepositoryPolicy.UPDATE_POLICY_ALWAYS else
+        session.updatePolicy = if (forceUpdate) {
+            RepositoryPolicy.UPDATE_POLICY_ALWAYS
+        } else {
             RepositoryPolicy.UPDATE_POLICY_NEVER
+        }
     }
 
     fun setTransferEventListener(listener: (event: TransferEvent) -> Unit) {
diff --git a/ktlint/src/main/resources/config/codestyles/ktlint.xml b/ktlint/src/main/resources/config/codestyles/ktlint.xml
index 213a17d..e73bb3b 100644
--- a/ktlint/src/main/resources/config/codestyles/ktlint.xml
+++ b/ktlint/src/main/resources/config/codestyles/ktlint.xml
@@ -7,8 +7,10 @@
     </option>
     <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
     <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
+    <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
   </JetCodeStyleSettings>
   <codeStyleSettings language="kotlin">
+    <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
     <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
     <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
     <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
diff --git a/ktlint/src/main/resources/config/inspection/ktlint.xml b/ktlint/src/main/resources/config/inspection/ktlint.xml
index a1ad97e..449db9e 100644
--- a/ktlint/src/main/resources/config/inspection/ktlint.xml
+++ b/ktlint/src/main/resources/config/inspection/ktlint.xml
@@ -3,6 +3,5 @@
   <option name="myName" value="ktlint" />
   <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
   <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
-  <inspection_tool class="UnusedSymbol" enabled="true" level="ERROR" enabled_by_default="true" />
 </inspections>
 
diff --git a/ktlint/src/main/resources/ktlint-git-pre-commit-hook-android.sh b/ktlint/src/main/resources/ktlint-git-pre-commit-hook-android.sh
index 3d8432c..58b3994 100755
--- a/ktlint/src/main/resources/ktlint-git-pre-commit-hook-android.sh
+++ b/ktlint/src/main/resources/ktlint-git-pre-commit-hook-android.sh
@@ -1,4 +1,4 @@
 #!/bin/sh
 # https://github.com/shyiko/ktlint pre-commit hook
-git diff --name-only --cached --relative | grep '\.kts\?$' | xargs ktlint --android --relative .
+git diff --name-only --cached --relative | grep '\.kt[s"]\?$' | xargs ktlint --android --relative .
 if [ $? -ne 0 ]; then exit 1; fi
diff --git a/ktlint/src/main/resources/ktlint-git-pre-commit-hook.sh b/ktlint/src/main/resources/ktlint-git-pre-commit-hook.sh
index dcc2485..7a25ea8 100755
--- a/ktlint/src/main/resources/ktlint-git-pre-commit-hook.sh
+++ b/ktlint/src/main/resources/ktlint-git-pre-commit-hook.sh
@@ -1,4 +1,4 @@
 #!/bin/sh
 # https://github.com/shyiko/ktlint pre-commit hook
-git diff --name-only --cached --relative | grep '\.kts\?$' | xargs ktlint --relative .
+git diff --name-only --cached --relative | grep '\.kt[s"]\?$' | xargs ktlint --relative .
 if [ $? -ne 0 ]; then exit 1; fi
diff --git a/ktlint/src/main/resources/project-config/.idea/codeStyles/Project.xml b/ktlint/src/main/resources/project-config/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..c8d67de
--- /dev/null
+++ b/ktlint/src/main/resources/project-config/.idea/codeStyles/Project.xml
@@ -0,0 +1,25 @@
+<component name="ProjectCodeStyleConfiguration">
+  <code_scheme name="Project" version="173">
+    <JetCodeStyleSettings>
+      <option name="PACKAGES_TO_USE_STAR_IMPORTS">
+        <value>
+          <package name="kotlinx.android.synthetic" withSubpackages="true" static="false" />
+        </value>
+      </option>
+      <option name="NAME_COUNT_TO_USE_STAR_IMPORT" value="2147483647" />
+      <option name="NAME_COUNT_TO_USE_STAR_IMPORT_FOR_MEMBERS" value="2147483647" />
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+    </JetCodeStyleSettings>
+    <codeStyleSettings language="kotlin">
+      <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
+      <option name="KEEP_BLANK_LINES_IN_DECLARATIONS" value="1" />
+      <option name="KEEP_BLANK_LINES_IN_CODE" value="1" />
+      <option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="0" />
+      <option name="ALIGN_MULTILINE_PARAMETERS" value="false" />
+      <indentOptions>
+        <option name="INDENT_SIZE" value="4" />
+        <option name="CONTINUATION_INDENT_SIZE" value="8" />
+      </indentOptions>
+    </codeStyleSettings>
+  </code_scheme>
+</component>
diff --git a/ktlint/src/main/resources/project-config/.idea/codeStyles/codeStyleConfig.xml b/ktlint/src/main/resources/project-config/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..0f7bc51
--- /dev/null
+++ b/ktlint/src/main/resources/project-config/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="USE_PER_PROJECT_SETTINGS" value="true" />
+  </state>
+</component>
diff --git a/ktlint/src/main/resources/project-config/.idea/inspectionProfiles/ktlint.xml b/ktlint/src/main/resources/project-config/.idea/inspectionProfiles/ktlint.xml
new file mode 100644
index 0000000..7d04a74
--- /dev/null
+++ b/ktlint/src/main/resources/project-config/.idea/inspectionProfiles/ktlint.xml
@@ -0,0 +1,7 @@
+<component name="InspectionProjectProfileManager">
+  <profile version="1.0">
+    <option name="myName" value="ktlint" />
+    <inspection_tool class="KotlinUnusedImport" enabled="true" level="ERROR" enabled_by_default="true" />
+    <inspection_tool class="RedundantSemicolon" enabled="true" level="ERROR" enabled_by_default="true" />
+  </profile>
+</component>
diff --git a/ktlint/src/main/resources/project-config/.idea/inspectionProfiles/profiles_settings.xml b/ktlint/src/main/resources/project-config/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..64580d1
--- /dev/null
+++ b/ktlint/src/main/resources/project-config/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+<component name="InspectionProjectProfileManager">
+  <settings>
+    <option name="PROJECT_PROFILE" value="ktlint" />
+    <version value="1.0" />
+  </settings>
+</component>
diff --git a/ktlint/src/test/kotlin/com/github/shyiko/ktlint/internal/EditorConfigTest.kt b/ktlint/src/test/kotlin/com/github/shyiko/ktlint/internal/EditorConfigTest.kt
new file mode 100644
index 0000000..eb65709
--- /dev/null
+++ b/ktlint/src/test/kotlin/com/github/shyiko/ktlint/internal/EditorConfigTest.kt
@@ -0,0 +1,109 @@
+package com.github.shyiko.ktlint.internal
+
+import com.google.common.jimfs.Configuration
+import com.google.common.jimfs.Jimfs
+import org.assertj.core.api.Assertions.assertThat
+import org.testng.annotations.Test
+import java.nio.file.Files
+
+class EditorConfigTest {
+
+    @Test
+    fun testParentDirectoryFallback() {
+        val fs = Jimfs.newFileSystem(Configuration.unix())
+        Files.createDirectories(fs.getPath("/projects/project-1/project-1-subdirectory"))
+        for (cfg in arrayOf(
+            """
+            [*]
+            indent_size = 2
+            """,
+            """
+            root = true
+            [*]
+            indent_size = 2
+            """,
+            """
+            [*]
+            indent_size = 4
+            [*.{kt,kts}]
+            indent_size = 2
+            """,
+            """
+            [*.{kt,kts}]
+            indent_size = 4
+            [*]
+            indent_size = 2
+            """
+        )) {
+            Files.write(fs.getPath("/projects/project-1/.editorconfig"), cfg.trimIndent().toByteArray())
+            val editorConfig = EditorConfig.of(fs.getPath("/projects/project-1/project-1-subdirectory"))
+            assertThat(editorConfig?.parent).isNull()
+            assertThat(editorConfig?.toMap())
+                .overridingErrorMessage("Expected \n%s\nto yield indent_size = 2", cfg.trimIndent())
+                .isEqualTo(mapOf("indent_size" to "2"))
+        }
+    }
+
+    @Test
+    fun testRootTermination() {
+        val fs = Jimfs.newFileSystem(Configuration.unix())
+        Files.createDirectories(fs.getPath("/projects/project-1/project-1-subdirectory"))
+        Files.write(fs.getPath("/projects/.editorconfig"), """
+            root = true
+            [*]
+            end_of_line = lf
+        """.trimIndent().toByteArray())
+        Files.write(fs.getPath("/projects/project-1/.editorconfig"), """
+            root = true
+            [*.{kt,kts}]
+            indent_size = 4
+            indent_style = space
+        """.trimIndent().toByteArray())
+        Files.write(fs.getPath("/projects/project-1/project-1-subdirectory/.editorconfig"), """
+            [*]
+            indent_size = 2
+        """.trimIndent().toByteArray())
+        EditorConfig.of(fs.getPath("/projects/project-1/project-1-subdirectory")).let { editorConfig ->
+            assertThat(editorConfig?.parent).isNotNull()
+            assertThat(editorConfig?.parent?.parent).isNull()
+            assertThat(editorConfig?.toMap()).isEqualTo(mapOf(
+                "indent_size" to "2",
+                "indent_style" to "space"
+            ))
+        }
+        EditorConfig.of(fs.getPath("/projects/project-1")).let { editorConfig ->
+            assertThat(editorConfig?.parent).isNull()
+            assertThat(editorConfig?.toMap()).isEqualTo(mapOf(
+                "indent_size" to "4",
+                "indent_style" to "space"
+            ))
+        }
+        EditorConfig.of(fs.getPath("/projects")).let { editorConfig ->
+            assertThat(editorConfig?.parent).isNull()
+            assertThat(editorConfig?.toMap()).isEqualTo(mapOf(
+                "end_of_line" to "lf"
+            ))
+        }
+    }
+
+    @Test
+    fun testSectionParsing() {
+        assertThat(EditorConfig.parseSection("*")).isEqualTo(listOf("*"))
+        assertThat(EditorConfig.parseSection("*.{js,py}")).isEqualTo(listOf("*.js", "*.py"))
+        assertThat(EditorConfig.parseSection("*.py")).isEqualTo(listOf("*.py"))
+        assertThat(EditorConfig.parseSection("Makefile")).isEqualTo(listOf("Makefile"))
+        assertThat(EditorConfig.parseSection("lib/**.js")).isEqualTo(listOf("lib/**.js"))
+        assertThat(EditorConfig.parseSection("{package.json,.travis.yml}"))
+            .isEqualTo(listOf("package.json", ".travis.yml"))
+    }
+
+    @Test
+    fun testMalformedSectionParsing() {
+        assertThat(EditorConfig.parseSection("")).isEqualTo(listOf(""))
+        assertThat(EditorConfig.parseSection(",*")).isEqualTo(listOf("", "*"))
+        assertThat(EditorConfig.parseSection("*,")).isEqualTo(listOf("*", ""))
+        assertThat(EditorConfig.parseSection("*.{js,py")).isEqualTo(listOf("*.js", "*.py"))
+        assertThat(EditorConfig.parseSection("*.{js,{py")).isEqualTo(listOf("*.js", "*.{py"))
+        assertThat(EditorConfig.parseSection("*.py}")).isEqualTo(listOf("*.py}"))
+    }
+}
diff --git a/pom.xml b/pom.xml
index 0113e4c..f952983 100644
--- a/pom.xml
+++ b/pom.xml
@@ -36,16 +36,27 @@
             <name>Sonatype Nexus Staging</name>
             <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
         </repository>
+        <snapshotRepository>
+            <id>maven-central</id>
+            <name>Sonatype Nexus Snapshots</name>
+            <url>https://oss.sonatype.org/content/repositories/snapshots</url>
+        </snapshotRepository>
     </distributionManagement>
 
     <properties>
         <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-        <kotlin.version>1.1.51</kotlin.version>
+        <kotlin.version>1.2.41</kotlin.version>
+        <kotlin.compiler.languageVersion>1.1</kotlin.compiler.languageVersion>
+        <kotlin.compiler.apiVersion>1.1</kotlin.compiler.apiVersion>
         <aether.version>1.1.0</aether.version>
-        <aether.maven.provider.version>3.2.5</aether.maven.provider.version>
-        <args4j.version>2.33</args4j.version>
+        <aether.maven.provider.version>3.3.9</aether.maven.provider.version>
+        <picocli.version>2.3.0</picocli.version>
+        <kolor.version>0.0.2</kolor.version>
         <testng.version>6.8.21</testng.version>
-        <assertj.version>1.7.1</assertj.version>
+        <assertj.version>3.9.0</assertj.version>
+        <jimfs.version>1.1</jimfs.version>
+        <gpg.skip>false</gpg.skip>
+        <github.description>${project.version}</github.description>
     </properties>
 
     <modules>
@@ -62,14 +73,14 @@
         <repository>
             <id>bintray</id>
             <name>JCenter</name>
-            <url>http://jcenter.bintray.com</url>
+            <url>https://jcenter.bintray.com</url>
         </repository>
     </repositories>
     <pluginRepositories>
         <pluginRepository>
             <id>bintray</id>
             <name>JCenter</name>
-            <url>http://jcenter.bintray.com</url>
+            <url>https://jcenter.bintray.com</url>
         </pluginRepository>
     </pluginRepositories>
 
@@ -78,7 +89,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
-                <version>3.5.1</version>
+                <version>3.7.0</version>
                 <configuration>
                     <source>1.8</source>
                     <target>1.8</target>
@@ -126,6 +137,24 @@
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-enforcer-plugin</artifactId>
+                <version>3.0.0-M1</version>
+                <executions>
+                    <execution>
+                        <id>enforce</id>
+                        <configuration>
+                            <rules>
+                                <dependencyConvergence/>
+                            </rules>
+                        </configuration>
+                        <goals>
+                            <goal>enforce</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-deploy-plugin</artifactId>
                 <version>2.8.2</version>
                 <configuration>
@@ -159,7 +188,7 @@
             <extension>
                 <groupId>com.github.shyiko.servers-maven-extension</groupId>
                 <artifactId>servers-maven-extension</artifactId>
-                <version>1.3.0</version>
+                <version>1.3.1</version>
             </extension>
             <extension>
                 <groupId>com.github.shyiko.usage-maven-plugin</groupId>
@@ -198,12 +227,12 @@
                     <plugin>
                         <groupId>org.sonatype.plugins</groupId>
                         <artifactId>nexus-staging-maven-plugin</artifactId>
-                        <version>1.6.7</version>
+                        <version>1.6.8</version>
                         <extensions>true</extensions>
                         <configuration>
                             <nexusUrl>https://oss.sonatype.org/</nexusUrl>
                             <serverId>maven-central</serverId>
-                            <skipStagingRepositoryClose>true</skipStagingRepositoryClose>
+                            <!--<skipStagingRepositoryClose>true</skipStagingRepositoryClose>-->
                             <!--<autoReleaseAfterClose>true</autoReleaseAfterClose>-->
                         </configuration>
                     </plugin>
@@ -239,7 +268,7 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-antrun-plugin</artifactId>
-                        <version>1.7</version>
+                        <version>1.8</version>
                         <configuration>
                             <target name="update-github-release-notes">
                                 <exec executable="chandler" dir="${basedir}" failonerror="true">
@@ -267,7 +296,7 @@
                     <plugin>
                         <groupId>org.apache.maven.plugins</groupId>
                         <artifactId>maven-antrun-plugin</artifactId>
-                        <version>1.7</version>
+                        <version>1.8</version>
                         <configuration>
                             <target name="deploy-to-homebrew">
                                 <exec executable="${project.basedir}/.deploy-to-homebrew" failonerror="true">
@@ -289,20 +318,24 @@
                 </property>
             </activation>
             <build>
-                <defaultGoal>antrun:run</defaultGoal>
+                <defaultGoal>exec:exec@announce</defaultGoal>
                 <plugins>
                     <plugin>
-                        <groupId>org.apache.maven.plugins</groupId>
-                        <artifactId>maven-antrun-plugin</artifactId>
-                        <version>1.7</version>
-                        <configuration>
-                            <target name="announce">
-                                <exec executable="${project.basedir}/.announce" failonerror="true">
-                                    <env key="VERSION" value="${project.version}"/>
-                                    <env key="GITHUB_TOKEN" value="${settings.servers.github.privateKey}"/>
-                                </exec>
-                            </target>
-                        </configuration>
+                        <groupId>org.codehaus.mojo</groupId>
+                        <artifactId>exec-maven-plugin</artifactId>
+                        <version>1.6.0</version>
+                        <executions>
+                            <execution>
+                                <id>announce</id>
+                                <configuration>
+                                    <executable>${project.basedir}/.announce</executable>
+                                    <environmentVariables>
+                                        <VERSION>${project.version}</VERSION>
+                                        <GITHUB_TOKEN>${settings.servers.github.privateKey}</GITHUB_TOKEN>
+                                    </environmentVariables>
+                                </configuration>
+                            </execution>
+                        </executions>
                     </plugin>
                 </plugins>
             </build>
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..487626c
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,7 @@
+include ":ktlint-core"
+include ':ktlint-test'
+include ':ktlint-ruleset-standard'
+include ':ktlint-reporter-plain'
+include ':ktlint-reporter-json'
+include ':ktlint-reporter-checkstyle'
+include ":ktlint"