├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── NOTICE ├── README.md ├── build.sbt ├── input └── src │ └── test │ ├── java │ └── fix │ │ └── scala213 │ │ └── explicitNonNullaryApply │ │ └── Fooable.java │ ├── scala-2.12 │ └── fix │ │ └── scala213 │ │ ├── Console212.scala │ │ ├── NullaryHashHash212.scala │ │ └── Varargs212.scala │ └── scala │ └── fix │ └── scala213 │ ├── Any2StringAdd.scala │ ├── Core.scala │ ├── NoAutoApply.scala │ ├── NullaryEtaExpansion.scala │ ├── NullaryEtaExpansionJava.scala │ ├── NullaryHashHash.scala │ ├── ScalaSeq.scala │ ├── Varargs.scala │ └── explicitNonNullaryApply │ ├── ApplyType.scala │ ├── Foo.scala │ ├── Implicit.scala │ ├── Infix.scala │ ├── NonNullary.scala │ ├── Nullary.scala │ ├── Override.scala │ ├── Postfix.scala │ ├── ToString.scala │ └── package.scala ├── output └── src │ └── test │ ├── java │ └── fix │ │ └── scala213 │ │ └── explicitNonNullaryApply │ │ └── Fooable.java │ ├── scala-2.12 │ └── fix │ │ └── scala213 │ │ ├── Console212.scala │ │ ├── NullaryHashHash212.scala │ │ └── Varargs212.scala │ └── scala │ └── fix │ └── scala213 │ ├── Any2StringAdd.scala │ ├── Core.scala │ ├── NoAutoApply.scala │ ├── NullaryEtaExpansion.scala │ ├── NullaryEtaExpansionJava.scala │ ├── NullaryHashHash.scala │ ├── ScalaSeq.scala │ ├── Varargs.scala │ └── explicitNonNullaryApply │ ├── ApplyType.scala │ ├── Foo.scala │ ├── Implicit.scala │ ├── Infix.scala │ ├── NonNullary.scala │ ├── Nullary.scala │ ├── Override.scala │ ├── Postfix.scala │ ├── ToString.scala │ └── package.scala ├── project ├── ScalaNightlyPlugin.scala ├── build.properties └── plugins.sbt ├── rewrites └── src │ └── main │ ├── resources │ └── META-INF │ │ └── services │ │ └── scalafix.v1.Rule │ └── scala │ ├── fix │ └── scala213 │ │ ├── Any2StringAdd.scala │ │ ├── Core.scala │ │ ├── ExplicitNonNullaryApply.scala │ │ ├── ExplicitNullaryEtaExpansion.scala │ │ ├── NullaryHashHash.scala │ │ ├── ScalaSeq.scala │ │ └── Varargs.scala │ └── impl │ ├── CollectingTraverser.scala │ ├── GlobalImports.scala │ ├── Power.scala │ ├── SignatureMatcher.scala │ └── TreeReplacers.scala └── tests └── src └── test └── scala └── fix ├── BeforeAndAfterAllConfigMapAlt.scala └── RuleSuite.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | test: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | java: [8] 13 | scala: [2.12, 2.12.next, 2.13] 14 | runs-on: ubuntu-latest 15 | env: 16 | CI_SCALA_VERSION: ${{matrix.scala}} 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: coursier/cache-action@v6 20 | - uses: actions/setup-java@v3 21 | with: 22 | distribution: temurin 23 | java-version: ${{matrix.java}} 24 | - uses: sbt/setup-sbt@v1 25 | - name: Test 26 | run: sbt test 27 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | tags: ["*"] 5 | jobs: 6 | publish: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v3 10 | with: 11 | fetch-depth: 0 12 | - uses: actions/setup-java@v3 13 | with: 14 | distribution: temurin 15 | java-version: 8 16 | - uses: sbt/setup-sbt@v1 17 | - run: sbt ci-release 18 | env: 19 | PGP_PASSPHRASE: ${{secrets.PGP_PASSPHRASE}} 20 | PGP_SECRET: ${{secrets.PGP_SECRET}} 21 | SONATYPE_PASSWORD: ${{secrets.SONATYPE_PASSWORD}} 22 | SONATYPE_USERNAME: ${{secrets.SONATYPE_USERNAME}} 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | target-2.13 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | all repositories in these organizations: 2 | 3 | * [scala](https://github.com/scala) 4 | * [scalacenter](https://github.com/scalacenter) 5 | * [lampepfl](https://github.com/lampepfl) 6 | 7 | are covered by the Scala Code of Conduct: https://scala-lang.org/conduct/ 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | scala-rewrites 2 | Copyright (c) 2019-2023 Lightbend, Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"). 5 | Unless required by applicable law or agreed to in writing, software 6 | distributed under the License is distributed on an "AS IS" BASIS, 7 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 8 | See the License for the specific language governing permissions and 9 | limitations under the License. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Scalafix Rewrites for Scala 2 | 3 | ## How to run the rewrites 4 | 5 | Add the `sbt-scalafix` sbt plugin, with the SemanticDB compiler plugin enabled ([official docs][1]): 6 | 7 | ```scala 8 | // project/plugins.sbt 9 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.0") 10 | ``` 11 | 12 | ```scala 13 | // build.sbt 14 | inThisBuild(List( 15 | semanticdbEnabled := true, 16 | semanticdbOptions += "-P:semanticdb:synthetics:on", // make sure to add this 17 | semanticdbVersion := scalafixSemanticdb.revision, 18 | scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value), 19 | )) 20 | ``` 21 | 22 | Then run the desired rewrite(s) ([official docs][2]), in sbt: 23 | 24 | ```scala 25 | > scalafixAll dependency:fix.scala213.ExplicitNonNullaryApply@org.scala-lang:scala-rewrites: 26 | ``` 27 | 28 | You can also add the following to your `build.sbt`: 29 | 30 | ```scala 31 | ThisBuild / scalafixDependencies += "org.scala-lang" %% "scala-rewrites" % "" 32 | ``` 33 | 34 | and then: 35 | 36 | ```scala 37 | > scalafixAll fix.scala213.ExplicitNonNullaryApply 38 | ``` 39 | 40 | [1]: https://scalacenter.github.io/scalafix/docs/users/installation.html 41 | [2]: https://scalacenter.github.io/scalafix/docs/rules/external-rules.html 42 | 43 | ## To develop/contribute to any of the rewrites 44 | 45 | ``` 46 | sbt ~tests/test 47 | # edit rewrites/src/main/scala/... 48 | ``` 49 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | import _root_.scalafix.sbt.BuildInfo._ 2 | import sbt.librarymanagement.Configurations.CompilerPlugin 3 | 4 | inThisBuild(List( 5 | organization := "org.scala-lang", 6 | licenses := List("Apache-2.0" -> url("https://www.apache.org/licenses/LICENSE-2.0")), 7 | developers := List(Developer("", "", "", url("https://github.com/scala/scala-rewrites/graphs/contributors"))), 8 | homepage := Some(url("https://github.com/scala/scala-rewrites")), 9 | scalaVersion := (sys.env.get("CI_SCALA_VERSION") match { 10 | case Some("2.13") => scala213 11 | case Some("2.12") => scala212 12 | case Some("2.12.next") => scala212 // and then overridden by ScalaNightlyPlugin 13 | case None => scala212 14 | case tsv => sys.error(s"Unknown CI_SCALA_VERSION $tsv") 15 | }), 16 | scalacOptions ++= List("-deprecation", "-feature", "-language:_", "-unchecked", "-Xlint"), 17 | crossScalaVersions := Seq(scala212, scala213), 18 | publish / skip := true, 19 | )) 20 | 21 | val rewrites = project.enablePlugins(ScalaNightlyPlugin).settings( 22 | moduleName := "scala-rewrites", 23 | libraryDependencies += "ch.epfl.scala" %% "scalafix-rules" % scalafixVersion, 24 | publish / skip := false, 25 | ) 26 | 27 | val input = project.enablePlugins(ScalaNightlyPlugin).settings( 28 | scalacOptions ++= List("-Yrangepos", "-P:semanticdb:synthetics:on"), 29 | libraryDependencies += "org.scalameta" % "semanticdb-scalac" % scalametaVersion % CompilerPlugin cross CrossVersion.patch, 30 | ) 31 | 32 | val output = project 33 | 34 | // This project is used to verify that the output code actually compiles with scala 2.13 35 | val output213 = output.withId("output213").settings( 36 | target := file(s"${target.value.getPath}-2.13"), 37 | scalaVersion := scala213, 38 | crossScalaVersions := Seq(scalaVersion.value), 39 | ) 40 | 41 | val tests = project.dependsOn(rewrites).enablePlugins(ScalaNightlyPlugin, ScalafixTestkitPlugin).settings( 42 | libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % scalafixVersion % Test cross CrossVersion.patch, 43 | Compile / compile := (Compile / compile).dependsOn(input / Test / compile).value, 44 | scalafixTestkitInputClasspath := ( input / Test / fullClasspath).value, 45 | scalafixTestkitInputSourceDirectories := ( input / Test / sourceDirectories).value, 46 | scalafixTestkitOutputSourceDirectories := (output / Test / sourceDirectories).value, 47 | ScalaNightlyPlugin.ifNightly(Test / fork := true), 48 | ) 49 | 50 | ScalaNightlyPlugin.bootstrapSettings 51 | -------------------------------------------------------------------------------- /input/src/test/java/fix/scala213/explicitNonNullaryApply/Fooable.java: -------------------------------------------------------------------------------- 1 | package fix.scala213.explicitNonNullaryApply; 2 | 3 | // A Java interface to test ExplicitNonNullaryApply against 4 | interface Fooable { String foo(); } 5 | -------------------------------------------------------------------------------- /input/src/test/scala-2.12/fix/scala213/Console212.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.Core 3 | */ 4 | package fix.scala213 5 | 6 | object Console212 { 7 | def consoleReadBoolean = Console.readBoolean() 8 | def consoleReadByte = Console.readByte() 9 | def consoleReadChar = Console.readChar() 10 | def consoleReadDouble = Console.readDouble() 11 | def consoleReadFloat = Console.readFloat() 12 | def consoleReadInt = Console.readInt() 13 | def consoleReadLine = Console.readLine() 14 | def consoleReadLine(text: String, args: Any*) = Console.readLine(text, args: _*) 15 | def consoleReadLong = Console.readLong() 16 | def consoleReadShort = Console.readShort() 17 | def consoleReadf(format: String) = Console.readf(format) 18 | def consoleReadf1(format: String) = Console.readf1(format) 19 | def consoleReadf2(format: String) = Console.readf2(format) 20 | def consoleReadf3(format: String) = Console.readf3(format) 21 | } 22 | -------------------------------------------------------------------------------- /input/src/test/scala-2.12/fix/scala213/NullaryHashHash212.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.NullaryHashHash 3 | */ 4 | package fix.scala213 5 | 6 | object NullaryHashHash212 { 7 | ("": Any).##() 8 | ("": AnyRef).##() 9 | ("": Object).##() 10 | ("": String).##() 11 | "".##() 12 | } 13 | -------------------------------------------------------------------------------- /input/src/test/scala-2.12/fix/scala213/Varargs212.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.Varargs 3 | */ 4 | package fix.scala213 5 | 6 | class Varargs212 { 7 | def foo(xs: scala.collection.Seq[String]) = List(xs: _*) 8 | } 9 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/Any2StringAdd.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.Any2StringAdd 3 | */ 4 | package fix.scala213 5 | 6 | abstract class Any2StringAdd { 7 | // Strings: leave as-is, both literal strings and non-literal 8 | def s = "bob" 9 | def str1 = s + s 10 | def str2 = s + "bob" 11 | def str3 = "bob" + s 12 | def str4 = "bob" + "fred" 13 | 14 | // Non-strings: wrap with String.valueOf 15 | def n: Any = null 16 | def nil = Nil + s 17 | def none = None + s 18 | def Null = n + s 19 | 20 | // Non-string, generic type: wrap with String.valueOf 21 | type A 22 | def x: A 23 | def generic = x + "bob" 24 | 25 | // Numeric primitives: add toString 26 | def byte = 1.toByte 27 | def short = 1.toShort 28 | def char = 'a' 29 | def int = 1 30 | def long = 1L 31 | def float = 1.0F 32 | def double = 1.0 33 | // 34 | def byte1 = byte + s 35 | def byte2 = byte + byte 36 | def short1 = short + s 37 | def short2 = short + short 38 | def char1 = char + s 39 | def char2 = char + char 40 | def int1 = int + s 41 | def int2 = int + int 42 | def long1 = long + s 43 | def long2 = long + long 44 | def float1 = float + s 45 | def float2 = float + float 46 | def double1 = double + s 47 | def double2 = double + double 48 | 49 | // Boolean: wrap with String.valueOf (there's no + on Boolean, AFAICT) 50 | def bool = true 51 | def bool1 = bool + s 52 | 53 | // Scala's Unit primitive: wrap with String.valueOf (there's no + on Unit, AFAICT) 54 | def unit = () 55 | def unit1 = unit + s 56 | 57 | // Custom value types: wrap with String.valueOf (adding toString would be preferable) 58 | import Any2StringAdd.Name 59 | //val name: Name = null // type mismatch; found: Null(null), required: Name 60 | val name = new Name(null) 61 | def name1 = name + s 62 | 63 | // With infix operators, make sure to use parens 64 | def parens1 = Nil ++ Nil + s 65 | def parens2 = int + int + s 66 | def parens3 = {Nil ++ Nil} + s 67 | } 68 | 69 | object Any2StringAdd { 70 | final class Name(val value: String) extends AnyVal { override def toString = value } 71 | } 72 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/Core.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.Core 3 | */ 4 | package fix.scala213 5 | 6 | import scala.compat.Platform 7 | import scala.compat.Platform.{EOL, arraycopy, currentTime} 8 | 9 | object Core { 10 | def eol1 = "Hello World!" + EOL 11 | def eol2 = s"Hello World!$EOL" 12 | def elo2b = s"Hello World!${EOL}" 13 | def eol3 = "Hello World!" + Platform.EOL 14 | def eol4 = s"Hello World!${Platform.EOL}" 15 | def eol5 = "Hello World!" + scala.compat.Platform.EOL 16 | def eol6 = s"Hello World!${scala.compat.Platform.EOL}" 17 | 18 | def currentTimeMillis1 = currentTime 19 | def currentTimeMillis2 = Platform.currentTime 20 | def currentTimeMillis3 = scala.compat.Platform.currentTime 21 | def currentTimeMillis4 = s"now: $currentTime" 22 | 23 | def arrayCopy1() = arraycopy(null, 0, null, 0, 0) 24 | def arrayCopy2() = Platform.arraycopy(null, 0, null, 0, 0) 25 | def arrayCopy3() = scala.compat.Platform.arraycopy(null, 0, null, 0, 0) 26 | 27 | def arrow1: PartialFunction[Any, String] = { 28 | case 0 => "zero" 29 | case 1 ⇒ "one" 30 | case 2 ⇒ "two" 31 | case n: Int ⇒ "ginormous" 32 | case "⇒" => "?" 33 | } 34 | def arrow2(f: Int ⇒ String) = f(1) 35 | def arrow3 = { 36 | import scala.{PartialFunction ⇒ ?=>} 37 | def f: Int ?=> String = { 38 | case 1 => "one" 39 | } 40 | } 41 | def arrow4 = Some(1).map(x ⇒ x.toString) 42 | 43 | def singleArrow1: (Int, Int) = 1 → 1 44 | def singleArrow2: (Int, Int) = 1.→(1) 45 | 46 | def symbolLiteral0 = Symbol("foo") 47 | def symbolLiteral1 = 'foo 48 | } 49 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/NoAutoApply.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213 5 | 6 | object NoAutoApply { 7 | object buz { 8 | override def toString(): String = "" 9 | def empty[T]() = List.empty[T] 10 | } 11 | val x: Iterator[Int] = ??? 12 | def foo() = println(1) 13 | def bar = 32 14 | foo 15 | println(foo) 16 | foo() 17 | bar 18 | x.next 19 | class buz() { 20 | def qux() = List(1) 21 | } 22 | new buz().qux.toIterator.next 23 | new java.util.ArrayList[Int]().toString 24 | val builder = List.newBuilder[Int] 25 | builder.result() 26 | //builder result () 27 | builder.result 28 | fix.scala213.NoAutoApply.buz.empty[String] 29 | var opt: Option[() => Int] = None 30 | opt = None 31 | println(1.toString) 32 | println(buz.toString) // not inserted 33 | List(builder) map (_.result) 34 | builder.## 35 | def lzy(f: => Int) = { 36 | var k = f _ 37 | k = () => 3 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/NullaryEtaExpansion.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNullaryEtaExpansion 3 | */ 4 | package fix.scala213 5 | 6 | class NullaryEtaExpansion { 7 | val str1 = "" 8 | def prop = "" 9 | def meth() = "" 10 | def void(x: Any) = "" 11 | 12 | def def_str1 = str1 _ // rewrite 13 | def def_prop = prop _ // rewrite 14 | def def_meth = meth _ // leave 15 | def def_idty = void _ // leave 16 | 17 | val val_str1 = str1 _ // rewrite 18 | val val_prop = prop _ // rewrite 19 | val val_meth = meth _ // leave 20 | val val_idty = void _ // leave 21 | 22 | var var_str1 = str1 _ // rewrite 23 | var var_prop = prop _ // rewrite 24 | var var_meth = meth _ // leave 25 | var var_idty = void _ // leave 26 | 27 | lazy val lzy_str1 = str1 _ // rewrite 28 | lazy val lzy_prop = prop _ // rewrite 29 | lazy val lzy_meth = meth _ // leave 30 | lazy val lzy_idty = void _ // leave 31 | } 32 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/NullaryEtaExpansionJava.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNullaryEtaExpansion 3 | */ 4 | package fix.scala213 5 | 6 | class NullaryEtaExpansionJava { 7 | class ToS1 { override def toString(): String = "" } 8 | class ToS2 { override def toString: String = "" } 9 | class ToS3 { override val toString: String = "" } 10 | class ToS4 { override lazy val toString: String = "" } 11 | 12 | val toS1 = new ToS1 13 | val toS2 = new ToS2 14 | val toS3 = new ToS3 15 | val toS4 = new ToS4 16 | 17 | val val_toS1 = toS1.toString _ 18 | var var_toS1 = toS1.toString _ 19 | def def_toS1 = toS1.toString _ 20 | lazy val lzy_toS1 = toS1.toString _ 21 | 22 | val val_toS2 = toS2.toString _ 23 | var var_toS2 = toS2.toString _ 24 | def def_toS2 = toS2.toString _ 25 | lazy val lzy_toS2 = toS2.toString _ 26 | 27 | val val_toS3 = toS3.toString _ 28 | var var_toS3 = toS3.toString _ 29 | def def_toS3 = toS3.toString _ 30 | lazy val lzy_toS3 = toS3.toString _ 31 | 32 | val val_toS4 = toS4.toString _ 33 | var var_toS4 = toS4.toString _ 34 | def def_toS4 = toS4.toString _ 35 | lazy val lzy_toS4 = toS4.toString _ 36 | } 37 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/NullaryHashHash.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.NullaryHashHash 3 | */ 4 | package fix.scala213 5 | 6 | object NullaryHashHash { 7 | ("": Any).## 8 | ("": AnyRef).## 9 | ("": Object).## 10 | ("": String).## 11 | "".## 12 | } 13 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/ScalaSeq.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ScalaSeq 3 | fix.scala213.ScalaSeq = { 4 | paramType = "sc.Seq", 5 | paramImport = "scala.{collection => sc}", 6 | otherType = "sci.Seq", 7 | otherImport = "scala.collection.{immutable => sci}" 8 | } 9 | */ 10 | package fix.scala213 11 | 12 | class ScalaSeq { 13 | def f(a: Seq[Int], b: List[Seq[Int]]): Seq[Any] = List(a, b) 14 | } 15 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/Varargs.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.Varargs 3 | */ 4 | package fix.scala213 5 | 6 | class Varargs { 7 | def bar = List(List(1,2,3): _*) 8 | def baz = List(scala.Seq(1,2,3): _*) 9 | // TODO 10 | // def biz = List(scala.collection.mutable.ArrayBuffer(1,2): _*) 11 | } 12 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/ApplyType.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class ApplyType { 7 | object Props { def apply[A]() = this } 8 | object Props2 { def apply[A, B]() = this } 9 | object Nullary { def apply[A] = this } 10 | 11 | Props.apply[Int] // fix 12 | Props[Int] // fix 13 | Props[Int]() // keep 14 | 15 | Props2.apply[Int, String] // fix 16 | Props2[Int, String] // fix 17 | 18 | Nullary[Int] // keep 19 | } 20 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/Foo.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class Foo { 7 | cm.foo() 8 | cm.foo 9 | cp.foo() 10 | cp.foo 11 | } 12 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/Implicit.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class Implicit { 7 | imp // fix 8 | imp() // keep 9 | } 10 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/Infix.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class Infix { 7 | //this meth () // no point, parses as "this.meth(())" which doesn't typecheck 8 | 9 | qual id qual 10 | qual id qual() 11 | qual() id qual 12 | qual() id qual() 13 | 14 | qual id (qual) 15 | qual id ((qual)) 16 | qual id (((qual))) 17 | 18 | qual id[Any] qual 19 | } 20 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/NonNullary.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class NonNullary { 7 | meth 8 | meth() 9 | 10 | self.meth 11 | self.meth() 12 | 13 | qual.meth 14 | qual.meth() 15 | 16 | qual().meth 17 | qual().meth() 18 | 19 | 20 | meth _ 21 | 22 | 23 | ???.getClass() 24 | ???.getClass 25 | 26 | any.getClass() 27 | any.getClass 28 | 29 | ref.getClass() 30 | ref.getClass 31 | 32 | obj.getClass() 33 | obj.getClass 34 | 35 | str.getClass() 36 | str.getClass 37 | 38 | 39 | ???.hashCode() 40 | ???.hashCode 41 | 42 | any.hashCode() 43 | any.hashCode 44 | 45 | ref.hashCode() 46 | ref.hashCode 47 | 48 | obj.hashCode() 49 | obj.hashCode 50 | 51 | str.hashCode() 52 | str.hashCode 53 | 54 | 55 | cm.run() 56 | cm.run 57 | cp.run() 58 | cp.run 59 | } 60 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/Nullary.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class Nullary { 7 | prop 8 | self.prop 9 | qual.prop 10 | qual().prop 11 | 12 | ???.## 13 | any.## 14 | ref.## 15 | obj.## 16 | str.## 17 | 18 | ???.asInstanceOf[Int] 19 | any.asInstanceOf[Int] 20 | ref.asInstanceOf[Int] 21 | obj.asInstanceOf[Int] 22 | str.asInstanceOf[Int] 23 | 24 | ???.isInstanceOf[Int] 25 | any.isInstanceOf[Int] 26 | ref.isInstanceOf[Int] 27 | obj.isInstanceOf[Int] 28 | str.isInstanceOf[Int] 29 | } 30 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/Override.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | // ExplicitNonNullaryApply, when methods are overridden 7 | class Override { 8 | class Meth { def m2p() = "" } 9 | class Prop { def p2m = "" } 10 | 11 | class Meth2Prop extends Meth { override def m2p = "" } 12 | class Prop2Meth extends Prop { override def p2m() = "" } 13 | 14 | val meth2prop = new Meth2Prop 15 | val prop2meth = new Prop2Meth 16 | 17 | meth2prop.m2p() 18 | meth2prop.m2p // <- should call with parens 19 | 20 | prop2meth.p2m() 21 | prop2meth.p2m // <- should call with parens 22 | } 23 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/Postfix.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | import scala.language.postfixOps 7 | 8 | class Postfix { 9 | self meth; 10 | //self meth(); // won't typecheck - parsed as self.meth(()) 11 | 12 | 13 | qual meth; 14 | qual() meth; 15 | 16 | //qual meth(); // won't typecheck - parsed as qual.meth(()) 17 | //qual() meth(); // won't typecheck - parsed as qual().meth(()) 18 | } 19 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/ToString.scala: -------------------------------------------------------------------------------- 1 | /* 2 | rule = fix.scala213.ExplicitNonNullaryApply 3 | */ 4 | package fix.scala213.explicitNonNullaryApply 5 | 6 | class ToString { 7 | ???.toString() 8 | ???.toString 9 | 10 | any.toString() 11 | any.toString 12 | 13 | ref.toString() 14 | ref.toString 15 | 16 | obj.toString() 17 | obj.toString 18 | 19 | str.toString() 20 | str.toString 21 | 22 | cm.toString() 23 | cm.toString 24 | cp.toString() 25 | cp.toString 26 | 27 | vcm.toString() 28 | vcm.toString 29 | vcp.toString() 30 | vcp.toString 31 | 32 | ccm.toString() 33 | ccm.toString 34 | ccp.toString() 35 | ccp.toString 36 | 37 | vccm.toString() 38 | vccm.toString 39 | vccp.toString() 40 | vccp.toString 41 | } 42 | -------------------------------------------------------------------------------- /input/src/test/scala/fix/scala213/explicitNonNullaryApply/package.scala: -------------------------------------------------------------------------------- 1 | /**/ 2 | package fix.scala213 3 | 4 | // Definitions to test ExplicitNonNullaryApply against 5 | package object explicitNonNullaryApply { 6 | def self = this 7 | def qual() = this 8 | 9 | def prop = "" 10 | def meth() = "" 11 | 12 | def id[A](x: A) = x 13 | 14 | def imp()(implicit z: DummyImplicit) = "" 15 | 16 | val any: Any = "" 17 | val ref: AnyRef = "" 18 | val obj: Object = "" 19 | val str: String = "" 20 | 21 | val cm: CM = new CM() 22 | val cp: CP = new CP() 23 | val vcm: VCM = new VCM("") 24 | val vcp: VCP = new VCP("") 25 | val ccm: CCM = CCM() 26 | val ccp: CCP = CCP() 27 | val vccm: VCCM = VCCM("") 28 | val vccp: VCCP = VCCP("") 29 | } 30 | 31 | package explicitNonNullaryApply { 32 | class CM extends Runnable with Fooable { override def toString() = ""; def run() = (); def foo() = "" } 33 | class CP extends Runnable with Fooable { override def toString = ""; def run = (); def foo = "" } 34 | 35 | case class CCM() extends Runnable with Fooable { override def toString() = ""; def run() = (); def foo() = "" } 36 | case class CCP() extends Runnable with Fooable { override def toString = ""; def run = (); def foo = "" } 37 | 38 | class VCM(val x: String) extends AnyVal { override def toString() = "" } 39 | class VCP(val x: String) extends AnyVal { override def toString = "" } 40 | 41 | case class VCCM(x: String) extends AnyVal { override def toString() = "" } 42 | case class VCCP(x: String) extends AnyVal { override def toString = "" } 43 | } 44 | -------------------------------------------------------------------------------- /output/src/test/java/fix/scala213/explicitNonNullaryApply/Fooable.java: -------------------------------------------------------------------------------- 1 | package fix.scala213.explicitNonNullaryApply; 2 | 3 | // A Java interface to test ExplicitNonNullaryApply against 4 | interface Fooable { String foo(); } 5 | -------------------------------------------------------------------------------- /output/src/test/scala-2.12/fix/scala213/Console212.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.io.StdIn 4 | object Console212 { 5 | def consoleReadBoolean = StdIn.readBoolean() 6 | def consoleReadByte = StdIn.readByte() 7 | def consoleReadChar = StdIn.readChar() 8 | def consoleReadDouble = StdIn.readDouble() 9 | def consoleReadFloat = StdIn.readFloat() 10 | def consoleReadInt = StdIn.readInt() 11 | def consoleReadLine = StdIn.readLine() 12 | def consoleReadLine(text: String, args: Any*) = StdIn.readLine(text, args: _*) 13 | def consoleReadLong = StdIn.readLong() 14 | def consoleReadShort = StdIn.readShort() 15 | def consoleReadf(format: String) = StdIn.readf(format) 16 | def consoleReadf1(format: String) = StdIn.readf1(format) 17 | def consoleReadf2(format: String) = StdIn.readf2(format) 18 | def consoleReadf3(format: String) = StdIn.readf3(format) 19 | } 20 | -------------------------------------------------------------------------------- /output/src/test/scala-2.12/fix/scala213/NullaryHashHash212.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | object NullaryHashHash212 { 4 | ("": Any).## 5 | ("": AnyRef).## 6 | ("": Object).## 7 | ("": String).## 8 | "".## 9 | } 10 | -------------------------------------------------------------------------------- /output/src/test/scala-2.12/fix/scala213/Varargs212.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | class Varargs212 { 4 | def foo(xs: scala.collection.Seq[String]) = List(xs.toSeq: _*) 5 | } 6 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/Any2StringAdd.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213 3 | 4 | abstract class Any2StringAdd { 5 | // Strings: leave as-is, both literal strings and non-literal 6 | def s = "bob" 7 | def str1 = s + s 8 | def str2 = s + "bob" 9 | def str3 = "bob" + s 10 | def str4 = "bob" + "fred" 11 | 12 | // Non-strings: wrap with String.valueOf 13 | def n: Any = null 14 | def nil = String.valueOf(Nil) + s 15 | def none = String.valueOf(None) + s 16 | def Null = String.valueOf(n) + s 17 | 18 | // Non-string, generic type: wrap with String.valueOf 19 | type A 20 | def x: A 21 | def generic = String.valueOf(x) + "bob" 22 | 23 | // Numeric primitives: add toString 24 | def byte = 1.toByte 25 | def short = 1.toShort 26 | def char = 'a' 27 | def int = 1 28 | def long = 1L 29 | def float = 1.0F 30 | def double = 1.0 31 | // 32 | def byte1 = "" + byte + s 33 | def byte2 = byte + byte 34 | def short1 = "" + short + s 35 | def short2 = short + short 36 | def char1 = "" + char + s 37 | def char2 = char + char 38 | def int1 = "" + int + s 39 | def int2 = int + int 40 | def long1 = "" + long + s 41 | def long2 = long + long 42 | def float1 = "" + float + s 43 | def float2 = float + float 44 | def double1 = "" + double + s 45 | def double2 = double + double 46 | 47 | // Boolean: wrap with String.valueOf (there's no + on Boolean, AFAICT) 48 | def bool = true 49 | def bool1 = String.valueOf(bool) + s 50 | 51 | // Scala's Unit primitive: wrap with String.valueOf (there's no + on Unit, AFAICT) 52 | def unit = () 53 | def unit1 = String.valueOf(unit) + s 54 | 55 | // Custom value types: wrap with String.valueOf (adding toString would be preferable) 56 | import Any2StringAdd.Name 57 | //val name: Name = null // type mismatch; found: Null(null), required: Name 58 | val name = new Name(null) 59 | def name1 = String.valueOf(name) + s 60 | 61 | // With infix operators, make sure to use parens 62 | def parens1 = String.valueOf(Nil ++ Nil) + s 63 | def parens2 = "" + (int + int) + s 64 | def parens3 = String.valueOf({Nil ++ Nil}) + s 65 | } 66 | 67 | object Any2StringAdd { 68 | final class Name(val value: String) extends AnyVal { override def toString = value } 69 | } 70 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/Core.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.compat.Platform 4 | 5 | object Core { 6 | def eol1 = "Hello World!" + System.lineSeparator 7 | def eol2 = s"Hello World!${System.lineSeparator}" 8 | def elo2b = s"Hello World!${{System.lineSeparator}}" 9 | def eol3 = "Hello World!" + System.lineSeparator 10 | def eol4 = s"Hello World!${System.lineSeparator}" 11 | def eol5 = "Hello World!" + System.lineSeparator 12 | def eol6 = s"Hello World!${System.lineSeparator}" 13 | 14 | def currentTimeMillis1 = System.currentTimeMillis 15 | def currentTimeMillis2 = System.currentTimeMillis 16 | def currentTimeMillis3 = System.currentTimeMillis 17 | def currentTimeMillis4 = s"now: ${System.currentTimeMillis}" 18 | 19 | def arrayCopy1() = System.arraycopy(null, 0, null, 0, 0) 20 | def arrayCopy2() = System.arraycopy(null, 0, null, 0, 0) 21 | def arrayCopy3() = System.arraycopy(null, 0, null, 0, 0) 22 | 23 | def arrow1: PartialFunction[Any, String] = { 24 | case 0 => "zero" 25 | case 1 => "one" 26 | case 2 => "two" 27 | case n: Int => "ginormous" 28 | case "⇒" => "?" 29 | } 30 | def arrow2(f: Int => String) = f(1) 31 | def arrow3 = { 32 | import scala.{PartialFunction => ?=>} 33 | def f: Int ?=> String = { 34 | case 1 => "one" 35 | } 36 | } 37 | def arrow4 = Some(1).map(x => x.toString) 38 | 39 | def singleArrow1: (Int, Int) = 1 -> 1 40 | def singleArrow2: (Int, Int) = 1.->(1) 41 | 42 | def symbolLiteral0 = Symbol("foo") 43 | def symbolLiteral1 = Symbol("foo") 44 | } 45 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/NoAutoApply.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213 3 | 4 | object NoAutoApply { 5 | object buz { 6 | override def toString(): String = "" 7 | def empty[T]() = List.empty[T] 8 | } 9 | val x: Iterator[Int] = ??? 10 | def foo() = println(1) 11 | def bar = 32 12 | foo() 13 | println(foo()) 14 | foo() 15 | bar 16 | x.next() 17 | class buz() { 18 | def qux() = List(1) 19 | } 20 | new buz().qux().toIterator.next() 21 | new java.util.ArrayList[Int]().toString 22 | val builder = List.newBuilder[Int] 23 | builder.result() 24 | //builder result () 25 | builder.result() 26 | fix.scala213.NoAutoApply.buz.empty[String]() 27 | var opt: Option[() => Int] = None 28 | opt = None 29 | println(1.toString) 30 | println(buz.toString) // not inserted 31 | List(builder) map (_.result()) 32 | builder.## 33 | def lzy(f: => Int) = { 34 | var k = f _ 35 | k = () => 3 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/NullaryEtaExpansion.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | class NullaryEtaExpansion { 4 | val str1 = "" 5 | def prop = "" 6 | def meth() = "" 7 | def void(x: Any) = "" 8 | 9 | def def_str1 = () => str1 // rewrite 10 | def def_prop = () => prop // rewrite 11 | def def_meth = meth _ // leave 12 | def def_idty = void _ // leave 13 | 14 | val val_str1 = () => str1 // rewrite 15 | val val_prop = () => prop // rewrite 16 | val val_meth = meth _ // leave 17 | val val_idty = void _ // leave 18 | 19 | var var_str1 = () => str1 // rewrite 20 | var var_prop = () => prop // rewrite 21 | var var_meth = meth _ // leave 22 | var var_idty = void _ // leave 23 | 24 | lazy val lzy_str1 = () => str1 // rewrite 25 | lazy val lzy_prop = () => prop // rewrite 26 | lazy val lzy_meth = meth _ // leave 27 | lazy val lzy_idty = void _ // leave 28 | } 29 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/NullaryEtaExpansionJava.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | class NullaryEtaExpansionJava { 4 | class ToS1 { override def toString(): String = "" } 5 | class ToS2 { override def toString: String = "" } 6 | class ToS3 { override val toString: String = "" } 7 | class ToS4 { override lazy val toString: String = "" } 8 | 9 | val toS1 = new ToS1 10 | val toS2 = new ToS2 11 | val toS3 = new ToS3 12 | val toS4 = new ToS4 13 | 14 | val val_toS1 = toS1.toString _ 15 | var var_toS1 = toS1.toString _ 16 | def def_toS1 = toS1.toString _ 17 | lazy val lzy_toS1 = toS1.toString _ 18 | 19 | val val_toS2 = toS2.toString _ 20 | var var_toS2 = toS2.toString _ 21 | def def_toS2 = toS2.toString _ 22 | lazy val lzy_toS2 = toS2.toString _ 23 | 24 | val val_toS3 = () => toS3.toString 25 | var var_toS3 = () => toS3.toString 26 | def def_toS3 = () => toS3.toString 27 | lazy val lzy_toS3 = () => toS3.toString 28 | 29 | val val_toS4 = () => toS4.toString 30 | var var_toS4 = () => toS4.toString 31 | def def_toS4 = () => toS4.toString 32 | lazy val lzy_toS4 = () => toS4.toString 33 | } 34 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/NullaryHashHash.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | object NullaryHashHash { 4 | ("": Any).## 5 | ("": AnyRef).## 6 | ("": Object).## 7 | ("": String).## 8 | "".## 9 | } 10 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/ScalaSeq.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.{collection => sc} 4 | import scala.collection.{immutable => sci} 5 | class ScalaSeq { 6 | def f(a: sc.Seq[Int], b: List[sc.Seq[Int]]): sci.Seq[Any] = List(a, b) 7 | } 8 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/Varargs.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | class Varargs { 4 | def bar = List(List(1,2,3): _*) 5 | def baz = List(scala.Seq(1,2,3): _*) 6 | // TODO 7 | // def biz = List(scala.collection.mutable.ArrayBuffer(1,2): _*) 8 | } 9 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/ApplyType.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class ApplyType { 5 | object Props { def apply[A]() = this } 6 | object Props2 { def apply[A, B]() = this } 7 | object Nullary { def apply[A] = this } 8 | 9 | Props.apply[Int]() // fix 10 | Props[Int]() // fix 11 | Props[Int]() // keep 12 | 13 | Props2.apply[Int, String]() // fix 14 | Props2[Int, String]() // fix 15 | 16 | Nullary[Int] // keep 17 | } 18 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/Foo.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class Foo { 5 | cm.foo() 6 | cm.foo 7 | cp.foo() 8 | cp.foo 9 | } 10 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/Implicit.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class Implicit { 5 | imp() // fix 6 | imp() // keep 7 | } 8 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/Infix.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class Infix { 5 | //this meth () // no point, parses as "this.meth(())" which doesn't typecheck 6 | 7 | qual() id qual() 8 | qual() id qual() 9 | qual() id qual() 10 | qual() id qual() 11 | 12 | qual() id (qual()) 13 | qual() id ((qual())) 14 | qual() id (((qual()))) 15 | 16 | qual() id[Any] qual() 17 | } 18 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/NonNullary.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class NonNullary { 5 | meth() 6 | meth() 7 | 8 | self.meth() 9 | self.meth() 10 | 11 | qual().meth() 12 | qual().meth() 13 | 14 | qual().meth() 15 | qual().meth() 16 | 17 | 18 | meth _ 19 | 20 | 21 | ???.getClass() 22 | ???.getClass 23 | 24 | any.getClass() 25 | any.getClass 26 | 27 | ref.getClass() 28 | ref.getClass 29 | 30 | obj.getClass() 31 | obj.getClass 32 | 33 | str.getClass() 34 | str.getClass 35 | 36 | 37 | ???.hashCode() 38 | ???.hashCode 39 | 40 | any.hashCode() 41 | any.hashCode 42 | 43 | ref.hashCode() 44 | ref.hashCode 45 | 46 | obj.hashCode() 47 | obj.hashCode 48 | 49 | str.hashCode() 50 | str.hashCode 51 | 52 | 53 | cm.run() 54 | cm.run 55 | cp.run() 56 | cp.run 57 | } 58 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/Nullary.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class Nullary { 5 | prop 6 | self.prop 7 | qual().prop 8 | qual().prop 9 | 10 | ???.## 11 | any.## 12 | ref.## 13 | obj.## 14 | str.## 15 | 16 | ???.asInstanceOf[Int] 17 | any.asInstanceOf[Int] 18 | ref.asInstanceOf[Int] 19 | obj.asInstanceOf[Int] 20 | str.asInstanceOf[Int] 21 | 22 | ???.isInstanceOf[Int] 23 | any.isInstanceOf[Int] 24 | ref.isInstanceOf[Int] 25 | obj.isInstanceOf[Int] 26 | str.isInstanceOf[Int] 27 | } 28 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/Override.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | // ExplicitNonNullaryApply, when methods are overridden 5 | class Override { 6 | class Meth { def m2p() = "" } 7 | class Prop { def p2m = "" } 8 | 9 | class Meth2Prop extends Meth { override def m2p = "" } 10 | class Prop2Meth extends Prop { override def p2m() = "" } 11 | 12 | val meth2prop = new Meth2Prop 13 | val prop2meth = new Prop2Meth 14 | 15 | meth2prop.m2p() 16 | meth2prop.m2p() // <- should call with parens 17 | 18 | prop2meth.p2m() 19 | prop2meth.p2m() // <- should call with parens 20 | } 21 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/Postfix.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | import scala.language.postfixOps 5 | 6 | class Postfix { 7 | self.meth(); 8 | //self meth(); // won't typecheck - parsed as self.meth(()) 9 | 10 | 11 | qual().meth(); 12 | qual().meth(); 13 | 14 | //qual meth(); // won't typecheck - parsed as qual.meth(()) 15 | //qual() meth(); // won't typecheck - parsed as qual().meth(()) 16 | } 17 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/ToString.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213.explicitNonNullaryApply 3 | 4 | class ToString { 5 | ???.toString() 6 | ???.toString 7 | 8 | any.toString() 9 | any.toString 10 | 11 | ref.toString() 12 | ref.toString 13 | 14 | obj.toString() 15 | obj.toString 16 | 17 | str.toString() 18 | str.toString 19 | 20 | cm.toString() 21 | cm.toString 22 | cp.toString() 23 | cp.toString 24 | 25 | vcm.toString() 26 | vcm.toString 27 | vcp.toString() 28 | vcp.toString 29 | 30 | ccm.toString() 31 | ccm.toString 32 | ccp.toString() 33 | ccp.toString 34 | 35 | vccm.toString() 36 | vccm.toString 37 | vccp.toString() 38 | vccp.toString 39 | } 40 | -------------------------------------------------------------------------------- /output/src/test/scala/fix/scala213/explicitNonNullaryApply/package.scala: -------------------------------------------------------------------------------- 1 | 2 | package fix.scala213 3 | 4 | // Definitions to test ExplicitNonNullaryApply against 5 | package object explicitNonNullaryApply { 6 | def self = this 7 | def qual() = this 8 | 9 | def prop = "" 10 | def meth() = "" 11 | 12 | def id[A](x: A) = x 13 | 14 | def imp()(implicit z: DummyImplicit) = "" 15 | 16 | val any: Any = "" 17 | val ref: AnyRef = "" 18 | val obj: Object = "" 19 | val str: String = "" 20 | 21 | val cm: CM = new CM() 22 | val cp: CP = new CP() 23 | val vcm: VCM = new VCM("") 24 | val vcp: VCP = new VCP("") 25 | val ccm: CCM = CCM() 26 | val ccp: CCP = CCP() 27 | val vccm: VCCM = VCCM("") 28 | val vccp: VCCP = VCCP("") 29 | } 30 | 31 | package explicitNonNullaryApply { 32 | class CM extends Runnable with Fooable { override def toString() = ""; def run() = (); def foo() = "" } 33 | class CP extends Runnable with Fooable { override def toString = ""; def run = (); def foo = "" } 34 | 35 | case class CCM() extends Runnable with Fooable { override def toString() = ""; def run() = (); def foo() = "" } 36 | case class CCP() extends Runnable with Fooable { override def toString = ""; def run = (); def foo = "" } 37 | 38 | class VCM(val x: String) extends AnyVal { override def toString() = "" } 39 | class VCP(val x: String) extends AnyVal { override def toString = "" } 40 | 41 | case class VCCM(x: String) extends AnyVal { override def toString() = "" } 42 | case class VCCP(x: String) extends AnyVal { override def toString = "" } 43 | } 44 | -------------------------------------------------------------------------------- /project/ScalaNightlyPlugin.scala: -------------------------------------------------------------------------------- 1 | import sbt._, Keys._ 2 | 3 | object ScalaNightlyPlugin extends AutoPlugin { 4 | val BinCompatV = """(\d+)\.(\d+)\.(\d+)(-\w+)??(-bin-.*)?""".r 5 | val isScalaNightly = sys.env.get("CI_SCALA_VERSION").contains("2.12.next") 6 | def ifNightly(ss: SettingsDefinition*) = if (isScalaNightly) Def.settings(ss: _*) else Nil 7 | 8 | override def buildSettings = ifNightly( 9 | resolvers += "scala-integration" at "https://scala-ci.typesafe.com/artifactory/scala-integration/", 10 | ) 11 | 12 | def bootstrapSettings = ifNightly( 13 | scalaVersion := { 14 | val props = new java.util.Properties 15 | val url1 = url("https://raw.github.com/scala/community-build/2.12.x/nightly.properties") 16 | sbt.io.Using.urlInputStream(url1)(props.load(_)) 17 | val sv = props.getProperty("nightly").ensuring(_ != null) 18 | sLog.value.info(s"Using Scala nightly version $sv") 19 | sv 20 | }, 21 | ) 22 | 23 | override def projectSettings = ifNightly( 24 | scalaVersion := ((LocalRootProject / scalaVersion).value match { 25 | case BinCompatV(x, y, z, _, _) => s"$x.$y.${z.toInt - 1}" 26 | case sv => sv 27 | }), 28 | scalaInstance := { 29 | val si = (LocalRootProject / scalaInstance).value 30 | new sbt.internal.inc.ScalaInstance( 31 | scalaVersion.value, 32 | si.loader, si.loaderCompilerOnly, si.loaderLibraryOnly, 33 | si.libraryJars, si.compilerJars, si.allJars, 34 | si.explicitActual 35 | ) 36 | } 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.9.8 2 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.9.2") 2 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.11.0") 3 | -------------------------------------------------------------------------------- /rewrites/src/main/resources/META-INF/services/scalafix.v1.Rule: -------------------------------------------------------------------------------- 1 | fix.scala213.Any2StringAdd 2 | fix.scala213.Core 3 | fix.scala213.ExplicitNonNullaryApply 4 | fix.scala213.ExplicitNullaryEtaExpansion 5 | fix.scala213.NullaryHashHash 6 | fix.scala213.ScalaSeq 7 | fix.scala213.Varargs 8 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/Any2StringAdd.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scalafix.v1._ 4 | 5 | import scala.meta._ 6 | 7 | object Any2StringAdd { 8 | val any2stringaddPlusString = SymbolMatcher.exact("scala/Predef.any2stringadd#`+`().") 9 | val primitivePlusString = SymbolMatcher.exact( 10 | "scala/Byte#`+`().", 11 | "scala/Short#`+`().", 12 | "scala/Char#`+`().", 13 | "scala/Int#`+`().", 14 | "scala/Long#`+`().", 15 | "scala/Float#`+`().", 16 | "scala/Double#`+`().", 17 | ) 18 | } 19 | 20 | final class Any2StringAdd extends SemanticRule("fix.scala213.Any2StringAdd") { 21 | import Any2StringAdd._ 22 | 23 | override def fix(implicit doc: SemanticDocument): Patch = { 24 | doc.tree.collect { 25 | case any2stringaddPlusString(Term.ApplyInfix.Initial(lhs, _, _, _)) => wrapStringValueOf(lhs) 26 | case primitivePlusString(Term.ApplyInfix.Initial(lhs, _, _, _)) => blankStringPlus(lhs) 27 | }.asPatch 28 | } 29 | 30 | private def wrapStringValueOf(term: Term) = 31 | Patch.addLeft(term, "String.valueOf(") + Patch.addRight(term, ")") 32 | 33 | private def blankStringPlus(term: Term) = term match { 34 | case _: Term.Name | _: Term.Select | _: Term.Block => Patch.addLeft(term, """"" + """) 35 | case _ => Patch.addLeft(term, """"" + (""") + Patch.addRight(term, ")") 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/Core.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.meta._ 4 | 5 | import scalafix.v1._ 6 | 7 | final class Core extends SemanticRule("fix.scala213.Core") { 8 | import Core._ 9 | 10 | override def fix(implicit doc: SemanticDocument) = new impl.ReplacingTraverser { 11 | val platformEOL: Replacer = { 12 | case EOL(i: Importee) => Patch.removeImportee(i) 13 | case EOL(t: Term) => replaceTree(t, "System.lineSeparator") 14 | } 15 | 16 | val platformCurrentTime: Replacer = { 17 | case currentTime(i: Importee) => Patch.removeImportee(i) 18 | case currentTime(t: Term) => replaceTree(t, "System.currentTimeMillis") 19 | } 20 | 21 | val platformArraycopy: Replacer = { 22 | case arraycopy(i: Importee) => Patch.removeImportee(i) 23 | case arraycopy(Term.Apply.Initial(t, _)) => replaceTree(t, "System.arraycopy") 24 | } 25 | 26 | val consoleRead: Replacer = { 27 | case deprecatedConsoleReadBoolean(Term.Apply.Initial(t, _)) => stdInReplace(t, "readBoolean") 28 | case deprecatedConsoleReadByte( Term.Apply.Initial(t, _)) => stdInReplace(t, "readByte") 29 | case deprecatedConsoleReadChar( Term.Apply.Initial(t, _)) => stdInReplace(t, "readChar") 30 | case deprecatedConsoleReadDouble( Term.Apply.Initial(t, _)) => stdInReplace(t, "readDouble") 31 | case deprecatedConsoleReadFloat( Term.Apply.Initial(t, _)) => stdInReplace(t, "readFloat") 32 | case deprecatedConsoleReadInt( Term.Apply.Initial(t, _)) => stdInReplace(t, "readInt") 33 | case deprecatedConsoleReadLine( Term.Apply.Initial(t, _)) => stdInReplace(t, "readLine") 34 | case deprecatedConsoleReadLine1( Term.Apply.Initial(t, _)) => stdInReplace(t, "readLine") 35 | case deprecatedConsoleReadLong( Term.Apply.Initial(t, _)) => stdInReplace(t, "readLong") 36 | case deprecatedConsoleReadShort( Term.Apply.Initial(t, _)) => stdInReplace(t, "readShort") 37 | case deprecatedConsoleReadf( Term.Apply.Initial(t, _)) => stdInReplace(t, "readf") 38 | case deprecatedConsoleReadf1( Term.Apply.Initial(t, _)) => stdInReplace(t, "readf1") 39 | case deprecatedConsoleReadf2( Term.Apply.Initial(t, _)) => stdInReplace(t, "readf2") 40 | case deprecatedConsoleReadf3( Term.Apply.Initial(t, _)) => stdInReplace(t, "readf3") 41 | } 42 | 43 | private def stdInReplace(tree: Tree, name: String): Patch = 44 | replaceTree(tree, s"StdIn.$name") + globalImports.add(importer"scala.io.StdIn") 45 | 46 | val unicodeArrows: Replacer = { 47 | case t: Case => replaceTokens(t, "⇒", "=>") 48 | case t: Type.Function => replaceTokens(t, "⇒", "=>") 49 | case t: Term.Function => replaceTokens(t, "⇒", "=>") 50 | case t: Importee => replaceTokens(t, "⇒", "=>") 51 | case arrowAssoc(t: Term.Name) => replaceTree(t, "->") 52 | } 53 | 54 | val symbolLiteral: Replacer = { 55 | case t @ Lit.Symbol(sym) => replaceTree(t, s"""Symbol("${sym.name}")""") 56 | } 57 | 58 | val replacers = List( 59 | platformEOL, 60 | platformCurrentTime, 61 | platformArraycopy, 62 | consoleRead, 63 | unicodeArrows, 64 | symbolLiteral, 65 | ) 66 | }.run(doc.tree) 67 | } 68 | 69 | object Core { 70 | private def replaceTokens(t: Tree, from: String, to: String): Patch = 71 | t.tokens.collect { case tok if tok.text == from => Patch.replaceToken(tok, to) }.asPatch 72 | 73 | private val EOL = SymbolMatcher.exact("scala/compat/Platform.EOL.") 74 | private val currentTime = SymbolMatcher.exact("scala/compat/Platform.currentTime().") 75 | private val arraycopy = SymbolMatcher.exact("scala/compat/Platform.arraycopy().") 76 | 77 | private val deprecatedConsoleReadBoolean = SymbolMatcher.exact("scala/DeprecatedConsole#readBoolean().") 78 | private val deprecatedConsoleReadByte = SymbolMatcher.exact("scala/DeprecatedConsole#readByte().") 79 | private val deprecatedConsoleReadChar = SymbolMatcher.exact("scala/DeprecatedConsole#readChar().") 80 | private val deprecatedConsoleReadDouble = SymbolMatcher.exact("scala/DeprecatedConsole#readDouble().") 81 | private val deprecatedConsoleReadFloat = SymbolMatcher.exact("scala/DeprecatedConsole#readFloat().") 82 | private val deprecatedConsoleReadInt = SymbolMatcher.exact("scala/DeprecatedConsole#readInt().") 83 | private val deprecatedConsoleReadLine = SymbolMatcher.exact("scala/DeprecatedConsole#readLine().") 84 | private val deprecatedConsoleReadLine1 = SymbolMatcher.exact("scala/DeprecatedConsole#readLine(+1).") 85 | private val deprecatedConsoleReadLong = SymbolMatcher.exact("scala/DeprecatedConsole#readLong().") 86 | private val deprecatedConsoleReadShort = SymbolMatcher.exact("scala/DeprecatedConsole#readShort().") 87 | private val deprecatedConsoleReadf = SymbolMatcher.exact("scala/DeprecatedConsole#readf().") 88 | private val deprecatedConsoleReadf1 = SymbolMatcher.exact("scala/DeprecatedConsole#readf1().") 89 | private val deprecatedConsoleReadf2 = SymbolMatcher.exact("scala/DeprecatedConsole#readf2().") 90 | private val deprecatedConsoleReadf3 = SymbolMatcher.exact("scala/DeprecatedConsole#readf3().") 91 | 92 | private val arrowAssoc = SymbolMatcher.exact("scala/Predef.ArrowAssoc#`→`().") 93 | } 94 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/ExplicitNonNullaryApply.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.PartialFunction.{ cond, condOpt } 4 | import scala.collection.mutable 5 | import scala.util.control.Exception.nonFatalCatch 6 | import scala.util.Try 7 | 8 | import metaconfig.Configured 9 | 10 | import scala.meta._ 11 | import scala.meta.internal.pc.ScalafixGlobal 12 | 13 | import scalafix.v1._ 14 | import scalafix.internal.rule.CompilerException 15 | import scalafix.internal.v1.LazyValue 16 | 17 | /** Explicitly insert () to non-nullary method applications that lack it. 18 | * https://dotty.epfl.ch/docs/reference/dropped-features/auto-apply.html 19 | * https://github.com/scala/scala/pull/8833 20 | */ 21 | final class ExplicitNonNullaryApply(global: LazyValue[ScalafixGlobal]) 22 | extends SemanticRule("fix.scala213.ExplicitNonNullaryApply") 23 | { 24 | def this() = this(LazyValue.later(() => ScalafixGlobal.newCompiler(Nil, Nil, Map.empty).get)) 25 | 26 | override def fix(implicit doc: SemanticDocument): Patch = { 27 | try unsafeFix() catch { 28 | case _: CompilerException => 29 | // Give it another shot (good old "retry.retry") 30 | // as the presentation compiler sometimes just dies and succeeds the next time... 31 | shutdownAndResetCompiler() 32 | try unsafeFix() catch { 33 | case _: CompilerException => 34 | // Give up on fixing this file as compiling it crashed the (presentation) compiler twice 35 | // but first reset the state of the compiler for the next file 36 | shutdownAndResetCompiler() 37 | Patch.empty 38 | } 39 | } 40 | } 41 | 42 | private def unsafeFix()(implicit doc: SemanticDocument) = { 43 | lazy val power = new impl.Power(global.value) 44 | val handled = mutable.Set.empty[Term.Name] 45 | 46 | def isJavaDefined(name: Term.Name): Boolean = name.value match { 47 | case "toString" => true // fast-track known, common cases 48 | case "getClass" => true 49 | case "hashCode" => true 50 | case "asInstanceOf" => true 51 | case "isInstanceOf" => true 52 | case _ => power.isJavaDefined(name) 53 | } 54 | 55 | def fix(tree: Tree, meth: Term, noTypeArgs: Boolean, noArgs: Boolean) = { 56 | for { 57 | name <- termName(meth) 58 | if name.value != "##" // fast-track https://github.com/scala/scala/pull/8814 59 | if handled.add(name) 60 | if noArgs 61 | if name.isReference 62 | if !cond(name.parent) { case Some(Term.ApplyInfix.Initial(_, `name`, _, _)) => true } 63 | if !tree.parent.exists(_.is[Term.Eta]) // else rewrites `meth _` to `meth() _`, or requires running ExplicitNullaryEtaExpansion first 64 | // HACK: In certain cases, `Symbol.info` may throw `MissingSymbolException` due to some unknown reason 65 | // If it happens, here we assume that this symbol has no info 66 | info <- Try(name.symbol.info).toOption.flatten 67 | if !info.isJava // shallow, isJavaDefined (below) checks overrides 68 | if cond(info.signature) { 69 | case MethodSignature(_, Nil :: _, _) => true 70 | case ClassSignature(_, _, _, decls) if tree.isInstanceOf[Term.ApplyType] => 71 | decls.exists { decl => 72 | decl.displayName == "apply" && 73 | cond(decl.signature) { case MethodSignature(_, Nil :: _, _) => true } 74 | } 75 | } 76 | if !isJavaDefined(name) // full check, using the presentation compiler :O 77 | } yield { 78 | val optAddDot = name.parent.collect { 79 | case PostfixSelect(qual, `name`) => 80 | Patch.removeTokens(doc.tokenList.trailingSpaces(qual.tokens.last)) + 81 | Patch.addLeft(name.tokens.head, ".") 82 | } 83 | val target = if (noTypeArgs) name else tree 84 | // WORKAROUND scalameta/scalameta#1083 85 | // Fixes `lhs op (arg)` from being rewritten as `lhs op (arg)()` (instead of `lhs op (arg())`) 86 | val token = target.tokens.reverseIterator.find(!_.is[Token.RightParen]).get 87 | optAddDot.asPatch + Patch.addRight(token, "()") 88 | } 89 | }.asPatch 90 | 91 | doc.tree.collect { 92 | case t @ q"$meth[..$targs](...$args)" => fix(t, meth, targs.isEmpty, args.isEmpty) 93 | case t @ q"$meth(...$args)" => fix(t, meth, true, args.isEmpty) 94 | }.asPatch 95 | } 96 | 97 | // No PostfixSelect in Scalameta, so build one 98 | private object PostfixSelect { 99 | def unapply(t: Tree)(implicit doc: SemanticDocument): Option[(Term, Term.Name)] = t match { 100 | case Term.Select(qual, name) => 101 | val tokenList = doc.tokenList 102 | val inBetweenSlice = tokenList.slice(tokenList.next(qual.tokens.last), name.tokens.head) 103 | if (inBetweenSlice.exists(_.is[Token.Dot])) None 104 | else Some((qual, name)) 105 | case _ => None 106 | } 107 | } 108 | 109 | private def termName(term: Term): Option[Term.Name] = condOpt(term) { 110 | case name: Term.Name => name 111 | case Term.Select(_, name: Term.Name) => name 112 | } 113 | 114 | override def withConfiguration(config: Configuration) = { 115 | val compileSv = config.scalaVersion 116 | val runtimeSv = scala.util.Properties.versionNumberString 117 | if ((compileSv.take(4) != runtimeSv.take(4)) && config.scalacOptions.nonEmpty) { 118 | Configured.error( 119 | s"Scala version mismatch: " + 120 | s"(1) the target sources were compiled with Scala $compileSv; " + 121 | s"(2) Scalafix is running on Scala $runtimeSv. " + 122 | s"To fix make scalafixScalaBinaryVersion == ${compileSv.take(4)}. " + 123 | "Try `ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value)`." 124 | ) 125 | } else { 126 | Configured.ok(new ExplicitNonNullaryApply(LazyValue.later { () => 127 | ScalafixGlobal.newCompiler(config.scalacClasspath, config.scalacOptions, Map.empty).get 128 | })) 129 | } 130 | } 131 | 132 | override def afterComplete() = shutdownAndResetCompiler() 133 | 134 | def shutdownAndResetCompiler() = { 135 | for (g <- global) { 136 | nonFatalCatch { 137 | g.askShutdown() 138 | g.close() 139 | } 140 | } 141 | global.restart() // more of a "reset", as nothing's eagerly started 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/ExplicitNullaryEtaExpansion.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.meta._ 4 | 5 | import scalafix.v1._ 6 | 7 | final class ExplicitNullaryEtaExpansion extends SemanticRule("fix.scala213.ExplicitNullaryEtaExpansion") { 8 | override def fix(implicit doc: SemanticDocument) = { 9 | doc.tree.collect { case eta: Term.Eta => 10 | eta.expr.symbol.info.map(_.signature).collect { 11 | case MethodSignature(_, Nil, _) => 12 | val etaSuffix = eta.tokens.takeRight(eta.pos.end - eta.expr.pos.end) // the " _" after eta's expr 13 | Patch.addLeft(eta, "() => ") + Patch.removeTokens(etaSuffix) 14 | }.asPatch 15 | }.asPatch 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/NullaryHashHash.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.meta._ 4 | 5 | import scalafix.v1._ 6 | 7 | final class NullaryHashHash extends SemanticRule("fix.scala213.NullaryHashHash") { 8 | private val HashHash = SymbolMatcher.exact("scala/Any#`##`().", "java/lang/Object#`##`().") 9 | 10 | override def fix(implicit doc: SemanticDocument) = { 11 | doc.tree.collect { 12 | case expr @ Term.Apply.Initial(HashHash(fun), Nil) => 13 | Patch.removeTokens(expr.tokens.takeRight(expr.pos.end - fun.pos.end)) 14 | }.asPatch 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/ScalaSeq.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import metaconfig.generic.Surface 4 | import metaconfig.{ConfDecoder, Configured} 5 | 6 | import scala.meta._ 7 | 8 | import scalafix.v1._ 9 | 10 | final class ScalaSeq(val config: ScalaSeq.Config) extends SemanticRule("fix.scala213.ScalaSeq") { 11 | def this() = this(ScalaSeq.Config()) 12 | import ScalaSeq._ 13 | 14 | override def withConfiguration(config: Configuration): Configured[Rule] = { 15 | // the following don't seem to work: 16 | // - getOrElse("fix.scala213.ScalaSeq") 17 | // - getOrElse("fix", "scala213", "ScalaSeq") 18 | config.conf.dynamic.fix.scala213.asConf 19 | .andThen(_.getOrElse("ScalaSeq")(this.config)) 20 | .map(new ScalaSeq(_)) 21 | } 22 | 23 | override def fix(implicit doc: SemanticDocument) = new impl.CollectingTraverser { 24 | private var inParam = false 25 | 26 | private val paramImport: Option[Importer] = toImporter(config.paramImport) 27 | private val otherImport: Option[Importer] = toImporter(config.otherImport) 28 | private def toImporter(s: String) = if (s.isEmpty) None else Some(s.parse[Importer].get) 29 | 30 | override def apply(tree: Tree): Unit = tree match { 31 | case p: Term.Param => 32 | inParam = true 33 | apply(p.decltpe) 34 | inParam = false 35 | apply(p.default) 36 | 37 | case scalaSeq(Type.Apply.Initial(t, _)) => 38 | val sub = if (inParam) { 39 | paramImport.foreach(i => patch += globalImports.add(i)) 40 | config.paramType 41 | } else { 42 | otherImport.foreach(i => patch += globalImports.add(i)) 43 | config.otherType 44 | } 45 | patch += Patch.replaceTree(t, sub) 46 | 47 | case _ => 48 | super.apply(tree) 49 | } 50 | }.run(doc.tree) 51 | } 52 | 53 | object ScalaSeq { 54 | private val scalaSeq = SymbolMatcher.exact("scala/package.Seq#") 55 | 56 | final case class Config( 57 | paramType: String = "collection.Seq", 58 | otherType: String = "immutable.Seq", 59 | paramImport: String = "scala.collection", 60 | otherImport: String = "scala.collection.immutable", 61 | ) 62 | 63 | object Config { 64 | implicit val surface: Surface[Config] = metaconfig.generic.deriveSurface[Config] 65 | implicit val decoder: ConfDecoder[Config] = metaconfig.generic.deriveDecoder(new Config()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/fix/scala213/Varargs.scala: -------------------------------------------------------------------------------- 1 | package fix.scala213 2 | 3 | import scala.meta._ 4 | 5 | import scalafix.v1._ 6 | 7 | import impl.SignatureMatcher 8 | 9 | object Varargs { 10 | val scSeq = SignatureMatcher.exact("scala/collection/Seq#") 11 | }; import Varargs._ 12 | 13 | final class Varargs extends SemanticRule("fix.scala213.Varargs") { 14 | override def fix(implicit doc: SemanticDocument): Patch = 15 | doc.tree.collect { 16 | case Term.Repeated(scSeq(expr)) => Patch.addRight(expr, ".toSeq") 17 | }.asPatch 18 | } 19 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/impl/CollectingTraverser.scala: -------------------------------------------------------------------------------- 1 | package impl 2 | 3 | import scalafix.v1._ 4 | 5 | import scala.meta._ 6 | 7 | abstract class CollectingTraverser extends Traverser { 8 | final protected val globalImports = new GlobalImports 9 | final protected var patch = Patch.empty; 10 | 11 | final def run(tree: Tree): Patch = { apply(tree); patch } 12 | } 13 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/impl/GlobalImports.scala: -------------------------------------------------------------------------------- 1 | package impl 2 | 3 | import scalafix.v1._ 4 | 5 | import scala.collection.mutable 6 | import scala.meta._ 7 | 8 | final class GlobalImports { 9 | private val known = mutable.Set.empty[String] 10 | 11 | def add(importer: Importer): Patch = 12 | if (known.add(importer.syntax)) 13 | Patch.addGlobalImport(importer) 14 | else 15 | Patch.empty 16 | } 17 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/impl/Power.scala: -------------------------------------------------------------------------------- 1 | package impl 2 | 3 | import scala.meta._ 4 | import scala.meta.internal.pc.ScalafixGlobal 5 | 6 | import scalafix.v1._ 7 | import scalafix.internal.rule.CompilerException 8 | 9 | /** Necessary b/c Scalafix/Scalameta don't maintain any overriding info. */ 10 | // From scalafix-rules/src/main/scala/scalafix/internal/rule/CompilerTypePrinter.scala#L55 11 | // in scalacenter/scalafix (at db93793c2a8f80d0e7bbcacef729dc5c2fa7ceb8). 12 | final class Power(g: ScalafixGlobal)(implicit doc: SemanticDocument) { 13 | def isJavaDefined(t: Tree): Boolean = try { 14 | gsymbol(t).overrideChain.exists(sym => sym.isJavaDefined || sym.owner == g.definitions.AnyClass) 15 | } catch { 16 | case e: Throwable => throw CompilerException(e) 17 | } 18 | 19 | private def gsymbol(t: Tree): g.Symbol = { 20 | val sSym = t.symbol 21 | val gSymbols = g.inverseSemanticdbSymbols(sSym.value) 22 | gSymbols.find(gSym => g.semanticdbSymbol(gSym) == sSym.value).getOrElse(g.NoSymbol) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/impl/SignatureMatcher.scala: -------------------------------------------------------------------------------- 1 | package impl 2 | 3 | import scala.meta._ 4 | 5 | import scalafix.v1._ 6 | 7 | object SignatureMatcher { 8 | def exact(symbols: String*): SignatureMatcher = 9 | new SignatureMatcher(SymbolMatcher.exact(symbols: _*)) 10 | } 11 | 12 | final class SignatureMatcher(symbolMatcher: SymbolMatcher) { 13 | def unapply(tree: Tree)(implicit sdoc: SemanticDocument): Option[Tree] = 14 | tree.symbol.info.map(_.signature).collect { 15 | case ValueSignature(TypeRef(_, symbolMatcher(_), _)) => tree 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /rewrites/src/main/scala/impl/TreeReplacers.scala: -------------------------------------------------------------------------------- 1 | package impl 2 | 3 | import scala.PartialFunction.cond 4 | import scala.collection.mutable 5 | 6 | import scala.meta._ 7 | 8 | import scalafix.Patch 9 | 10 | abstract class ReplacingTraverser extends CollectingTraverser { 11 | type Replacer = PartialFunction[Tree, Patch] 12 | 13 | private val replaced = mutable.Set.empty[Tree] // replaced trees, so skip children 14 | 15 | def replacers: List[Replacer] 16 | 17 | final override def apply(tree: Tree) = { 18 | for (replacer <- replacers) 19 | replacer.runWith(patch += _)(tree) 20 | if (!replaced.contains(tree)) 21 | super.apply(tree) 22 | } 23 | 24 | final protected def replaceTree(from: Tree, to0: String): Patch = { 25 | def interpolating = from.parent.exists(_.isInstanceOf[Term.Interpolate]) 26 | def toParses = cond(to0.parse[Term]) { case Parsed.Success(_: Term.Name) => true } 27 | val to = from match { 28 | case _: Term.Name if interpolating && !toParses => s"{$to0}" 29 | case _ => to0 30 | } 31 | replaced += from 32 | Patch.replaceTree(from, to) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/src/test/scala/fix/BeforeAndAfterAllConfigMapAlt.scala: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | import org.scalatest._ 4 | 5 | object BeforeAndAfterAllConfigMapAlt { 6 | private def anExceptionThatShouldCauseAnAbort(throwable: Throwable): Boolean = throwable match { 7 | case _: java.lang.annotation.AnnotationFormatError | 8 | _: java.nio.charset.CoderMalfunctionError | 9 | _: javax.xml.parsers.FactoryConfigurationError | 10 | _: LinkageError | 11 | _: ThreadDeath | 12 | _: javax.xml.transform.TransformerFactoryConfigurationError | 13 | _: VirtualMachineError => true 14 | case e if e.getClass.getName == "java.awt.AWTError" => true 15 | case _ => false 16 | } 17 | }; import BeforeAndAfterAllConfigMapAlt._ 18 | 19 | trait BeforeAndAfterAllConfigMapAlt extends SuiteMixin { _: Suite => 20 | protected def beforeAllAlt(configMap: ConfigMap) = () 21 | protected def afterAllAlt(configMap: ConfigMap) = () 22 | 23 | abstract override def run(testName: Option[String], args: Args): Status = { 24 | val shouldRun = !args.runTestInNewInstance && expectedTestCount(args.filter) > 0 25 | var exception: Exception = null 26 | var status: Status = FailedStatus 27 | 28 | try { 29 | if (shouldRun) beforeAllAlt(args.configMap) 30 | status = super.run(testName, args) 31 | } catch {case e: Exception => exception = e} 32 | 33 | try { 34 | val statusToReturn = if (shouldRun) { 35 | status.withAfterEffect { 36 | try afterAllAlt(args.configMap) catch { 37 | case e: Exception if !anExceptionThatShouldCauseAnAbort(e) && exception != null => 38 | } 39 | } 40 | } else status 41 | if (exception != null) throw exception 42 | statusToReturn 43 | } catch { 44 | case e: Exception => 45 | if (exception != null) throw exception 46 | throw e 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /tests/src/test/scala/fix/RuleSuite.scala: -------------------------------------------------------------------------------- 1 | package fix 2 | 3 | import scala.reflect.ensureAccessible 4 | import org.scalatest.ConfigMap 5 | import org.scalatest.funspec.AnyFunSpecLike 6 | import scalafix.testkit.AbstractSemanticRuleSuite 7 | 8 | class RuleSuite extends AbstractSemanticRuleSuite with AnyFunSpecLike with BeforeAndAfterAllConfigMapAlt { 9 | val isSaveExpectField = ensureAccessible(classOf[AbstractSemanticRuleSuite].getDeclaredField("isSaveExpect")) 10 | 11 | // If you invoke the test as "tests/testOnly -- -Doverwrite=true" it will save the expected 12 | override protected def beforeAllAlt(configMap: ConfigMap) = { 13 | val overwrite = configMap.getWithDefault("overwrite", "false").equalsIgnoreCase("true") 14 | isSaveExpectField.setBoolean(this, overwrite) 15 | } 16 | 17 | runAllTests() 18 | } 19 | --------------------------------------------------------------------------------