aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAurimas Liutikas <aurimas@google.com>2018-05-25 16:15:10 -0700
committerAurimas Liutikas <aurimas@google.com>2018-05-25 16:15:10 -0700
commit3432676ef4ed599933a9a05a9f4e712e7e657f1b (patch)
tree8aa62a48ff99be9923c5f57521ba855b763eb8aa
parent946fa4217826656ac2e82c101f63c5c471ee5df0 (diff)
parent326cdc53aa67dc3e8d68eb24fcc08b3e4a7b9bde (diff)
downloadktlint-master.tar.gz
Merge commit '326cdc53aa67dc3e8d68eb24fcc08b3e4a7b9bde'HEADmastermain
Update to ktlint 0.23.1 https://github.com/shyiko/ktlint/tree/0.23.1 Test: None
-rw-r--r--.appveyor.yml19
-rw-r--r--.gitignore2
-rw-r--r--.travis.yml12
-rw-r--r--CHANGELOG.md165
-rw-r--r--README.md164
-rw-r--r--build.gradle35
-rw-r--r--gradle/wrapper/gradle-wrapper.jarbin0 -> 53636 bytes
-rw-r--r--gradle/wrapper/gradle-wrapper.properties6
-rwxr-xr-xgradlew160
-rw-r--r--gradlew.bat90
-rw-r--r--ktlint-core/build.gradle11
-rw-r--r--ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/KtLint.kt209
-rw-r--r--ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/ReporterProvider.kt2
-rw-r--r--ktlint-core/src/main/kotlin/com/github/shyiko/ktlint/core/Rule.kt26
-rw-r--r--ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/ErrorSuppressionTest.kt7
-rw-r--r--ktlint-core/src/test/kotlin/com/github/shyiko/ktlint/core/KtLintTest.kt36
-rw-r--r--ktlint-reporter-checkstyle/build.gradle11
-rw-r--r--ktlint-reporter-checkstyle/src/test/kotlin/com/github/shyiko/ktlint/reporter/checkstyle/CheckStyleReporterTest.kt2
-rw-r--r--ktlint-reporter-json/build.gradle11
-rw-r--r--ktlint-reporter-json/src/main/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporter.kt42
-rw-r--r--ktlint-reporter-json/src/test/kotlin/com/github/shyiko/ktlint/reporter/json/JsonReporterTest.kt2
-rw-r--r--ktlint-reporter-plain/build.gradle12
-rw-r--r--ktlint-reporter-plain/pom.xml11
-rw-r--r--ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporter.kt31
-rw-r--r--ktlint-reporter-plain/src/main/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterProvider.kt13
-rw-r--r--ktlint-reporter-plain/src/test/kotlin/com/github/shyiko/ktlint/reporter/plain/PlainReporterTest.kt4
-rw-r--r--ktlint-ruleset-standard/build.gradle12
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRule.kt127
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRule.kt35
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/EditorConfig.kt40
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRule.kt63
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRule.kt13
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRule.kt90
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRule.kt134
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRule.kt44
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoBlankLineBeforeRbraceRule.kt11
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRule.kt13
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRule.kt2
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRule.kt28
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRule.kt23
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoMultipleSpacesRule.kt20
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoSemicolonsRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoTrailingSpacesRule.kt34
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnitReturnRule.kt8
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRule.kt36
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoWildcardImportsRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRule.kt151
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundColonRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRule.kt26
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRule.kt20
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRule.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRule.kt24
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRule.kt42
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StandardRuleSetProvider.kt7
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/StringTemplateRule.kt29
-rw-r--r--ktlint-ruleset-standard/src/main/kotlin/com/github/shyiko/ktlint/ruleset/standard/package.kt12
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ChainWrappingRuleTest.kt14
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/CommentSpacingRuleTest.kt64
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FilenameRuleTest.kt129
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/FinalNewlineRuleTest.kt5
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/IndentationRuleTest.kt143
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/MaxLineLengthRuleTest.kt33
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ModifierOrderRuleTest.kt59
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoConsecutiveBlankLinesRuleTest.kt32
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoEmptyClassBodyRuleTest.kt8
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakAfterElseRuleTest.kt127
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoLineBreakBeforeAssignmentRuleTest.kt69
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/NoUnusedImportsRuleTest.kt44
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/ParameterListWrappingRuleTest.kt498
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCommaRuleTest.kt28
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundCurlyRuleTest.kt3
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundKeywordRuleTest.kt1
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundOperatorsRuleTest.kt61
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/SpacingAroundRangeOperatorRuleTest.kt16
-rw-r--r--ktlint-ruleset-standard/src/test/kotlin/com/github/shyiko/ktlint/ruleset/standard/package-test.kt7
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format-expected.kt.spec66
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/format.kt.spec66
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/chain-wrapping/lint.kt.spec46
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/max-line-length/lint.kt.spec4
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/no-blank-line-before-rbrace/lint.kt.spec6
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format-expected.kt.spec12
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/op-spacing/format.kt.spec12
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/op-spacing/lint.kt.spec28
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format-expected.kt.spec8
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/range-spacing/format.kt.spec8
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/range-spacing/lint.kt.spec13
-rw-r--r--ktlint-ruleset-standard/src/test/resources/spec/string-template/lint.kt.spec6
-rw-r--r--ktlint-ruleset-template/build.gradle16
-rw-r--r--ktlint-ruleset-template/src/main/kotlin/yourpkgname/NoVarRule.kt7
-rw-r--r--ktlint-ruleset-template/src/test/kotlin/yourpkgname/NoVarRuleTest.kt1
-rw-r--r--ktlint-test/build.gradle9
-rw-r--r--ktlint-test/pom.xml11
-rw-r--r--ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/DumpAST.kt94
-rw-r--r--ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/RuleExtension.kt28
-rw-r--r--ktlint-test/src/main/kotlin/com/github/shyiko/ktlint/test/package.kt27
-rw-r--r--ktlint/build.gradle42
-rw-r--r--ktlint/pom.xml57
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/Main.kt572
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/EditorConfig.kt111
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/IntellijIDEAIntegration.kt156
-rw-r--r--ktlint/src/main/kotlin/com/github/shyiko/ktlint/internal/MavenDependencyResolver.kt18
-rw-r--r--ktlint/src/main/resources/config/codestyles/ktlint.xml2
-rw-r--r--ktlint/src/main/resources/config/inspection/ktlint.xml1
-rwxr-xr-xktlint/src/main/resources/ktlint-git-pre-commit-hook-android.sh2
-rwxr-xr-xktlint/src/main/resources/ktlint-git-pre-commit-hook.sh2
-rw-r--r--ktlint/src/main/resources/project-config/.idea/codeStyles/Project.xml25
-rw-r--r--ktlint/src/main/resources/project-config/.idea/codeStyles/codeStyleConfig.xml5
-rw-r--r--ktlint/src/main/resources/project-config/.idea/inspectionProfiles/ktlint.xml7
-rw-r--r--ktlint/src/main/resources/project-config/.idea/inspectionProfiles/profiles_settings.xml6
-rw-r--r--ktlint/src/test/kotlin/com/github/shyiko/ktlint/internal/EditorConfigTest.kt109
-rw-r--r--pom.xml81
-rw-r--r--settings.gradle7
112 files changed, 4343 insertions, 824 deletions
diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 00000000..a848655a
--- /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 6e3b9168..11693fe4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,5 @@ target
.gradle
build
!ktlint/src/main/resources/config/.idea
+/.idea
+*.iml
diff --git a/.travis.yml b/.travis.yml
index 108bf07b..606bfc18 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 41af5437..6da54b41 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 @@ set in `[*{kt,kts}]` section).
## 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 b8bd23c6..b2a0a96b 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 @@ It's also [easy to create your own](#creating-a-reporter).
- 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 @@ ktlint recognizes the following [.editorconfig](http://editorconfig.org/) proper
[*.{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 @@ max_line_length=off
> 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 @@ Usually simple `http_proxy=http://proxy-server:port https_proxy=http://proxy-ser
```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 @@ $ ktlint --install-git-pre-commit-hook
<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 @@ $ ktlint --install-git-pre-commit-hook
<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 @@ $ ktlint --install-git-pre-commit-hook
<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 @@ $ ktlint --install-git-pre-commit-hook
<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 @@ To run formatter - `mvn antrun:run@ktlint-format`.
#### ... 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 @@ configurations {
}
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 @@ check.dependsOn ktlint
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.
+
+#### (with a plugin)
-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.
+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 @@ You might also want to take a look at [diffplug/spotless](https://github.com/dif
> (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 @@ $ ktlint -R com.github.username:rulseset:master-SNAPSHOT
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 @@ In short, all you need to do is to implement a
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 @@ git clone https://github.com/shyiko/ktlint && cd ktlint
./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 00000000..c1c9c6b8
--- /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 00000000..13372aef
--- /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 00000000..fc449c78
--- /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 00000000..9d82f789
--- /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 00000000..8a0b282a
--- /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 00000000..3d45e08e
--- /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 13c7ea4e..977b97d7 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 org.jetbrains.kotlin.psi.psiUtil.startOffset
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 @@ object KtLint {
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))
- }
- } catch (e: Exception) {
- val (line, col) = positionByOffset(node.startOffset)
- throw RuleExecutionException(line, col, id, e)
+ 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 flatten(ruleSets: Iterable<RuleSet>) = ArrayList<Pair<String, Rule>>().apply {
- ruleSets.forEach { ruleSet ->
+ 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}:"
- ruleSet.forEach { rule -> add("$prefix${rule.id}" to rule) }
+ 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)
+ }
+ }
+ } else {
+ for ((fqRuleId, rule) in fqrsExpectedToBeExecutedLast) {
+ rootNode.visit { node ->
+ visit(node, rule, fqRuleId)
+ }
+ }
+ }
+ }
}
}
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 @@ object KtLint {
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 @@ object KtLint {
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 @@ object KtLint {
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
+ mutated = true
+ if (isSuppressed !== nullSuppression) {
+ isSuppressed = calculateSuppressedRegions(rootNode)
+ }
}
- 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)
+ // 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)
}
}
}
- }
- if (autoCorrect) {
- rootNode.visit { node ->
- r.forEach { (id, rule) ->
- if (!isSuppressed(node.startOffset, id)) {
- try {
- rule.visit(node, true) { _, _, canBeAutoCorrected ->
- if (canBeAutoCorrected && 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)
+ 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 rootNode.text.replace("\n", determineLineSeparator(text))
}
- 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 @@ object KtLint {
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 @@ object KtLint {
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 @@ object KtLint {
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 1cfd3d84..6d7ef310 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 @@ import java.io.PrintStream
/**
* `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 33905631..20eddfd1 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 @@ abstract class Rule(val id: String) {
* @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 c85dea7f..73542369 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 @@ class ErrorSuppressionTest {
@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 00000000..b56bcb4e
--- /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 00000000..27b42f88
--- /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 9243d633..a6c09a86 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 @@ class CheckStyleReporterTest {
<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 00000000..951dcf6e
--- /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 3d9b2097..cb2ff302 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 @@ class JsonReporter(val out: PrintStream) : Reporter {
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 d3512e23..02ce58c5 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 @@ class JsonReporterTest {
]
}
]
-""".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 00000000..e512d044
--- /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 3d79e4dc..8287fef7 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 0586cce1..72229501 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 @@ class PlainReporter(val out: PrintStream, val verbose: Boolean = false, val grou
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 @@ class PlainReporter(val out: PrintStream, val verbose: Boolean = false, val grou
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 9b254447..f4bb01f2 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 com.github.shyiko.ktlint.core.ReporterProvider
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 61c896b3..10f5d7f5 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 @@ class PlainReporterTest {
/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 @@ class PlainReporterTest {
/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 00000000..8d2ee519
--- /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 00000000..3c24344d
--- /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 00000000..68ba62b0
--- /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 00000000..64a56484
--- /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 00000000..8c7169c8
--- /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 67057e6e..93e80290 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 @@ class FinalNewlineRule : Rule("final-newline") {
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 @@ class FinalNewlineRule : Rule("final-newline") {
}
}
}
+
+ 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 87b833af..d270735d 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 indent = DEFAULT_INDENT
+ private var indentSize = -1
- 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 58d784e0..aa7cca16 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 @@ class MaxLineLengthRule : Rule("max-line-length") {
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 4815c065..3c9c9e5d 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 @@ 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.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.PUBLIC_KEYWORD
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 @@ class ModifierOrderRule : Rule("modifier-order") {
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 ce64489f..cd7649e1 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 @@ import org.jetbrains.kotlin.lexer.KtTokens
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 1e6142f8..ae7c582d 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 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
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 59a57a34..5e412204 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 @@ class NoEmptyClassBodyRule : Rule("no-empty-class-body") {
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 00000000..d28ba5eb
--- /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 00000000..effbd187
--- /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 36cf26fe..15aa1a2a 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 @@ class NoMultipleSpacesRule : Rule("no-multi-spaces") {
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 @@ class NoMultipleSpacesRule : Rule("no-multi-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.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 @@ class NoMultipleSpacesRule : Rule("no-multi-spaces") {
}
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 fce5ce3c..770e4090 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 @@ import org.jetbrains.kotlin.psi.KtEnumEntry
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 fa3e492f..a7e03c88 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 @@ import org.jetbrains.kotlin.com.intellij.psi.util.PsiTreeUtil
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 205448a3..c911cbbd 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 @@ class NoUnitReturnRule : Rule("no-unit-return") {
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 cbb8287d..b46f04df 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 @@ import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
class NoUnusedImportsRule : Rule("no-unused-imports") {
+ private val componentNRegex = Regex("^component\\d+$")
+
private val operatorSet = setOf(
// unary
"unaryPlus", "unaryMinus", "not",
@@ -34,46 +36,45 @@ class NoUnusedImportsRule : Rule("no-unused-imports") {
// 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 @@ class NoUnusedImportsRule : Rule("no-unused-imports") {
}
}
- 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 db4e7bb9..9cb18aa1 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 @@ import org.jetbrains.kotlin.psi.stubs.elements.KtStubElementTypes
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 00000000..81d06038
--- /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 bb9aca83..e2d6b6a7 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 @@ import org.jetbrains.kotlin.psi.KtTypeParameterList
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 2ebca751..2be2935a 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.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
+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 2f6d226b..2bce695e 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 @@ import org.jetbrains.kotlin.psi.KtLambdaExpression
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 @@ class SpacingAroundCurlyRule : Rule("curly-spacing") {
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 15bb5597..ec31bf9b 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 @@ class SpacingAroundKeywordRule : Rule("keyword-spacing") {
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 4bc9448a..06dd72ee 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.KtSuperExpression
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 00000000..d7bedbea
--- /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 9fd9190c..87f06b42 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 @@ import com.github.shyiko.ktlint.core.RuleSetProvider
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 @@ class StandardRuleSetProvider : RuleSetProvider {
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 f58a4bfe..e63d7927 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 @@ class StringTemplateRule : Rule("string-template") {
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 2acc5c2d..d9e03b1e 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 00000000..b96cf10b
--- /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 00000000..1b8894f7
--- /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 00000000..84e7ad4a
--- /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 2f911990..bea860f6 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 @@ class FinalNewlineRuleTest {
"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 539b45aa..636f812c 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 @@ import org.testng.annotations.Test
class IndentationRuleTest {
@Test
- fun testRule() {
+ fun testLint() {
assertThat(IndentationRule().lint(
"""
/**
@@ -23,7 +23,7 @@ class IndentationRuleTest {
val b = builder().setX().setY()
.build()
val c = builder("long_string" +
- "")
+ "")
}
class A {
@@ -33,52 +33,29 @@ class IndentationRuleTest {
}
""".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) {
- }
-
- 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
- ) {
+ fun main() {
+ val v = ""
+ println(v)
}
- """.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,7 +77,7 @@ class IndentationRuleTest {
}
@Test
- fun testErrorWithCustomIndentSize() {
+ fun testLintIndentSizeUnset() {
assertThat(IndentationRule().lint(
"""
fun main() {
@@ -108,22 +85,102 @@ class IndentationRuleTest {
println(v)
}
""".trimIndent(),
- mapOf("indent_size" to "3")
+ 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(3, 1, "indent", "Unexpected indentation (4) (it should be multiple of 3)")
+ 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 testErrorWithIndentSizeUnset() {
+ fun testLintMultilineFunctionCall() {
assertThat(IndentationRule().lint(
"""
fun main() {
- val v = ""
- println(v)
+ 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 "unset")
+ 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 723b4d3f..52659f8a 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 @@ class MaxLineLengthRuleTest {
}
@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 3c2d1fb4..24372bbc 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 @@ class ModifierOrderRuleTest {
// 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 ModifierOrderRuleTest {
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 @@ class ModifierOrderRuleTest {
}
""".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 @@ class ModifierOrderRuleTest {
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 ModifierOrderRuleTest {
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 @@ class ModifierOrderRuleTest {
"""
)).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 ModifierOrderRuleTest {
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 60da450b..5054d992 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 @@ class NoConsecutiveBlankLinesRuleTest {
}
@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 @@ class NoConsecutiveBlankLinesRuleTest {
"""
)
}
+
+ @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 e115bd03..3bcd180f 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 @@ class NoEmptyClassBodyRuleTest {
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 00000000..8b645ecf
--- /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 00000000..55ac3e39
--- /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 2384ffa9..ad5f6cc9 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 @@ class NoUnusedImportsRuleTest {
}
@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 00000000..24c048e9
--- /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 cc71781f..e0bcb9cc 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 @@ class SpacingAroundCommaRuleTest {
)).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 f5d2ad2e..4d50c68a 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 @@ class SpacingAroundCurlyRuleTest {
@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 @@ class SpacingAroundCurlyRuleTest {
val f =
{ true }
}
+ class A { private val shouldEjectBlock = block@ { (pathProgress ?: return@block false) >= 0.85 } }
""".trimIndent()
)).isEqualTo(
"""
@@ -129,6 +131,7 @@ class SpacingAroundCurlyRuleTest {
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 b8d25bd7..dad897ea 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 @@ class SpacingAroundKeywordRuleTest {
))
}
}
-
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 a64b4e3c..4bb35915 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 00000000..e836e327
--- /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 016cbd46..3c42d4b4 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 @@ fun testLintUsingResource(rule: Rule, qualifier: String = "", userData: Map<Stri
}
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 00000000..481c7b2a
--- /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 00000000..82af6da4
--- /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 00000000..2770cdef
--- /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 c55b6970..de3ebd66 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 @@ fun main() {
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 7113f946..d53a105e 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 @@ fun main() {println("""
}""")}
// 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 00000000..8e47d2ad
--- /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 00000000..407afc78
--- /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 00000000..430456f1
--- /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 00000000..137f4228
--- /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 00000000..20136c21
--- /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 00000000..b2336344
--- /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 3866ac1f..20e09901 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 @@ class B(val k: String) {
}
// 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 209230ff..3a7cbcec 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 @@ buildscript {
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 @@ sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
- mavenCentral()
+ jcenter()
}
task sourcesJar(type: Jar, dependsOn: classes) {
@@ -48,14 +48,14 @@ configurations {
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 0c6b81f1..9eee6eca 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 @@ import org.jetbrains.kotlin.psi.KtStringTemplateEntry
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 2692a3ea..633977dc 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 @@ class NoVarRuleTest : Spek({
}
}
})
-
diff --git a/ktlint-test/build.gradle b/ktlint-test/build.gradle
new file mode 100644
index 00000000..c9d2ae31
--- /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 5a41ded4..c212142f 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 00000000..a22223ee
--- /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 29117900..3914705b 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.Rule
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 @@ fun Rule.lint(text: String, userData: Map<String, String> = emptyMap()): List<Li
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 a3c6b837..00000000
--- 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 00000000..c5fd4c21
--- /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 a0f95091..9b86761f 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 ce805632..9541cdcb 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.core.RuleSetProvider
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
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.nio.file.Paths
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.Future
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 @@ object Main {
"--reporter-update" to
"--repository-update"
)
- private val CLI_MAX_LINE_LENGTH_REGEX = Regex("(.{0,120})(?:\\s|$)")
+ @Option(names = arrayOf("--android", "-a"), description = arrayOf("Turn on Android Kotlin Style Guide compatibility"))
+ private var android: Boolean = false
- // 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
+ // 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
- @Option(name="--android", aliases = arrayOf("-a"), usage = "Turn on Android Kotlin Style Guide compatibility")
- private var android: 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-project"), description = arrayOf("Update Intellij IDEA project settings"))
+ private var applyToProject: 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(names = arrayOf("--color"), description = arrayOf("Make output colorful"))
+ private var color: Boolean = false
- @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(names = arrayOf("--debug"), description = arrayOf("Turn on debug output"))
+ private var debug: Boolean = false
- @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>()
+ // 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(name="--repository-update", aliases = arrayOf("-U", "--ruleset-update", "--reporter-update"),
- usage = "Check remote repositories for updated snapshots")
- private var forceUpdate: 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="--limit", usage = "Maximum number of errors to show (default: show all)")
+ @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(name="--relative", usage = "Print files relative to the working directory " +
- "(e.g. dir/file.kt instead of /home/user/project/dir/file.kt)")
+ @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(name="--verbose", aliases = arrayOf("-v"), usage = "Show error codes")
- private var verbose: 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(name="--stdin", usage = "Read file from stdin")
+ @Option(names = arrayOf("--stdin"), description = arrayOf("Read file from stdin"))
private var stdin: Boolean = false
- @Option(name="--version", usage = "Version", help = true)
+ @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(name="--help", aliases = arrayOf("-h"), help = true)
+ @Option(names = arrayOf("--help", "-h"), help = true, hidden = 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(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")
- private var installGitPreCommitHook: Boolean = false
- @Option(name="-y", hidden = true)
+ @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).
-
- Usage:
- ktlint <flags> [patterns]
- java -jar ktlint <flags> [patterns]
+ private val workDir = File(".").canonicalPath
+ private fun File.location() = if (relative) this.toRelativeString(File(workDir)) else this.path
- Examples:
- # check the style of all Kotlin files inside the current dir (recursively)
- # (hidden folders will be skipped)
- ktlint
+ private fun usage() =
+ ByteArrayOutputStream()
+ .also { CommandLine.usage(this, PrintStream(it), CommandLine.Help.Ansi.OFF) }
+ .toString()
+ .replace(" ".repeat(32), " ".repeat(30))
- # 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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
}
}
}
- 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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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}\"") }
- }
- data class R(val id: String, val config: Map<String, String>, var output: String?)
- if (reporters.isEmpty()) {
- reporters.add("plain")
+ ruleSetProviders.forEach { System.err.println("[DEBUG] Discovered ruleset \"${it.first}\"") }
}
- 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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
}
} 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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
val url = repository.substring(colon + 1)
RemoteRepository.Builder(id, "default", url).build()
},
- forceUpdate
+ forceUpdate == true
)
if (debug) {
dependencyResolver.setTransferEventListener { e ->
@@ -493,14 +562,15 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 @@ ${ByteArrayOutputStream().let { this.printUsage(it); it }.toString().trimEnd().s
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 519f6a59..bce07c5a 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 @@ class EditorConfig private constructor (
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 7c7b23fb..0e3cc9f1 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 @@ import javax.xml.xpath.XPathFactory
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 @@ object IntellijIDEAIntegration {
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 a01f015f..1760279f 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.artifact.Artifact
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.transport.http.HttpTransporterFactory
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 @@ class MavenDependencyResolver(baseDir: File, val repositories: Iterable<RemoteRe
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 213a17db..e73bb3ba 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 a1ad97eb..449db9e1 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 3d8432cc..58b3994e 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 dcc2485f..7a25ea86 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 00000000..c8d67de2
--- /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 00000000..0f7bc519
--- /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 00000000..7d04a74b
--- /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 00000000..64580d14
--- /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 00000000..eb657093
--- /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 0113e4ca..f9529834 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 00000000..487626c7
--- /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"