Skip to content

Commit f47dc76

Browse files
committed
interfaces: add JSPlatform implementation
1 parent 51da9ed commit f47dc76

10 files changed

+375
-1
lines changed

build.sbt

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ lazy val dynamic = crossProject(JVMPlatform) // don't build for NativePlatform
8989
).dependsOn(interfaces, sysops).dependsOn(core % "test")
9090
.enablePlugins(BuildInfoPlugin)
9191

92-
lazy val interfaces = crossProject(JVMPlatform, NativePlatform)
92+
lazy val interfaces = crossProject(JVMPlatform, NativePlatform, JSPlatform)
9393
.withoutSuffixFor(JVMPlatform).in(file("scalafmt-interfaces")).settings(
9494
moduleName := "scalafmt-interfaces",
9595
description :=
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.scalafmt.interfaces
2+
3+
import java.nio.file.Path
4+
5+
/** An exception that happened at a position in a source file such as a parse
6+
* error.
7+
*/
8+
abstract class PositionException(message: String, cause: Throwable)
9+
extends Exception(message, cause) {
10+
11+
override def fillInStackTrace(): Throwable = synchronized(this)
12+
13+
/** @return
14+
* The file where the error occurred.
15+
*/
16+
def file(): Path
17+
18+
/** @return
19+
* The text contents of the file being formatted.
20+
*/
21+
def code(): String
22+
23+
/** @return
24+
* The fully formatted error message including line content and caret.
25+
*/
26+
def longMessage(): String
27+
28+
/** @return
29+
* Only the error message itself without line content and caret.
30+
*/
31+
def shortMessage(): String
32+
33+
def startLine(): Int
34+
def startCharacter(): Int
35+
def endLine(): Int
36+
def endCharacter(): Int
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.scalafmt.interfaces
2+
3+
final class RepositoryCredential(
4+
val host: String,
5+
val username: String,
6+
val password: String,
7+
)
8+
9+
object RepositoryCredential {
10+
trait ScalafmtExtension {
11+
def withRepositoryCredentials(credentials: RepositoryCredential*): Scalafmt
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package org.scalafmt.interfaces
2+
3+
import java.nio.file.Path
4+
import java.util
5+
import java.util.NoSuchElementException
6+
import java.util.ServiceLoader
7+
8+
/** A stable public interface to run Scalafmt.
9+
*
10+
* This interface is designed for integrations such as editor plugins and build
11+
* tools. An implementation of this interface is available in the module
12+
* 'scalafmt-dynamic'.
13+
*
14+
* It is recommended to use this interface over the org.scalafmt.Scalafmt
15+
* object for several reasons:
16+
*
17+
* - this API is guaranteed to be binary compatible with all future versions
18+
* of Scalafmt.
19+
* - it downloads the Scalafmt version matching the 'version' setting in
20+
* .scalafmt.conf. All versions down to v1.2.0 are supported.
21+
* - it respects the 'project.{excludeFilters,includeFilters}' setting in
22+
* .scalafmt.conf.
23+
* - it uses the correct parser for `*.sbt` and `*.sc` files.
24+
* - it automatically caches parsing of configuration files avoiding
25+
* redundant work when possible.
26+
* - it has two external library dependencies (com.geirsson:coursier-small
27+
* and com.typesafe:config), which is a smaller dependency footprint
28+
* compared to scalafmt-core.
29+
*/
30+
trait Scalafmt {
31+
32+
/** Format a single file with the given .scalafmt.conf configuration.
33+
*
34+
* @param config
35+
* the absolute path to the configuration file. This file must exist or an
36+
* exception will be thrown.
37+
* @param file
38+
* relative or absolute path to the file being formatted. Used only for the
39+
* path name, the file does not have to exist on disk.
40+
* @param code
41+
* the text contents to format.
42+
* @return
43+
* the formatted contents if formatting was successful, otherwise the
44+
* original text contents.
45+
*/
46+
def format(config: Path, file: Path, code: String): String
47+
48+
/** Builder method.
49+
*
50+
* @param respectExcludeFilters
51+
* If true (default), returns the original file contents when formatting a
52+
* file that does not matches the project settings in .scalafmt.conf. If
53+
* false, formats every file that is passed to the
54+
* {@link #format(Path, Path, String)} method regardless of .scalafmt.conf
55+
* settings.
56+
* @return
57+
* an updated interface instance controlling whether to respect the
58+
* 'project.{excludeFilters,includeFilters}' setting in .scalafmt.conf.
59+
*/
60+
def withRespectProjectFilters(respectExcludeFilters: Boolean): Scalafmt
61+
62+
/** Builder method.
63+
*
64+
* @param respectVersion
65+
* If true (default), refuses to format files when the 'version' setting is
66+
* missing in .scalafmt.conf and users must update .scalafmt.conf to format
67+
* files. If false, falls back to the default version provided via
68+
* {@link #withDefaultVersion(String)} .
69+
* @return
70+
* an updated interface instance controlling whether to respect the
71+
* 'version' setting in .scalafmt.conf.
72+
*/
73+
@Deprecated
74+
def withRespectVersion(respectVersion: Boolean): Scalafmt
75+
76+
/** Builder method.
77+
*
78+
* @param defaultVersion
79+
* the fallback Scalafmt version to use when there is no 'version' setting
80+
* in `.scalafmt.conf`; N.B. ignored when
81+
* {@link #withRespectVersion(boolean)} is true
82+
* @return
83+
* an updated interface instance with the default version set
84+
*/
85+
@Deprecated
86+
def withDefaultVersion(defaultVersion: String): Scalafmt
87+
88+
/** Builder method.
89+
*
90+
* @param reporter
91+
* Use this instance to report errors and information messages
92+
* @return
93+
* an updated interface instance with the reporter instance set
94+
*/
95+
def withReporter(reporter: ScalafmtReporter): Scalafmt
96+
97+
/** Builder method.
98+
*
99+
* @param repositories
100+
* maven repositories to use when resolving
101+
* @return
102+
* an updated interface instance with the repositories to use to resolve
103+
* dependencies.
104+
*/
105+
def withMavenRepositories(repositories: String*): Scalafmt
106+
107+
/** Builder method.
108+
*
109+
* @param credentials
110+
* repository credentials to use when resolving
111+
* @return
112+
* an updated interface instance.
113+
*/
114+
def withRepositoryCredentials(credentials: RepositoryCredential*): Scalafmt
115+
116+
/** Clear internal caches such as classloaded Scalafmt instances.
117+
*/
118+
def clear(): Unit
119+
120+
/** Create a ScalafmtSession to format a batch of files using fixed
121+
* configuration.
122+
* @param config
123+
* location of the configuration file
124+
* @return
125+
* a new session instance
126+
*/
127+
def createSession(config: Path): ScalafmtSession
128+
}
129+
130+
object Scalafmt {
131+
132+
/** Classload a new instance of this Scalafmt instance.
133+
*
134+
* @param loader
135+
* the classloader containing the 'scalafmt-dynamic' module.
136+
* @return
137+
* a new instance of this interface
138+
* @throws NoSuchElementException
139+
* if the classloader does not have the 'scalafmt-dynamic' module.
140+
*/
141+
def create(loader: ClassLoader): Scalafmt = throw new ScalafmtException(
142+
"Can't use different version for native CLI",
143+
null,
144+
)
145+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.scalafmt.interfaces
2+
3+
/** A classloader that shares only scalafmt-interfaces classes from the parent
4+
* classloader.
5+
*
6+
* This classloader is intended to be used as a parent when class-loading
7+
* scalafmt-dynamic. By using this classloader as a parent, it's possible to
8+
* cast runtime instances from the scalafmt-dynamic classloader into
9+
* `org.scalafmt.interfaces.Scalafmt` from this classloader.
10+
*/
11+
class ScalafmtClassLoader(val parent: ClassLoader) extends ClassLoader(null) {
12+
@throws[ClassNotFoundException]
13+
override protected def findClass(name: String): Class[_] =
14+
if (name.startsWith("org.scalafmt.interfaces")) parent.loadClass(name)
15+
else super.findClass(name)
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package org.scalafmt.interfaces
2+
3+
class ScalafmtException(message: String, cause: Throwable)
4+
extends RuntimeException(message, cause, true, false)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.scalafmt.interfaces
2+
3+
import java.io.OutputStreamWriter
4+
import java.io.PrintWriter
5+
import java.nio.file.Path
6+
7+
/** A reporter to handle error and information messages from Scalafmt.
8+
*/
9+
trait ScalafmtReporter {
10+
11+
/** An error occurred while trying to process this file.
12+
*
13+
* @param file
14+
* can be either a Scala source file or .scalafmt.conf.
15+
* @param message
16+
* the error message.
17+
*/
18+
def error(file: Path, message: String): Unit
19+
20+
/** An exception occurred while trying to process this file.
21+
*
22+
* @param file
23+
* can be either a Scala source file or .scalafmt.conf.
24+
* @param e
25+
* the exception that occurred, has type {@link PositionException} when the
26+
* error appeared as a position.
27+
*/
28+
def error(file: Path, e: Throwable): Unit
29+
30+
/** An exception occurred while trying to process this file.
31+
*
32+
* @param file
33+
* can be either a Scala source file or .scalafmt.conf.
34+
* @param message
35+
* additional error message
36+
* @param e
37+
* the exception that occurred, has type {@link PositionException} when the
38+
* error appeared as a position.
39+
*/
40+
def error(file: Path, message: String, e: Throwable): Unit =
41+
if (e == null) error(file, message)
42+
else error(file, new ScalafmtException(message, e))
43+
44+
/** This file was not formatted because it's excluded by project settings from
45+
* .scalafmt.conf.
46+
*
47+
* @param file
48+
* the file path that was not formatted.
49+
*/
50+
def excluded(file: Path): Unit
51+
52+
/** This .scalafmt.conf file is missing the 'version' setting.
53+
*
54+
* @param config
55+
* the .scalafmt.conf file.
56+
* @param defaultVersion
57+
* the configured default Scalafmt version.
58+
*/
59+
def missingVersion(config: Path, defaultVersion: String): Unit = {
60+
val message = String.format(
61+
"missing setting 'version'. To fix this problem, add the following line to .scalafmt.conf: 'version=%s'.",
62+
defaultVersion,
63+
)
64+
error(config, message)
65+
}
66+
67+
/** Record the configuration file and version used
68+
*
69+
* @param config
70+
* location of the configuration file parsed
71+
* @param scalafmtVersion
72+
* the version of scalafmt used to parse
73+
*/
74+
def parsedConfig(config: Path, scalafmtVersion: String): Unit
75+
76+
/** Use {@link #downloadOutputStreamWriter} instead.
77+
*
78+
* @return
79+
* an instance of progress writer
80+
*/
81+
@deprecated
82+
def downloadWriter(): PrintWriter
83+
84+
/** Use this writer for printing progress while downloading new Scalafmt
85+
* versions.
86+
*
87+
* @return
88+
* an instance of progress writer
89+
*/
90+
def downloadOutputStreamWriter(): OutputStreamWriter
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.scalafmt.interfaces
2+
3+
final class ScalafmtResult(val value: String, val exception: Throwable) {
4+
def this(value: String) = this(value, null)
5+
def this(exception: Throwable) = this(null, exception)
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.scalafmt.interfaces
2+
3+
import java.nio.file.Path
4+
5+
/** A session based on a fixed configuration.
6+
*/
7+
trait ScalafmtSession {
8+
9+
/** Format a single file with the given configuration.
10+
*
11+
* @param file
12+
* relative or absolute path to the file being formatted. Used only for the
13+
* path name, the file does not have to exist on disk.
14+
* @param code
15+
* the text contents to format.
16+
* @return
17+
* the formatted contents if formatting was successful, otherwise the
18+
* original text contents.
19+
*/
20+
def format(file: Path, code: String): String
21+
22+
/** Format a single file with the given configuration.
23+
*
24+
* @param file
25+
* relative or absolute path to the file being formatted. Used only for the
26+
* path name, the file does not have to exist on disk.
27+
* @param code
28+
* the text contents to format.
29+
* @return
30+
* the formatted contents if formatting was successful, otherwise an error.
31+
*/
32+
def formatOrError(file: Path, code: String): ScalafmtResult
33+
34+
/** Whether the path matches the 'project.{excludeFilters,includeFilters}'
35+
* setting.
36+
* @param file
37+
* path to match
38+
* @return
39+
* true if the path matched the filters
40+
*/
41+
def matchesProjectFilters(file: Path): Boolean;
42+
43+
/** Whether this configuration intends to limit files to those managed by git.
44+
*/
45+
def isGitOnly: Boolean
46+
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.scalafmt.interfaces
2+
3+
import java.nio.file.Path
4+
5+
trait ScalafmtSessionFactory {
6+
7+
/** Create a ScalafmtSession to format a batch of files using fixed
8+
* configuration.
9+
* @param config
10+
* location of the configuration file
11+
* @return
12+
* a new session instance
13+
*/
14+
def createSession(config: Path): ScalafmtSession
15+
}

0 commit comments

Comments
 (0)