Skip to content

Commit 4845223

Browse files
authored
Fantomas: use default setting value if doesn't exist (#516)
1 parent 76b9290 commit 4845223

File tree

10 files changed

+224
-145
lines changed

10 files changed

+224
-145
lines changed

ReSharper.FSharp/src/FSharp.Fantomas.Host/src/FantomasCodeFormatter.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ private static object ConvertToFormatConfig(string[] riderFormatConfigValues)
261261
{
262262
int => int.Parse(valueData),
263263
bool => bool.Parse(valueData),
264-
{ } => ConvertEnumValue(valueData)
264+
{ } defaultEnumValue => TryConvertEnumValue(valueData, defaultEnumValue)
265265
}))
266266
.ToDictionary(x => x.Name, x => x.Value);
267267

@@ -278,18 +278,13 @@ private static object ConvertToFormatConfig(string[] riderFormatConfigValues)
278278
: formatConfig;
279279
}
280280

281-
// TODO: alternatively, we can reuse the logic from
282-
// https://github.com/fsprojects/fantomas/blob/master/src/Fantomas.Extras/EditorConfig.fs
283-
// such as `parseOptionsFromEditorConfig`,
284-
// or take the OfConfigString methods of discriminated unions as a contract
285-
// https://github.com/fsprojects/fantomas/blob/master/src/Fantomas/FormatConfig.fs
286-
private static object ConvertEnumValue(string setting)
281+
private static object TryConvertEnumValue(string userValue, object defaultValue)
287282
{
288-
var camelCaseSetting = StringUtil.MakeUpperCamelCaseName(setting);
283+
var camelCaseSetting = StringUtil.MakeUpperCamelCaseName(userValue);
289284

290285
return FormatConfigDUs.TryGetValue(camelCaseSetting, out var unionCase)
291286
? FSharpValue.MakeUnion(unionCase, null, FSharpOption<BindingFlags>.None)
292-
: throw new ArgumentOutOfRangeException($"Unknown Fantomas FormatSetting {setting}");
287+
: defaultValue;
293288
}
294289
}
295290
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package com.jetbrains.rider.plugins.fsharp.test.cases.fantomas
2+
3+
import com.intellij.openapi.project.Project
4+
import com.intellij.util.application
5+
import com.intellij.util.io.createFile
6+
import com.intellij.util.io.delete
7+
import com.intellij.util.io.write
8+
import com.jetbrains.rd.platform.util.lifetime
9+
import com.jetbrains.rdclient.util.idea.waitAndPump
10+
import com.jetbrains.rider.plugins.fsharp.test.*
11+
import com.jetbrains.rider.projectView.solutionDirectory
12+
import com.jetbrains.rider.protocol.protocolManager
13+
import com.jetbrains.rider.test.OpenSolutionParams
14+
import com.jetbrains.rider.test.asserts.shouldBe
15+
import com.jetbrains.rider.test.base.EditorTestBase
16+
import com.jetbrains.rider.test.env.Environment
17+
import com.jetbrains.rider.test.env.dotNetSdk
18+
import com.jetbrains.rider.test.framework.frameworkLogger
19+
import com.jetbrains.rider.test.scriptingApi.restoreNuGet
20+
import java.io.File
21+
import java.io.PrintStream
22+
import java.nio.file.Paths
23+
import java.time.Duration
24+
import kotlin.io.path.*
25+
26+
abstract class FantomasDotnetToolTestBase : EditorTestBase() {
27+
override fun getSolutionDirectoryName() = "FormatCodeApp"
28+
override val restoreNuGetPackages = false
29+
30+
private fun getDotnetCliHome() = Path(tempTestDirectory.parent, "dotnetHomeCli")
31+
private val fantomasNotifications = ArrayList<String>()
32+
protected val bundledVersion = "5.2.1.0"
33+
protected val globalVersion = "4.7.2.0"
34+
private var dotnetToolsInvalidated = false
35+
36+
protected fun dumpRunOptions() = project.fcsHost.dumpFantomasRunOptions.sync(Unit)
37+
protected fun checkFantomasVersion(version: String) = project.fcsHost.fantomasVersion.sync(Unit).shouldBe(version)
38+
protected fun dumpNotifications(stream: PrintStream, expectedCount: Int) {
39+
stream.println("\n\nNotifications:")
40+
waitAndPump(project.lifetime,
41+
{ fantomasNotifications.size >= expectedCount },
42+
Duration.ofSeconds(30),
43+
{ "Didn't wait for notifications. Expected $expectedCount, but was ${fantomasNotifications.size}" })
44+
45+
fantomasNotifications.forEach { stream.println(it) }
46+
}
47+
48+
protected fun withFantomasSetting(value: String, function: () -> Unit) {
49+
withSetting(project, "FSharp/FSharpFantomasOptions/Location/@EntryValue", value, "AutoDetected") {
50+
function()
51+
}
52+
}
53+
54+
private fun withDotnetToolsUpdate(function: () -> Unit) {
55+
dotnetToolsInvalidated = false
56+
function()
57+
flushFileChanges(project)
58+
waitAndPump(Duration.ofSeconds(15), { dotnetToolsInvalidated == true }, { "Dotnet tools wasn't changed." })
59+
}
60+
61+
protected fun withFantomasLocalTool(name: String, version: String, restore: Boolean = true, function: () -> Unit) {
62+
val manifestFile = Paths.get(project.solutionDirectory.absolutePath, ".config", "dotnet-tools.json")
63+
frameworkLogger.info("Create '$manifestFile'")
64+
val file = manifestFile.createFile()
65+
66+
try {
67+
withDotnetToolsUpdate {
68+
val toolsJson = """"$name": { "version": "$version", "commands": [ "fantomas" ] }"""
69+
file.write("""{ "version": 1, "isRoot": true, "tools": { $toolsJson } }""")
70+
}
71+
if (restore) {
72+
withDotnetToolsUpdate {
73+
// Trigger dotnet tools restore
74+
// TODO: use separate dotnet tools restore API
75+
restoreNuGet(project)
76+
}
77+
}
78+
function()
79+
} finally {
80+
withDotnetToolsUpdate {
81+
file.delete()
82+
project.fcsHost.terminateFantomasHost.sync(Unit)
83+
}
84+
}
85+
}
86+
87+
protected fun withFantomasGlobalTool(function: () -> Unit) {
88+
try {
89+
val env = mapOf("DOTNET_CLI_HOME" to getDotnetCliHome().absolutePathString())
90+
91+
withDotnetToolsUpdate {
92+
runProcessWaitForExit(
93+
Environment.dotNetSdk(testMethod.environment.sdkVersion).dotnetExecutable.toPath(),
94+
listOf("tool", "install", "fantomas-tool", "-g", "--version", globalVersion),
95+
env
96+
)
97+
}
98+
function()
99+
} finally {
100+
project.fcsHost.terminateFantomasHost.sync(Unit)
101+
}
102+
}
103+
104+
override fun openSolution(solutionFile: File, params: OpenSolutionParams): Project {
105+
application.protocolManager.protocolHosts.forEach {
106+
editFSharpBackendSettings(it) {
107+
dotnetCliHomeEnvVar = getDotnetCliHome().absolutePathString()
108+
}
109+
}
110+
return super.openSolution(solutionFile, params)
111+
}
112+
113+
override fun beforeDoTestWithDocuments() {
114+
super.beforeDoTestWithDocuments()
115+
116+
fantomasNotifications.clear()
117+
118+
project.fcsHost.fantomasNotificationFired.advise(testLifetimeDef.lifetime) {
119+
fantomasNotifications.add(it)
120+
}
121+
project.fcsHost.dotnetToolInvalidated.advise(testLifetimeDef.lifetime) {
122+
dotnetToolsInvalidated = true
123+
}
124+
125+
val dotnetCliHome = getDotnetCliHome()
126+
if (!dotnetCliHome.exists()){
127+
dotnetCliHome.createDirectory()
128+
flushFileChanges(project)
129+
}
130+
else if (dotnetCliHome.listDirectoryEntries().any { it.name != ".nuget" }) {
131+
withDotnetToolsUpdate {
132+
dotnetCliHome.delete(true)
133+
dotnetCliHome.createDirectory()
134+
flushFileChanges(project)
135+
}
136+
}
137+
}
138+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.jetbrains.rider.plugins.fsharp.test.cases.fantomas
2+
3+
import com.jetbrains.rider.plugins.fsharp.test.flushFileChanges
4+
import com.jetbrains.rider.plugins.fsharp.test.withEditorConfig
5+
import com.jetbrains.rider.test.annotations.TestEnvironment
6+
import com.jetbrains.rider.test.env.enums.SdkVersion
7+
import com.jetbrains.rider.test.framework.executeWithGold
8+
import com.jetbrains.rider.test.scriptingApi.dumpOpenedDocument
9+
import com.jetbrains.rider.test.scriptingApi.reformatCode
10+
import com.jetbrains.rider.test.scriptingApi.withOpenedEditor
11+
import com.jetbrains.rider.test.waitForDaemon
12+
import org.testng.annotations.Test
13+
import java.io.File
14+
15+
@Test
16+
@TestEnvironment(sdkVersion = SdkVersion.DOT_NET_7, reuseSolution = false)
17+
class FantomasEditorConfigTest : FantomasDotnetToolTestBase() {
18+
override fun getSolutionDirectoryName() = "FormatCodeApp"
19+
override fun beforeDoTestWithDocuments() {
20+
super.beforeDoTestWithDocuments()
21+
22+
val sourceEditorConfigFile = File(testCaseSourceDirectory, ".editorconfig")
23+
val slnEditorConfigFile = File(tempTestDirectory, ".editorconfig")
24+
sourceEditorConfigFile.copyTo(slnEditorConfigFile, true)
25+
flushFileChanges(project)
26+
}
27+
28+
private fun doEditorConfigEnumTest(fantomasVersion: String) {
29+
withEditorConfig(project) {
30+
withFantomasLocalTool("fantomas", fantomasVersion) {
31+
withOpenedEditor("Program.fs", "Brackets.fs") {
32+
waitForDaemon()
33+
reformatCode()
34+
executeWithGold(testGoldFile) {
35+
dumpOpenedDocument(it, project!!, false)
36+
}
37+
}
38+
}
39+
}
40+
}
41+
42+
@Test(description = "Doesn't support experimental_stroustrup, 'cramped' should be used instead")
43+
fun `editorconfig enum values 01`() = doEditorConfigEnumTest("6.0.1")
44+
45+
@Test(description = "Supports stroustrup")
46+
fun `editorconfig enum values 02`() = doEditorConfigEnumTest("6.0.1")
47+
}

rider-fsharp/src/test/kotlin/com/jetbrains/rider/plugins/fsharp/test/cases/fantomas/FantomasRunOptionsTest.kt

Lines changed: 2 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,17 @@
11
package com.jetbrains.rider.plugins.fsharp.test.cases.fantomas
22

3-
import com.intellij.openapi.project.Project
4-
import com.intellij.util.application
5-
import com.intellij.util.io.createFile
6-
import com.intellij.util.io.delete
7-
import com.intellij.util.io.write
8-
import com.jetbrains.rd.platform.util.lifetime
9-
import com.jetbrains.rdclient.util.idea.waitAndPump
10-
import com.jetbrains.rider.plugins.fsharp.test.*
11-
import com.jetbrains.rider.projectView.solutionDirectory
12-
import com.jetbrains.rider.protocol.protocolManager
13-
import com.jetbrains.rider.test.OpenSolutionParams
143
import com.jetbrains.rider.test.annotations.TestEnvironment
15-
import com.jetbrains.rider.test.asserts.shouldBe
16-
import com.jetbrains.rider.test.base.EditorTestBase
17-
import com.jetbrains.rider.test.env.Environment
18-
import com.jetbrains.rider.test.env.dotNetSdk
194
import com.jetbrains.rider.test.env.enums.SdkVersion
205
import com.jetbrains.rider.test.framework.executeWithGold
21-
import com.jetbrains.rider.test.framework.frameworkLogger
226
import com.jetbrains.rider.test.scriptingApi.dumpOpenedDocument
237
import com.jetbrains.rider.test.scriptingApi.reformatCode
24-
import com.jetbrains.rider.test.scriptingApi.restoreNuGet
258
import com.jetbrains.rider.test.scriptingApi.withOpenedEditor
269
import com.jetbrains.rider.test.waitForDaemon
2710
import org.testng.annotations.Test
28-
import java.io.File
29-
import java.io.PrintStream
30-
import java.nio.file.Paths
31-
import java.time.Duration
32-
import kotlin.io.path.*
3311

3412
@Test
35-
@TestEnvironment(sdkVersion = SdkVersion.DOT_NET_6, reuseSolution = false)
36-
class FantomasRunOptionsTest : EditorTestBase() {
37-
override fun getSolutionDirectoryName() = "FormatCodeApp"
38-
override val restoreNuGetPackages = false
39-
40-
private fun getDotnetCliHome() = Path(tempTestDirectory.parent, "dotnetHomeCli")
41-
private val fantomasNotifications = ArrayList<String>()
42-
private val bundledVersion = "5.2.1.0"
43-
private val globalVersion = "4.7.2.0"
44-
private var dotnetToolsInvalidated = false
45-
46-
private fun dumpRunOptions() = project.fcsHost.dumpFantomasRunOptions.sync(Unit)
47-
private fun checkFantomasVersion(version: String) = project.fcsHost.fantomasVersion.sync(Unit).shouldBe(version)
48-
private fun dumpNotifications(stream: PrintStream, expectedCount: Int) {
49-
stream.println("\n\nNotifications:")
50-
waitAndPump(project.lifetime,
51-
{ fantomasNotifications.size >= expectedCount },
52-
Duration.ofSeconds(30),
53-
{ "Didn't wait for notifications. Expected $expectedCount, but was ${fantomasNotifications.size}" })
54-
55-
fantomasNotifications.forEach { stream.println(it) }
56-
}
57-
58-
private fun withFantomasSetting(value: String, function: () -> Unit) {
59-
withSetting(project, "FSharp/FSharpFantomasOptions/Location/@EntryValue", value, "AutoDetected") {
60-
function()
61-
}
62-
}
63-
64-
private fun withDotnetToolsUpdate(function: () -> Unit) {
65-
dotnetToolsInvalidated = false
66-
function()
67-
flushFileChanges(project)
68-
waitAndPump(Duration.ofSeconds(15), { dotnetToolsInvalidated == true }, { "Dotnet tools wasn't changed." })
69-
}
70-
71-
private fun withFantomasLocalTool(name: String, version: String, restore: Boolean = true, function: () -> Unit) {
72-
val manifestFile = Paths.get(project.solutionDirectory.absolutePath, ".config", "dotnet-tools.json")
73-
frameworkLogger.info("Create '$manifestFile'")
74-
val file = manifestFile.createFile()
75-
76-
try {
77-
withDotnetToolsUpdate {
78-
val toolsJson = """"$name": { "version": "$version", "commands": [ "fantomas" ] }"""
79-
file.write("""{ "version": 1, "isRoot": true, "tools": { $toolsJson } }""")
80-
}
81-
if (restore) {
82-
withDotnetToolsUpdate {
83-
// Trigger dotnet tools restore
84-
// TODO: use separate dotnet tools restore API
85-
restoreNuGet(project)
86-
}
87-
}
88-
function()
89-
} finally {
90-
withDotnetToolsUpdate {
91-
file.delete()
92-
project.fcsHost.terminateFantomasHost.sync(Unit)
93-
}
94-
}
95-
}
96-
97-
private fun withFantomasGlobalTool(function: () -> Unit) {
98-
try {
99-
val env = mapOf("DOTNET_CLI_HOME" to getDotnetCliHome().absolutePathString())
100-
101-
withDotnetToolsUpdate {
102-
runProcessWaitForExit(
103-
Environment.dotNetSdk(testMethod.environment.sdkVersion).dotnetExecutable.toPath(),
104-
listOf("tool", "install", "fantomas-tool", "-g", "--version", globalVersion),
105-
env
106-
)
107-
}
108-
function()
109-
} finally {
110-
project.fcsHost.terminateFantomasHost.sync(Unit)
111-
}
112-
}
113-
114-
override fun openSolution(solutionFile: File, params: OpenSolutionParams): Project {
115-
application.protocolManager.protocolHosts.forEach {
116-
editFSharpBackendSettings(it) {
117-
dotnetCliHomeEnvVar = getDotnetCliHome().absolutePathString()
118-
}
119-
}
120-
return super.openSolution(solutionFile, params)
121-
}
122-
123-
override fun beforeDoTestWithDocuments() {
124-
super.beforeDoTestWithDocuments()
125-
126-
fantomasNotifications.clear()
127-
128-
project.fcsHost.fantomasNotificationFired.advise(testLifetimeDef.lifetime) {
129-
fantomasNotifications.add(it)
130-
}
131-
project.fcsHost.dotnetToolInvalidated.advise(testLifetimeDef.lifetime) {
132-
dotnetToolsInvalidated = true
133-
}
134-
135-
val dotnetCliHome = getDotnetCliHome()
136-
if (!dotnetCliHome.exists()){
137-
dotnetCliHome.createDirectory()
138-
flushFileChanges(project)
139-
}
140-
else if (dotnetCliHome.listDirectoryEntries().any { it.name != ".nuget" }) {
141-
withDotnetToolsUpdate {
142-
dotnetCliHome.delete(true)
143-
dotnetCliHome.createDirectory()
144-
flushFileChanges(project)
145-
}
146-
}
147-
}
148-
13+
@TestEnvironment(sdkVersion = SdkVersion.DOT_NET_7, reuseSolution = false)
14+
class FantomasRunOptionsTest : FantomasDotnetToolTestBase() {
14915
@Test
15016
fun default() {
15117
executeWithGold(testGoldFile) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let myRecord =
2+
{ Level = 1
3+
Progress = "foo"
4+
Bar = "bar"
5+
Street = "Bakerstreet"
6+
Number = 42 }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
root=true
2+
3+
[*.fs]
4+
fsharp_multiline_bracket_style=experimental_stroustrup
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
let myRecord =
2+
{ Level = 1
3+
Progress = "foo"
4+
Bar = "bar"
5+
Street = "Bakerstreet"
6+
Number = 42 }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let myRecord = {
2+
Level = 1
3+
Progress = "foo"
4+
Bar = "bar"
5+
Street = "Bakerstreet"
6+
Number = 42
7+
}

0 commit comments

Comments
 (0)