I been trying to create a Ruleset using Pinterest ktlint Library but I can't remove the part of a children parameter list.
https://github.com/pinterest/ktlint/issues/709
Due to the update on Kotlin to support 'trailing-commas', is breaking all my static code analysis (SonarQube Gradle plugin 2.8). So I decided to create a RuleSetProvider to find a remove from the code this annoying comma ',' at the end of all the parameter list found in the project.
class NoTrailingCommaRule : Rule("no-trailing-comma") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == ElementType.COMMA) {
node.parents().forEach {
if (it.elementType == ElementType.VALUE_PARAMETER_LIST) {
if (it.text.contains("pepe")) {
println("############# IS PEPE ###############")
println("ParamList-> " + it.text)
println("-------------------------------------")
if (it is PsiParameterList) {
it.parameters.forEach { param ->
println(" -> ${param.text}")
// if (next.elementType == ElementType.COMMA)
// println(" -> comma,")
println("---==---")
}
println("#####################################")
}
}
}
}
}
}
}
/// Sample class to lint
data class PEPE(
val pepe: String,
var pepe1: List<String> = emptyList(), //<- This is the kind of comma I want to remove
)
Thats my current attempt of trying to get the comma and replace, but when I'm able to print the parameter line the comma is not there. 😿
See rule below for which I have sent a PR:
package com.pinterest.ktlint.ruleset.experimental
import com.pinterest.ktlint.core.Rule
import com.pinterest.ktlint.core.ast.ElementType
import com.pinterest.ktlint.core.ast.children
import org.jetbrains.kotlin.com.intellij.lang.ASTNode
import org.jetbrains.kotlin.psi.psiUtil.endOffset
class NoTrailingCommaRule : Rule("no-trailing-comma") {
override fun visit(
node: ASTNode,
autoCorrect: Boolean,
emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit
) {
if (node.elementType == ElementType.VALUE_ARGUMENT_LIST || node.elementType == ElementType.VALUE_PARAMETER_LIST) {
val lastNode = node
.children()
.filter { it.elementType != ElementType.WHITE_SPACE }
.filter { it.elementType != ElementType.EOL_COMMENT }
.filter { it.elementType != ElementType.RPAR }
.last()
if (lastNode.elementType == ElementType.COMMA) {
emit(lastNode.psi.endOffset - 1, "Trailing command in argument list is redundant", true)
if (autoCorrect) {
node.removeChild(lastNode)
}
}
}
}
}
and the tests:
package com.pinterest.ktlint.ruleset.experimental
import com.pinterest.ktlint.core.LintError
import com.pinterest.ktlint.test.format
import com.pinterest.ktlint.test.lint
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
class NoTrailingCommaRuleTest {
#Test
fun testFormatIsCorrectWithArgumentList() {
val code =
"""
val list1 = listOf("a", "b",)
val list2 = listOf(
"a",
"b", // The comma before the comment should be removed without removing the comment itself
)
""".trimIndent()
val autoCorrectedCode =
"""
val list1 = listOf("a", "b")
val list2 = listOf(
"a",
"b" // The comma before the comment should be removed without removing the comment itself
)
""".trimIndent()
assertThat(NoTrailingCommaRule().lint(code)).isEqualTo(
listOf(
LintError(line = 1, col = 28, ruleId = "no-trailing-comma", detail = "Trailing command in argument list is redundant"),
LintError(line = 4, col = 8, ruleId = "no-trailing-comma", detail = "Trailing command in argument list is redundant"),
)
)
assertThat(NoTrailingCommaRule().format(code))
.isEqualTo(autoCorrectedCode)
}
#Test
fun testFormatIsCorrectWithValueList() {
val code =
"""
data class Foo1(
val bar: Int, // The comma before the comment should be removed without removing the comment itself
)
data class Foo2(val bar: Int,)
""".trimIndent()
val autoCorrectedCode =
"""
data class Foo1(
val bar: Int // The comma before the comment should be removed without removing the comment itself
)
data class Foo2(val bar: Int)
""".trimIndent()
assertThat(NoTrailingCommaRule().lint(code)).isEqualTo(
listOf(
LintError(line = 2, col = 16, ruleId = "no-trailing-comma", detail = "Trailing command in argument list is redundant"),
LintError(line = 4, col = 29, ruleId = "no-trailing-comma", detail = "Trailing command in argument list is redundant"),
)
)
assertThat(NoTrailingCommaRule().format(code))
.isEqualTo(autoCorrectedCode)
}
}
Related
Consider I have a cold source of UTF-8 bytes (e. g.: reading a file on disk, or the body of an HTTP response), in a form of a Flow<Byte>. How do I convert the above source to a flow of strings?
In other words, I want the following behaviour:
/*
* A multi-line string, not terminated with a newline character.
*/
val string = """
first line
第二行
третья строка
""".trimIndent()
assertNotEquals('\n', string.last())
assertEquals(2, string.asSequence().count { it == '\n' })
val source: Flow<Byte> = string.toByteArray().asSequence().asFlow()
val transformed: Flow<String> = TODO()
val target = runBlocking {
transformed.toList(mutableListOf()).toTypedArray()
}
assertArrayEquals(
arrayOf("first line", "第二行", "третья строка"),
target
)
As an extra restriction, this is a Kotlin/JS project, so java.io APIs can't be used.
Eventually, I came up with the following solution:
fun Flow<Byte>.decodeToString(): Flow<String> =
flow {
val buffer: MutableList<Byte> = arrayListOf()
collect { value ->
when (value) {
/*
* Ignore.
*/
'\r'.code.toByte() -> Unit
'\n'.code.toByte() -> {
emit(buffer)
buffer.clear()
}
else -> buffer.add(value)
}
}
if (buffer.isNotEmpty()) {
emit(buffer)
}
}
.map(Collection<Byte>::toByteArray)
.map(ByteArray::decodeToString)
The ArrayList<Byte> above can be replaced with either okio.Buffer from okio or kotlinx.io.core.BytePacketBuilder from kotlinx-io, e.g.:
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import okio.Buffer
fun Flow<Byte>.decodeToString(): Flow<String> =
flow {
val buffer = Buffer()
collect { value ->
when (value) {
/*
* Ignore.
*/
'\r'.code.toByte() -> Unit
'\n'.code.toByte() -> {
emit(buffer.readUtf8())
buffer.clear()
}
else -> buffer.writeByte(value.toInt())
}
}
if (buffer.size > 0) {
emit(buffer.readUtf8())
}
}
I have the following structure at present:
#Entity
#Table(name = "table_app_settings")
data class AppSetting(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "app_setting_id")
val id: Long? = null,
#Column(name = "app_setting_name")
val name: String = "",
#Column(name = "app_setting_value")
var value: String = "",
#Column(name = "app_setting_type")
val type: AppSettingType,
)
enum class AppSettingType {
CHAR,
STRING,
BYTE,
SHORT,
INT,
LONG,
DOUBLE,
FLOAT,
BOOLEAN,
}
This is then saved to the database with the following:
override fun saveAppSetting(setting: AppSetting): DatabaseResult<AppSetting> {
log.info("Saving App Setting ${setting.name} to database.")
return try {
// Attempt to save the entity to the database. If we do not throw an exception, return success.
val savedSetting = appSettingsRepository.save(setting)
DatabaseResult(
code = ResultCode.CREATION_SUCCESS,
entity = savedSetting
)
} catch(exception: DataAccessException) {
log.error("Unable to save App Setting ${setting.name} to database. Reason: ${exception.message}")
DatabaseResult(
code = ResultCode.CREATION_FAILURE
)
}
}
Now, let's say that I wish to save a Char type to database, I figure I would use the following:
override fun saveAppSetting(name: String, value: Char): DatabaseResult<Char> {
val appSettingResult = saveAppSetting(AppSetting(
name = name,
value = value.toString(),
type = AppSettingType.CHAR,
))
return if(appSettingResult.code != ResultCode.CREATION_FAILURE) {
val entity = getAppSetting<Char>(appSettingResult.entity?.name!!).entity.toString().first()
DatabaseResult(
code = appSettingResult.code,
entity = entity
)
} else {
DatabaseResult(
code = ResultCode.CREATION_FAILURE,
)
}
}
I also figured that I would need to do the following in order to retrieve the correct object type:
override fun getAppSetting(name: String): DatabaseResult<Any?> {
log.info("Getting App Setting $name from database.")
val appSetting = appSettingsRepository.findAppSettingByName(name)
return if(appSetting != null) {
log.info("App Setting $name has ID of ${appSetting.id} within the database")
when(appSetting.type) {
AppSettingType.CHAR -> {
DatabaseResult<Char>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.first(),
)
}
AppSettingType.STRING -> {
DatabaseResult<String>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value,
)
}
AppSettingType.BYTE -> {
DatabaseResult<Byte>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toByte(),
)
}
AppSettingType.SHORT -> {
DatabaseResult<Short>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toShort(),
)
}
AppSettingType.INT -> {
DatabaseResult<Int>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toInt(),
)
}
AppSettingType.LONG -> {
DatabaseResult<Long>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toLong(),
)
}
AppSettingType.DOUBLE -> {
DatabaseResult<Double>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toDouble(),
)
}
AppSettingType.FLOAT -> {
DatabaseResult<Float>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toFloat()
)
}
AppSettingType.BOOLEAN -> {
DatabaseResult<Boolean>(
code = ResultCode.FETCH_SUCCESS,
entity = appSetting.value.toBoolean()
)
}
}
} else {
log.error("App Setting $name does not seem to exist within the database.")
DatabaseResult(
code = ResultCode.FETCH_FAILURE
)
}
However, when I then wish to use said object, I still have to write something like the following:
val newBarcode = getAppSetting("barcode_value").entity.toString().toInt()
Assuming I've "initialised" barcode_value with a value of 177 (for example).
How can I get the function to return what I need without having to do .toString.to...()?
Yes this all possible, here is a simplified demo, firstly
import kotlin.reflect.KClass
data class AppSetting(
val id: Long? = null,
val name: String = "",
var value: String = "",
val type: AppSettingType,
)
enum class AppSettingType(val clazz: KClass<out Any>) {
CHAR(Char::class),
STRING(String::class),
INT(Int::class),
}
So I added a clazz so from the enum we know the Kotlin type
and now a function to simulate your repository fetch
fun findAppSettingByName(name: String): AppSetting? {
return when(name) {
"Char thing" -> AppSetting(value= "C", type = AppSettingType.CHAR)
"String thing" -> AppSetting(value= "Str", type = AppSettingType.STRING)
"Int thing" -> AppSetting(value= "42", type = AppSettingType.INT)
else -> throw IllegalArgumentException()
}
}
Next in the function declaration I have made it generic with T and for the purposes of the demo removed the DatabaseResult container. Then I added a clazz parameter which is the typical Java way of carrying the required class information into the function:
fun <T : Any> getAppSetting(name: String, clazz: KClass<T>): T? {
val appSetting: AppSetting? = findAppSettingByName(name)
return appSetting?.let {
require(clazz == appSetting.type.clazz) {
"appSetting.type=${appSetting.type.clazz} mismatched with requested class=${clazz}"
}
when (appSetting.type) {
AppSettingType.CHAR -> appSetting.value.first()
AppSettingType.STRING -> appSetting.value
AppSettingType.INT -> appSetting.value.toInt()
} as T
}
}
the as T is important to cast the values into the required return type - this is unchecked but the when() clause should be creating the correct types.
Now let's test it:
val c1: Char? = getAppSetting("Char thing", Char::class)
val s1: String? = getAppSetting("String thing", String::class)
val i1: Int? = getAppSetting("Int thing", Int::class)
println("c1=$c1 s1=$s1 i1=$i1")
val c2: Char? = getAppSetting("Char thing")
val s2: String? = getAppSetting("String thing")
val i2: Int? = getAppSetting("Int thing")
println("c2=$c2 s2=$s2 i2=$i2")
}
The output is
c1=C s1=Str i1=42
c2=C s2=Str i2=42
But how do c2/s2/i2 work, the final part is this function
inline fun <reified T : Any> getAppSetting(name: String) = getAppSetting(name, T::class)
This is reified generic parameters... there is no need to pass the clazz because this can be found from the data type of the receiving variable.
There are many articles about this advanced topic, e.g.
https://typealias.com/guides/getting-real-with-reified-type-parameters/
https://medium.com/kotlin-thursdays/introduction-to-kotlin-generics-reified-generic-parameters-7643f53ba513
Now, I didn't completely answer what you wanted because you wanted to receive a DatabaseResult<T> wrapper. What might be possible, is to have a function that returns DatabaseResult<T> and you can obtain the T from it as the "clazz" parameter, but I'll leave that for someone else to improve on :-) but I think that gets you pretty close.
I'm calculating the projection of instants in time based on a cron expression and returning them as a Sequence. Here's the class:
// (package omitted)
import org.springframework.scheduling.support.CronExpression
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
class Recurrence(val cronExpression: String) {
private val cron = CronExpression.parse(cronExpression)
fun instants(
fromInclusive: LocalDate = LocalDate.now(),
toExclusive: LocalDate = fromInclusive.plusMonths(1)
): Sequence<LocalDateTime> = instants(fromInclusive.atStartOfDay(), toExclusive.atStartOfDay())
fun instants(
fromInclusive: LocalDateTime = LocalDateTime.now(),
toExclusive: LocalDateTime = fromInclusive.plusMonths(1)
): Sequence<LocalDateTime> {
return generateSequence(cron.next(fromInclusive.minusNanos(1))) {
if (it.isBefore(toExclusive)) {
cron.next(it)
} else {
null
}
}
}
}
The following test fails because the first assertion is false: the returned list has one extra, unexpected element at the end.
// (package omitted)
import java.time.LocalDate
import java.time.Month
import kotlin.test.Test
import kotlin.test.assertEquals
class RecurrenceTest {
#Test
fun testInstants() {
val r = Recurrence("#daily")
val from = LocalDate.of(2021, Month.JANUARY, 1)
val forDays = 31
val instants = r.instants(from, from.plusDays(forDays.toLong())).toList()
assertEquals(forDays, instants.size)
(1..forDays).forEach {
assertEquals(from.plusDays(it.toLong() - 1).atStartOfDay(), instants[it - 1])
}
}
}
If I reimplement by building an ArrayList instead, it works as expected:
// new collection-based methods in Recurrence
fun instantsList(
fromInclusive: LocalDate = LocalDate.now(),
toExclusive: LocalDate = fromInclusive.plusMonths(1)
): List<LocalDateTime> = instantsList(fromInclusive.atStartOfDay(), toExclusive.atStartOfDay())
fun instantsList(
fromInclusive: LocalDateTime = LocalDateTime.now(),
toExclusive: LocalDateTime = fromInclusive.plusMonths(1)
): List<LocalDateTime> {
val list = arrayListOf<LocalDateTime>()
var it = cron.next(fromInclusive.minusNanos(1))
while (it !== null) {
if (it.isBefore(toExclusive)) {
list.add(it)
it = cron.next(it)
} else {
break
}
}
return list
}
The one line to change in the test is to use the new method:
val instants = r.instantsList(from, from.plusDays(forDays.toLong()))
Why is the sequence-based implementation returning me one more element than the list-based one?
If I read your code correctly, in list implementation you check if it.isBefore(toExclusive) and only then you add it to the list. In sequence implementation you do the same check it.isBefore(toExclusive) and then you add next item to the sequence.
Similar with the first item. In list implementation you check if cron.next(fromInclusive.minusNanos(1)) meets the requirement. In sequence implementation you always add it.
Thanks, #broot -- you spotted the issue. Just took another set of eyeballs. Correct sequence implementation is
fun instants(
fromInclusive: LocalDateTime = LocalDateTime.now(),
toExclusive: LocalDateTime = fromInclusive.plusMonths(1)
): Sequence<LocalDateTime> {
val seed = cron.next(fromInclusive.minusNanos(1))
return generateSequence(seed) {
val next = cron.next(it)
if (next.isBefore(toExclusive)) {
next
} else {
null
}
}
}
I'll appreciate all your help.
I've been working on a course project where I have to make a parking lot that registers cars. When I use it in my IDE it works fine but when I run it through the platforms tests, in the first one, there's no problem but when the second iteration reaches the "when (val command = scanner.next())" in the createOrder fun, it crashes with the error:
java.lang.AssertionError: Exception in test #1
Probably your program run out of input (Scanner tried to read more than expected).
java.util.NoSuchElementException
at java.util.Scanner.throwFor(Scanner.java:862)
at java.util.Scanner.next(Scanner.java:1371)
at parking.ParkingLot.createOrder(Main.kt:39)
at parking.ParkingLot.start(Main.kt:31)
at parking.MainKt.main(Main.kt:6)
at parking.MainKt.main(Main.kt)
Please find below the output of your program during this failed test.
Note that the '>' character indicates the beginning of the input line.
---
> park KA-01-HH-1234 White
White car parked in spot 1.
the idea is that the test inputs many cars but it crashes when trying to do the second input
this is my code (sorry if my code is messy, I'm still learning)
import java.util.*
fun main() {
ParkingLot.start()
}
class Car(val regNumber: String = "", val color: String = "") {
}
class Order(val command: String) {
lateinit var regNum: String
lateinit var color: String
lateinit var spot: String
lateinit var status: String
}
object ParkingLot {
val spaces: Array<Pair<String?, Car?>> = Array(20) { Pair(null, null) }
const val occupied = "occupied"
const val park = "park"
const val leave = "leave"
const val exit = "exit"
fun start() {
val scanner = Scanner(System.`in`)
do {
val order = createOrder(scanner)
interaction(order, scanner)
} while (order.command != exit)
}
fun createOrder(scanner: Scanner): Order {
when (val command = scanner.next()) {
park -> {
val parkOrder = Order(command)
parkOrder.regNum = scanner.next()
parkOrder.color = scanner.next()
parkOrder.status = "valid"
return parkOrder
}
leave -> {
val retrieveOrder = Order(command)
retrieveOrder.spot = scanner.next()
retrieveOrder.status = "valid"
return retrieveOrder
}
exit -> {
val exitOrder = Order(command)
exitOrder.status = "valid"
return exitOrder
}
else -> {
val incorrectOrder = Order(command)
incorrectOrder.status = "invalid"
return incorrectOrder
}
}
}
fun interaction(order: Order, scanner: Scanner) {
if (order.command == park) {
// val toParkCar = Car(order.regNum, order.color)
park(Car(order.regNum, order.color))
}
if (order.command == leave) {
leave(order)
}
if (order.command == exit) return
//TODO update the error msg to include exit command
if (order.status == "invalid") println("\"${order.command}\" isn't a valid , either use \"park\" or \"leave\"")
// scanner.close()
}
fun park(car: Car) {
for ((index, item) in spaces.withIndex()) {
if (item.first == null) {
spaces[index] = Pair(occupied, car)
println("${car.color} car parked in spot ${index + 1}.")
return
}
}
println("Sorry, the parking lot is full.")
}
fun leave(order: Order) {
if (spaces[order.spot.toInt() - 1].first == occupied) {
spaces[order.spot.toInt() - 1] = Pair(null, null)
println("Spot ${order.spot} is free.")
} else {
println("There is no car in spot ${order.spot}.")
}
}
}
Ok so I noticed this is a problem for the JetBrains plugin. I don't know why but the solution was taking the scanner out of the function and directly in the main loop.
I have a data class like this:
data class TestModel(
val id: Int,
val description: String,
val picture: String)
If I create JSON from this data class using GSON and it generates a result like this
{"id":1,"description":"Test", "picture": "picturePath"}
What to do if I need the following JSON from my data class:
{"id":1, "description":"Test"}
And other times:
`{"id":1, "picture": "picturePath"}
`
Thanks in advance!
You can solve this problem with writing custom adapter and with optional types:
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
data class TestModel(
val id: Int,
val description: String? = "",
val picture: String? = "")
class TesModelTypeAdapter : TypeAdapter<TestModel>() {
override fun read(reader: JsonReader?): TestModel {
var id: Int? = null
var picture: String? = null
var description: String? = null
reader?.beginObject()
while (reader?.hasNext() == true) {
val name = reader.nextName()
if (reader.peek() == JsonToken.NULL) {
reader.nextNull()
continue
}
when (name) {
"id" -> id = reader.nextInt()
"picture" -> picture = reader.nextString()
"description" -> description = reader.nextString()
}
}
reader?.endObject()
return when {
!picture.isNullOrBlank() && description.isNullOrBlank() -> TestModel(id = id ?: 0, picture = picture)
!description.isNullOrBlank() && picture.isNullOrBlank() -> TestModel(id = id ?: 0, description = description)
else -> TestModel(id ?: 0, picture, description)
}
}
override fun write(out: JsonWriter?, value: TestModel?) {
out?.apply {
beginObject()
value?.let {
when {
!it.picture.isNullOrBlank() && it.description.isNullOrBlank() -> {
name("id").value(it.id)
name("picture").value(it.picture)
}
!it.description.isNullOrBlank() && it.picture.isNullOrBlank() -> {
name("id").value(it.id)
name("description").value(it.description)
}
else -> {
name("id").value(it.id)
name("picture").value(it.picture)
name("description").value(it.description)
}
}
}
endObject()
}
}
}
class App {
companion object {
#JvmStatic fun main(args: Array<String>) {
val tm = TestModel(12, description = "Hello desc")
val tm2 = TestModel(23, picture = "https://www.pexels.com/photo/daylight-forest-glossy-lake-443446/")
val tm3 = TestModel(12, "Hello desc", "https://www.pexels.com/photo/daylight-forest-glossy-lake-443446/")
val gson = GsonBuilder().registerTypeAdapter(TestModel::class.java, TesModelTypeAdapter()).create()
System.out.println(gson.toJson(tm))
System.out.println(gson.toJson(tm2))
System.out.println(gson.toJson(tm3))
}
}
}
Here is actually a way to ignore fields, that are not marked via #Exposed annotation. In order for this to work, special configuration should be used when instantiating Gson. Here is how you can to this.
Easy way is to mark the field as #Transient. Then it would not be either serialized and deserialized.
I want to give you alternative ways without manually serialization/deserialization.
data class TestModel(
val id: Int,
val description: String? = null,
val picture: String? = null)
When you create json from data class
val params = TestModel(id = 1, description = "custom text")
or
val params = TestModel(id = 1, picture = "picture path")
If one of them field is null of data class GSON skips that field
automatically.