diff --git a/.courseignore b/.courseignore new file mode 100644 index 0000000..959ba49 --- /dev/null +++ b/.courseignore @@ -0,0 +1,3 @@ +README.md +/.run +/.idea/inspectionProfiles/README.md diff --git a/.github/workflows/gradle-build-with-detekt.yml b/.github/workflows/gradle-build-with-detekt.yml new file mode 100644 index 0000000..78d8139 --- /dev/null +++ b/.github/workflows/gradle-build-with-detekt.yml @@ -0,0 +1,27 @@ +name: Gradle Build With Detekt + +on: [push, pull_request] + +# Allow cancelling all previous runs for the same branch +# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: liberica + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.4 + - uses: gradle/gradle-build-action@v2 + with: + arguments: build --stacktrace -PrunDetekt + \ No newline at end of file diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml new file mode 100644 index 0000000..2e27536 --- /dev/null +++ b/.github/workflows/gradle-build.yml @@ -0,0 +1,27 @@ +name: Gradle Build + +on: [push, pull_request] + +# Allow cancelling all previous runs for the same branch +# See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#concurrency +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: 17 + distribution: liberica + - name: Gradle Wrapper Validation + uses: gradle/wrapper-validation-action@v1.0.4 + - uses: gradle/gradle-build-action@v2 + with: + arguments: build --stacktrace + \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3d70f3a --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +.idea +!.idea/inspectionProfiles/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ diff --git a/.idea/inspectionProfiles/Custom_Inspections.xml b/.idea/inspectionProfiles/Custom_Inspections.xml new file mode 100644 index 0000000..ad57bc4 --- /dev/null +++ b/.idea/inspectionProfiles/Custom_Inspections.xml @@ -0,0 +1,78 @@ + + + + diff --git a/.idea/inspectionProfiles/README.md b/.idea/inspectionProfiles/README.md new file mode 100644 index 0000000..7a4d293 --- /dev/null +++ b/.idea/inspectionProfiles/README.md @@ -0,0 +1,2277 @@ +# Inspection descriptions +This README file contains descriptions of all adapted inspections in the [config](./Custom_Inspections.xml) file. + +> [!NOTE] +> This file, as well as the config file, contains only the most common inspections, that have been adapted for better learning experience. +> For a complete list of inspections available in the IntelliJ platform, see the Kotlin Inspections tab (Settings -> Editor -> Inspections -> Kotlin). + +The `Severity` field (`level` in the config file) indicates how the inspections will be displayed in the upper right corner of the editor and in the Problems tab. Some of the possible values are: + +| Name | Config name | Example | +|--------------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------| +| Error | ERROR | image | +| Warning | WARNING | image | +| Weak Warning | WEAK WARNING | image | + +The `Highlighting` field (`editorAttributes` in the config file) indicates how the inspection will be highlighted in the IDE. Some of the possible values are: + +| Name | Config name | Example | +|---------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------| +| Error | ERRORS_ATTRIBUTES | image | +| Warning | WARNING_ATTRIBUTES | image | +| Weak Warning | INFO_ATTRIBUTES | image | +| Strikethrough | MARKED_FOR_REMOVAL_ATTRIBUTES | image | + +> [!NOTE] +> To alter the config file please use the Kotlin Inspections tab where you could turn on/off inspections and choose theirs severity and/or highlighting. + +Below are detailed descriptions of all the inspections in the current configuration file. + +## AddOperatorModifier + +**Severity**: Weak Warning
+**Highlighting**: Weak Warning
+ +
+Function should have 'operator' modifier + +Reports a function that matches one of the operator conventions but lacks the `operator` keyword. +By adding the `operator` modifier, you might allow function consumers to write idiomatic Kotlin code. + +**Example:** + +```kotlin +class Complex(val real: Double, val imaginary: Double) { + fun plus(other: Complex) = + Complex(real + other.real, imaginary + other.imaginary) +} + +fun usage(a: Complex, b: Complex) { + a.plus(b) +} +``` + +The quick-fix adds the `operator` modifier keyword: + +```kotlin +class Complex(val real: Double, val imaginary: Double) { + operator fun plus(other: Complex) = + Complex(real + other.real, imaginary + other.imaginary) +} + +fun usage(a: Complex, b: Complex) { + a + b +} +``` + +
+ +## AddVarianceModifier + +**Severity**: Weak Warning
+**Highlighting**: Weak Warning
+ +
+Type parameter can have 'in' or 'out' variance + +Reports type parameters that can have `in` or `out` variance. +Using `in` and `out` variance provides more precise type inference in Kotlin and clearer code semantics. + +**Example:** + +```kotlin +class Box(val obj: T) + +fun consumeString(box: Box) {} +fun consumeCharSequence(box: Box) {} + +fun usage(box: Box) { + consumeString(box) + consumeCharSequence(box) // Compilation error +} +``` + +The quick-fix adds the matching variance modifier: + +```kotlin +class Box(val obj: T) + +fun consumeString(box: Box) {} +fun consumeCharSequence(box: Box) {} + +fun usage(box: Box) ++{ + consumeString(box) + consumeCharSequence(box) // OK +} +``` + +
+ +## CanBeParameter + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Constructor parameter is never used as a property + +Reports primary constructor parameters that can have `val` or `var` removed. + +Class properties declared in the constructor increase memory consumption. +If the parameter value is only used in the constructor, you can omit them. + +Note that the referenced object might be garbage-collected earlier. + +**Example:** + +```kotlin +class Task(val name: String) { + init { + print("Task created: $name") + } +} +``` + +The quick-fix removes the extra `val` or `var` keyword: + +```kotlin +class Task(name: String) { + init { + print("Task created: $name") + } +} +``` + +
+ +## CanBePrimaryConstructorProperty + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Property is explicitly assigned to constructor parameter + +Reports properties that are explicitly assigned to primary constructor parameters. +Properties can be declared directly in the primary constructor, reducing the amount of code and increasing code readability. + +**Example:** + +```kotlin +class User(name: String) { + val name = name +} +``` + +The quick-fix joins the parameter and property declaration into a primary constructor parameter: + +```kotlin +class User(val name: String) { +} +``` + +
+ +## CanBeVal + +**Severity**: Error
+**Highlighting**: Error
+ +
+Local 'var' is never modified and can be declared as 'val' + +Reports local variables declared with the `var` keyword that are never modified. +Kotlin encourages to declare practically immutable variables using the `val` keyword, ensuring that their value will never change. + +**Example:** + +```kotlin +fun example() { + var primeNumbers = listOf(1, 2, 3, 5, 7, 11, 13) + var fibonacciNumbers = listOf(1, 1, 2, 3, 5, 8, 13) + print("Same numbers: " + primeNumbers.intersect(fibonacciNumbers)) +} +``` + +The quick-fix replaces the `var` keyword with `val`: + +```kotlin +fun example() { + val primeNumbers = listOf(1, 2, 3, 5, 7, 11, 13) + val fibonacciNumbers = listOf(1, 1, 2, 3, 5, 8, 13) + print("Same numbers: " + primeNumbers.intersect(fibonacciNumbers)) +} +``` + +
+ +## CascadeIf + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Cascade 'if' can be replaced with 'when' + +Reports `if` statements with three or more branches that can be replaced with the `when` expression. +**Example:** + +```kotlin +fun checkIdentifier(id: String) { + fun Char.isIdentifierStart() = this in 'A'..'z' + fun Char.isIdentifierPart() = isIdentifierStart() || this in '0'..'9' + + if (id.isEmpty()) { + print("Identifier is empty") + } else if (!id.first().isIdentifierStart()) { + print("Identifier should start with a letter") + } else if (!id.subSequence(1, id.length).all(Char::isIdentifierPart)) { + print("Identifier should contain only letters and numbers") + } +} +``` + +The quick-fix converts the `if` expression to `when`: + +```kotlin +fun checkIdentifier(id: String) { + fun Char.isIdentifierStart() = this in 'A'..'z' + fun Char.isIdentifierPart() = isIdentifierStart() || this in '0'..'9' + + when { + id.isEmpty() -> { + print("Identifier is empty") + } + !id.first().isIdentifierStart() -> { + print("Identifier should start with a letter") + } + !id.subSequence(1, id.length).all(Char::isIdentifierPart) -> { + print("Identifier should contain only letters and numbers") + } + } +} +``` + +
+ +## ClassName + +**Severity**: Error
+**Highlighting**: Error
+ +
+Class naming convention + +Reports class names that do not follow the recommended naming conventions. + +Consistent naming allows for easier code reading and understanding. +According to the [Kotlin official style guide](https://kotlinlang.org/docs/coding-conventions.html#naming-rules), +class names should start with an uppercase letter and use camel case. + +It is possible to introduce other naming rules by changing the "Pattern" regular expression. + +**Example:** + +```kotlin +class user(val name: String) +``` + +The quick-fix renames the class according to the Kotlin naming conventions: + +```kotlin +class User(val name: String) +``` + +
+ +## ControlFlowWithEmptyBody + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Control flow with empty body + +Reports `if`, `while`, `do` or `for` statements with empty bodies. +While occasionally intended, this construction is confusing and often the result of a typo. + +The quick-fix removes a statement. + +**Example:** + +```kotlin +if (a > b) {} +``` + +
+ +## ConvertLambdaToReference + +**Severity**: Information
+**Highlighting**: None
+ +
+Can be replaced with function reference + +Reports function literal expressions that can be replaced with function references. +Replacing lambdas with function references often makes code look more concise and understandable. + +**Example:** + +```kotlin +fun Int.isEven() = this % 2 == 0 + +fun example() { + val numbers = listOf(1, 2, 4, 7, 9, 10) + val evenNumbers = numbers.filter { it.isEven() } +} +``` + +After the quick-fix is applied: + +```kotlin +fun Int.isEven() = this % 2 == 0 + +fun example() { + val numbers = listOf(1, 2, 4, 7, 9, 10) + val evenNumbers = numbers.filter(Int::isEven) +} +``` + +
+ +## ConvertPairConstructorToToFunction + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Convert Pair constructor to 'to' function + +Reports a `Pair` constructor invocation that can be replaced with a `to()` infix function call. + +Explicit constructor invocations may add verbosity, especially if they are used multiple times. +Replacing constructor calls with `to()` makes code easier to read and maintain. + +**Example:** + +```kotlin +val countries = mapOf( + Pair("France", "Paris"), + Pair("Spain", "Madrid"), + Pair("Germany", "Berlin") +) +``` + +After the quick-fix is applied: + +```kotlin +val countries = mapOf( + "France" to "Paris", + "Spain" to "Madrid", + "Germany" to "Berlin" +) +``` + +
+ +## ConvertReferenceToLambda + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Can be replaced with lambda + +Reports a function reference expression that can be replaced with a function literal (lambda). + +Sometimes, passing a lambda looks more straightforward and more consistent with the rest of the code. +Also, the fix might be handy if you need to replace a simple call with something more complex. + +**Example:** + +```kotlin +fun Int.isEven() = this % 2 == 0 + +fun example() { + val numbers = listOf(1, 2, 4, 7, 9, 10) + val evenNumbers = numbers.filter(Int::isEven) +} +``` + +After the quick-fix is applied: + +```kotlin +fun Int.isEven() = this % 2 == 0 + +fun example() { + val numbers = listOf(1, 2, 4, 7, 9, 10) + val evenNumbers = numbers.filter { it.isEven() } +} +``` + +
+ +## ConvertTwoComparisonsToRangeCheck + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Two comparisons should be converted to a range check + +Reports two consecutive comparisons that can be converted to a range check. +Checking against a range makes code simpler by removing test subject duplication. + +**Example:** + +```kotlin +fun checkMonth(month: Int): Boolean { + return month >= 1 && month <= 12 +} +``` + +The quick-fix replaces the comparison-based check with a range one: + +```kotlin +fun checkMonth(month: Int): Boolean { + return month in 1..12 +} +``` + +
+ +## Destructure + +**Severity**: Weak Warning
+**Highlighting**: Weak Warning
+ +
+Use destructuring declaration + +Reports declarations that can be destructured. +**Example:** + +```kotlin +data class My(val first: String, val second: Int, val third: Boolean) + +fun foo(list: List) { + list.forEach { my -> + println(my.second) + println(my.third) + } +} +``` + +The quick-fix destructures the declaration and introduces new variables with names from the corresponding class: + +```kotlin +data class My(val first: String, val second: Int, val third: Boolean) + +fun foo(list: List) { + list.forEach { (_, second, third) -> + println(second) + println(third) + } +} +``` + +
+ +## ExplicitThis + +**Severity**: Error
+**Highlighting**: Strikethrough
+ +
+Redundant explicit 'this' + +Reports an explicit `this` when it can be omitted. +**Example:** + +```kotlin +class C { + private val i = 1 + fun f() = this.i +} +``` + +The quick-fix removes the redundant `this`: + +```kotlin +class C { + private val i = 1 + fun f() = i +} +``` + +
+ +## ForEachParameterNotUsed + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Iterated elements are not used in forEach + +Reports `forEach` loops that do not use iterable values. +**Example:** + +```kotlin +listOf(1, 2, 3).forEach { } +``` + +The quick fix introduces anonymous parameter in the `forEach` section: + +```kotlin +listOf(1, 2, 3).forEach { _ -> } +``` + +
+ +## FunctionName + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Function naming convention + +Reports function names that do not follow the recommended naming conventions. +**Example:** + +```kotlin +fun Foo() {} +``` + +To fix the problem change the name of the function to match the recommended naming conventions. + +
+ +## HasPlatformType + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Function or property has platform type + +Reports functions and properties that have a platform type. +To prevent unexpected errors, the type should be declared explicitly. + +**Example:** + +```kotlin +fun foo() = java.lang.String.valueOf(1) +``` + +The quick fix allows you to specify the return type: + +```kotlin +fun foo(): String = java.lang.String.valueOf(1) +``` + +
+ +## ImplicitThis + +**Severity**: Weak Warning
+**Highlighting**: Weak Warning
+ +
+Implicit 'this' + +Reports usages of implicit **this**. +**Example:** + +```kotlin +class Foo { + fun s() = "" + + fun test() { + s() + } +} +``` + +The quick fix specifies **this** explicitly: + +```kotlin +class Foo { + fun s() = "" + + fun test() { + this.s() + } +} +``` + +
+ +## IncompleteDestructuring + +**Severity**: Error
+**Highlighting**: Error
+ +
+Incomplete destructuring declaration + +Reports incomplete destructuring declaration. +**Example:** + +```kotlin +data class Person(val name: String, val age: Int) +val person = Person("", 0) +val (name) = person +``` + +The quick fix completes destructuring declaration with new variables: + +```kotlin +data class Person(val name: String, val age: Int) +val person = Person("", 0) +val (name, age) = person +``` + +
+ +## IntroduceWhenSubject + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+when' that can be simplified by introducing an argument + +Reports a `when` expression that can be simplified by introducing a subject argument. +**Example:** + +```kotlin +fun test(obj: Any): String { + return when { + obj is String -> "string" + obj is Int -> "int" + else -> "unknown" + } +} +``` + +The quick fix introduces a subject argument: + +```kotlin +fun test(obj: Any): String { + return when (obj) { + is String -> "string" + is Int -> "int" + else -> "unknown" + } +} +``` + +
+ +## JoinDeclarationAndAssignment + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Join declaration and assignment + +Reports property declarations that can be joined with the following assignment. +**Example:** + +```kotlin +val x: String +x = System.getProperty("") +``` + +The quick fix joins the declaration with the assignment: + +```kotlin +val x = System.getProperty("") +``` + +Configure the inspection: + +You can disable the option **Report with complex initialization of member properties** to skip properties with complex initialization. This covers two cases: + +1. The property initializer is complex (it is a multiline or a compound/control-flow expression) +2. The property is first initialized and then immediately used in subsequent code (for example, to call additional initialization methods) + +
+ +## KDocUnresolvedReference + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Unresolved reference in KDoc + +Reports unresolved references in KDoc comments. +**Example:** + +```kotlin +/** + * [unresolvedLink] + */ +fun foo() {} +``` + +To fix the problem make the link valid. + +
+ +## KotlinConstantConditions + +**Severity**: Error
+**Highlighting**: Error
+ +
+Constant conditions + +Reports non-trivial conditions and values that are statically known to be always true, false, null or zero. +While sometimes intended, often this is a sign of logical error in the program. Additionally, +reports never reachable `when` branches and some expressions that are statically known to fail always. +Examples: + +```kotlin +fun process(x: Int?) { + val isNull = x == null + if (!isNull) { + if (x != null) {} // condition is always true + require(x!! < 0 && x > 10) // condition is always false + } else { + println(x!!) // !! operator will always fail + } +} +fun process(v: Any) { + when(v) { + is CharSequence -> println(v as Int) // cast will always fail + is String -> println(v) // branch is unreachable + } +} +``` + +Uncheck the "Warn when constant is stored in variable" option to avoid reporting of variables having constant values not in conditions. + +New in 2021.3 + +
+ +## KotlinDeprecation + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Usage of redundant or deprecated syntax or deprecated symbols + +Reports obsolete language features and unnecessarily verbose code constructs during the code cleanup operation (**Code | Code Cleanup**). + +The quick-fix automatically replaces usages of obsolete language features or unnecessarily verbose code constructs with compact and up-to-date syntax. + +It also replaces deprecated symbols with their proposed substitutions. + +
+ +## KotlinEqualsBetweenInconvertibleTypes + +**Severity**: Error
+**Highlighting**: Error
+ +
+equals()' between objects of inconvertible types + +Reports calls to `equals()` where the receiver and the argument are +of incompatible primitive, enum, or string types. + +While such a call might theoretically be useful, most likely it represents a bug. + +**Example:** + +```kotlin +5.equals(""); +``` + +
+ +## KotlinUnusedImport + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Unused import directive + +Reports redundant `import` statements. + +Default and unused imports can be safely removed. + +**Example:** + +```kotlin +import kotlin.* +import kotlin.collections.* +import kotlin.comparisons.* +import kotlin.io.* +import kotlin.ranges.* +import kotlin.sequences.* +import kotlin.text.* + +// jvm specific +import java.lang.* +import kotlin.jvm.* + +// js specific +import kotlin.js.* + +import java.io.* // this import is unused and could be removed +import java.util.* + +fun foo(list: ArrayList) { + list.add("") +} +``` + +
+ +## LiftReturnOrAssignment + +**Severity**: Error
+**Highlighting**: Error
+ +
+Return or assignment can be lifted out + +Reports `if`, `when`, and `try` statements that can be converted to expressions +by lifting the `return` statement or an assignment out. +**Example:** + +```kotlin +fun foo(arg: Int): String { + when (arg) { + 0 -> return "Zero" + 1 -> return "One" + else -> return "Multiple" + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(arg: Int): String { + return when (arg) { + 0 -> "Zero" + 1 -> "One" + else -> "Multiple" + } +} +``` + +If you would like this inspection to highlight more complex code with multi-statement branches, uncheck the option "Report only if each branch is a single statement". + +
+ +## LocalVariableName + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Local variable naming convention + +Reports local variables that do not follow the naming conventions. +You can specify the required pattern in the inspection options. + +[Recommended naming conventions](https://kotlinlang.org/docs/coding-conventions.html#function-names): it has to start with a lowercase letter, use camel case and no underscores. + +**Example:** + +```kotlin +fun fibonacciNumber(index: Int): Long = when(index) { + 0 -> 0 + else -> { + // does not follow naming conventions: contains underscore symbol (`_`) + var number_one: Long = 0 + // does not follow naming conventions: starts with an uppercase letter + var NUMBER_TWO: Long = 1 + // follow naming conventions: starts with a lowercase letter, use camel case and no underscores. + var numberThree: Long = number_one + NUMBER_TWO + + for(currentIndex in 2..index) { + numberThree = number_one + NUMBER_TWO + number_one = NUMBER_TWO + NUMBER_TWO = numberThree + } + numberThree + } +} +``` + +
+ +## LoopToCallChain + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Loop can be replaced with stdlib operations + +Reports `for` loops that can be replaced with a sequence of stdlib operations (like `map`, `filter`, and so on). +**Example:** + +```kotlin +fun foo(list: List): List { + val result = ArrayList() + for (s in list) { + if (s.length > 0) + result.add(s.hashCode()) + } + return result +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(list: List): List { + val result = list + .filter { it.length > 0 } + .map { it.hashCode() } + return result +} +``` + +
+ +## MayBeConstant + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Might be 'const' + +Reports top-level `val` properties in objects that might be declared as `const` +for better performance and Java interoperability. +**Example:** + +```kotlin +object A { + val foo = 1 +} +``` + +After the quick-fix is applied: + +```kotlin +object A { + const val foo = 1 +} +``` + +
+ +## MemberVisibilityCanBePrivate + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Class member can have 'private' visibility + +Reports declarations that can be made `private` to follow the encapsulation principle. +**Example:** + +```kotlin +class Service(val url: String) { + fun connect(): URLConnection = URL(url).openConnection() +} +``` + +After the quick-fix is applied (considering there are no usages of `url` outside of `Service` class): + +```kotlin +class Service(private val url: String) { + fun connect(): URLConnection = URL(url).openConnection() +} +``` + +
+ +## MoveVariableDeclarationIntoWhen + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Variable declaration could be moved inside 'when' + +Reports variable declarations that can be moved inside a `when` expression. +**Example:** + +```kotlin +fun someCalc(x: Int) = x * 42 + +fun foo(x: Int): Int { + val a = someCalc(x) + return when (a) { + 1 -> a + 2 -> 2 * a + else -> 24 + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(x: Int): Int { + return when (val a = someCalc(x)) { + 1 -> a + 2 -> 2 * a + else -> 24 + } +} +``` + +
+ +## NestedLambdaShadowedImplicitParameter + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Nested lambda has shadowed implicit parameter + +Reports nested lambdas with shadowed implicit parameters. +**Example:** + +```kotlin +fun foo(listOfLists: List>) { + listOfLists.forEach { + it.forEach { + println(it) + } + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(listOfLists: List>) { + listOfLists.forEach { + it.forEach { it1 -> + println(it1) + } + } +} +``` + +
+ +## PrivatePropertyName + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Private property naming convention + +Reports private property names that do not follow the recommended naming conventions. + +Consistent naming allows for easier code reading and understanding. +According to the [Kotlin official style guide](https://kotlinlang.org/docs/coding-conventions.html#naming-rules), +private property names should start with a lowercase letter and use camel case. +Optionally, underscore prefix is allowed but only for **private** properties. + +It is possible to introduce other naming rules by changing the "Pattern" regular expression. + +**Example:** + +```kotlin +val _My_Cool_Property = "" +``` + +The quick-fix renames the class according to the Kotlin naming conventions: + +```kotlin +val _myCoolProperty = "" +``` + +
+ +## PublicApiImplicitType + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Public API declaration with implicit return type + +Reports `public` and `protected` functions and properties that have an implicit return type. +For API stability reasons, it's recommended to specify such types explicitly. + +**Example:** + +```kotlin +fun publicFunctionWhichAbusesTypeInference() = + otherFunctionWithNotObviousReturnType() ?: yetAnotherFunctionWithNotObviousReturnType() +``` + +After the quick-fix is applied: + +```kotlin +fun publicFunctionWhichAbusesTypeInference(): **Api** = + otherFunctionWithNotObviousReturnType() ?: yetAnotherFunctionWithNotObviousReturnType() +``` + +
+ +## RedundantElseInIf + +**Severity**: Error
+**Highlighting**: Strikethrough
+ +
+Redundant 'else' in 'if' + +Reports redundant `else` in `if` with `return` + +**Example:** + +```kotlin +fun foo(arg: Boolean): Int { + if (arg) return 0 + else { // This else is redundant, code in braces could be just shifted left + someCode() + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(arg: Boolean): Int { + if (arg) return 0 + someCode() +} +``` + +
+ +## RedundantExplicitType + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Obvious explicit type + +Reports local variables' explicitly given types which are obvious and thus redundant, like `val f: Foo = Foo()`. + +**Example:** + +```kotlin +class Point(val x: Int, val y: Int) + +fun foo() { + val t: **Boolean** = true + val p: **Point** = Point(1, 2) + val i: **Int** = 42 +} +``` + +After the quick-fix is applied: + +```kotlin +class Point(val x: Int, val y: Int) + +fun foo() { + val t = true + val p = Point(1, 2) + val i = 42 +} +``` + +
+ +## RedundantIf + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Redundant 'if' statement + +Reports `if` statements which can be simplified to a single statement. + +**Example:** + +```kotlin +fun test(): Boolean { + if (foo()) { + return true + } else { + return false + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun test(): Boolean { + return foo() +} +``` + +
+ +## RedundantNullableReturnType + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Redundant nullable return type + +Reports functions and variables with nullable return type which never return or become `null`. +**Example:** + +```kotlin +fun greeting(user: String): String? = "Hello, $user!" +``` + +After the quick-fix is applied: + +```kotlin +fun greeting(user: String): String = "Hello, $user!" +``` + +
+ +## RedundantSemicolon + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Redundant semicolon + +Reports redundant semicolons (`;`) that can be safely removed. + +Kotlin does not require a semicolon at the end of each statement or expression. +The quick-fix is suggested to remove redundant semicolons. + +**Example:** + +```kotlin +val myMap = mapOf("one" to 1, "two" to 2); +myMap.forEach { (key, value) -> print("$key -> $value")}; +``` + +After the quick-fix is applied: + +```kotlin +val myMap = mapOf("one" to 1, "two" to 2) +myMap.forEach { (key, value) -> print("$key -> $value")} +``` + +There are two cases though where a semicolon is required: + +1. Several statements placed on a single line need to be separated with semicolons: + +```kotlin +map.forEach { val (key, value) = it; println("$key -> $value") } +``` + +2. `enum` classes that also declare properties or functions, require a semicolon after the list of enum constants: + +```kotlin +enum class Mode { + SILENT, VERBOSE; + + fun isSilent(): Boolean = this == SILENT +} +``` + +
+ +## RedundantVisibilityModifier + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Redundant visibility modifier + +Reports visibility modifiers that match the default visibility of an element +(`public` for most elements, `protected` for members that override a protected member). + +
+ +## RemoveCurlyBracesFromTemplate + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Redundant curly braces in string template + +Reports usages of curly braces in string templates around simple identifiers. +Use the 'Remove curly braces' quick-fix to remove the redundant braces. + +**Examples:** + +```kotlin +fun redundant() { + val x = 4 + val y = "${x}" // <== redundant +} + +fun correctUsage() { + val x = "x" + val y = "${x.length}" // <== Ok +} +``` + +After the quick-fix is applied: + +```kotlin +fun redundant() { + val x = 4 + val y = "$x" +} + +fun correctUsage() { + val x = "x" <== Updated + val y = "${x.length}" +} +``` + +
+ +## RemoveEmptyParenthesesFromLambdaCall + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Unnecessary parentheses in function call with lambda + +Reports redundant empty parentheses of function calls where the only parameter is a lambda that's outside the parentheses. +Use the 'Remove unnecessary parentheses from function call with lambda' quick-fix to clean up the code. + +**Examples:** + +```kotlin +fun foo() { + listOf(1).forEach() { } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo() { + listOf(1).forEach { } +} +``` + +
+ +## RemoveEmptyPrimaryConstructor + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Redundant empty primary constructor + +Reports empty primary constructors when they are implicitly available anyway. + +A primary constructor is redundant and can be safely omitted when it does not have any annotations or visibility modifiers. +Use the 'Remove empty primary constructor' quick-fix to clean up the code. + +**Examples:** + +```kotlin +class MyClassA constructor() // redundant, can be replaced with 'class MyClassA' + +annotation class MyAnnotation +class MyClassB @MyAnnotation constructor() // required because of annotation + +class MyClassC private constructor() // required because of visibility modifier +``` + +
+ +## RemoveExplicitTypeArguments + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Unnecessary type argument + +Reports function calls with type arguments that can be automatically inferred. Such type arguments are redundant and can be safely omitted. +Use the 'Remove explicit type arguments' quick-fix to clean up the code. + +**Examples:** + +```kotlin +// 'String' type can be inferred here +fun foo(): MutableList = mutableListOf() + +// Here 'String' cannot be inferred, type argument is required. +fun bar() = mutableListOf() +``` + +After the quick-fix is applied: + +```kotlin +fun foo(): MutableList = mutableListOf() <== Updated + +fun bar() = mutableListOf() +``` + +
+ +## RemoveForLoopIndices + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Unused loop index + +Reports `for` loops iterating over a collection using the `withIndex()` function and not using the index variable. +Use the "Remove indices in 'for' loop" quick-fix to clean up the code. + +**Examples:** + +```kotlin +fun foo(bar: List) { + for ((index : Int, value: String) in bar.withIndex()) { // <== 'index' is unused + println(value) + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(bar: List) { + for (value: String in bar) { // <== '.withIndex()' and 'index' are removed + println(value) + } +} +``` + +
+ +## RemoveRedundantCallsOfConversionMethods + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Redundant call of conversion method + +Reports redundant calls to conversion methods (for example, `toString()` on a `String` or `toDouble()` +on a `Double`). +Use the 'Remove redundant calls of the conversion method' quick-fix to clean up the code. + +
+ +## RemoveRedundantQualifierName + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Redundant qualifier name + +Reports redundant qualifiers (or their parts) on class names, functions, and properties. + +A fully qualified name is an unambiguous identifier that specifies which object, function, or property a call refers to. +In the contexts where the name can be shortened, the inspection informs on the opportunity and the associated +'Remove redundant qualifier name' quick-fix allows amending the code. + +**Examples:** + +```kotlin +package my.simple.name +import kotlin.Int.Companion.MAX_VALUE + +class Foo + +fun main() { + val a = my.simple.name.Foo() // 'Foo' resides in the declared 'my.simple.name' package, qualifier is redundant + val b = kotlin.Int.MAX_VALUE // Can be replaced with 'MAX_VALUE' since it's imported + val c = kotlin.Double.MAX_VALUE // Can be replaced with 'Double.MAX_VALUE' since built-in types are imported automatically +} +``` + +After the quick-fix is applied: + +```kotlin +package my.simple.name +import kotlin.Int.Companion.MAX_VALUE + +class Foo + +fun main() { + val a = Foo() + val b = MAX_VALUE + val c = Double.MAX_VALUE +} +``` + +
+ +## RemoveSingleExpressionStringTemplate + +**Severity**: Error
+**Highlighting**: Error
+ +
+Redundant string template + +Reports single-expression string templates that can be safely removed. +**Example:** + +```kotlin +val x = "Hello" +val y = "$x" +``` + +After the quick-fix is applied: + +```kotlin +val x = "Hello" +val y = x // <== Updated +``` + +
+ +## RemoveToStringInStringTemplate + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Redundant call to 'toString()' in string template + +Reports calls to `toString()` in string templates that can be safely removed. +**Example:** + +```kotlin +fun foo(a: Int, b: Int) = a + b + +fun test(): String { + return "Foo: ${foo(0, 4).toString()}" // 'toString()' is redundant +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(a: Int, b: Int) = a + b + +fun test(): String { + return "Foo: ${foo(0, 4)}" +} +``` + +
+ +## ReplaceCallWithBinaryOperator + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Can be replaced with binary operator + +Reports function calls that can be replaced with binary operators, in particular comparison-related ones. +**Example:** + +```kotlin +fun test(): Boolean { + return 2.compareTo(1) > 0 // replaceable 'compareTo()' +} +``` + +After the quick-fix is applied: + +```kotlin +fun test(): Boolean { + return 2 > 1 +} +``` + +
+ +## ReplaceCollectionCountWithSize + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Collection count can be converted to size + +Reports calls to `Collection.count()`. + +This function call can be replaced with `.size`. + +`.size` form ensures that the operation is O(1) and won't allocate extra objects, whereas +`count()` could be confused with `Iterable.count()`, which is O(n) and allocating. + +**Example:** + +```kotlin +fun foo() { + var list = listOf(1,2,3) + list.count() // replaceable 'count()' +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo() { + var list = listOf(1,2,3) + list.size +} +``` + +
+ +## ReplaceGetOrSet + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Explicit 'get' or 'set' call + +Reports explicit calls to `get` or `set` functions which can be replaced by an indexing operator `[]`. + +Kotlin allows custom implementations for the predefined set of operators on types. +To overload an operator, you can mark the corresponding function with the `operator` modifier: + +```kotlin +operator fun get(index: Int) {} +operator fun set(index: Int, value: Int) {} +``` + +The functions above correspond to the indexing operator. + +**Example:** + +```kotlin +class Test { + operator fun get(i: Int): Int = 0 +} + +fun test() { + Test().get(0) // replaceable 'get()' +} +``` + +After the quick-fix is applied: + +```kotlin +class Test { + operator fun get(i: Int): Int = 0 +} + +fun test() { + Test()[0] +} +``` + +
+ +## ReplaceGuardClauseWithFunctionCall + +**Severity**: Error
+**Highlighting**: Strikethrough
+ +
+Guard clause can be replaced with Kotlin's function call + +Reports guard clauses that can be replaced with a function call. +**Example:** + +```kotlin +fun test(foo: Int?) { + if (foo == null) throw IllegalArgumentException("foo") // replaceable clause +} +``` + +After the quick-fix is applied: + +```kotlin +fun test(foo: Int?) { + checkNotNull(foo) +} +``` + +
+ +## ReplaceManualRangeWithIndicesCalls + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Range can be converted to indices or iteration + +Reports `until` and `rangeTo` operators that can be replaced with `Collection.indices` or iteration over collection inside `for` loop. +Using syntactic sugar makes your code simpler. + +The quick-fix replaces the manual range with the corresponding construction. + +**Example:** + +```kotlin +fun main(args: Array) { + for (index in 0..args.size - 1) { + println(args[index]) + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun main(args: Array) { + for (element in args) { + println(element) + } +} +``` + +
+ +## ReplaceRangeToWithUntil + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+rangeTo' or the '..' call should be replaced with 'until' + +Reports calls to `rangeTo` or the `..` operator instead of calls to `until`. +Using corresponding functions makes your code simpler. + +The quick-fix replaces `rangeTo` or the `..` call with `until`. + +**Example:** + +```kotlin +fun foo(a: Int) { + for (i in 0..a - 1) { + + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(a: Int) { + for (i in 0 until a) { + + } +} +``` + +
+ +## ReplaceReadLineWithReadln + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+readLine' can be replaced with 'readln' or 'readlnOrNull' + +Reports calls to `readLine()` that can be replaced with `readln()` or `readlnOrNull()`. + +Using corresponding functions makes your code simpler. + +The quick-fix replaces `readLine()!!` with `readln()` and `readLine()` with `readlnOrNull()`. + +**Examples:** + +```kotlin +val x = readLine()!! +val y = readLine()?.length +``` + +After the quick-fix is applied: + +```kotlin +val x = readln() +val y = readlnOrNull()?.length +``` + +
+ +## ReplaceSizeCheckWithIsNotEmpty + +**Severity**: Error
+**Highlighting**: Error
+ +
+Size check can be replaced with 'isNotEmpty()' + +Reports size checks of `Collections/Array/String` that should be replaced with `isNotEmpty()`. +Using `isNotEmpty()` makes your code simpler. + +The quick-fix replaces the size check with `isNotEmpty()`. + +**Example:** + +```kotlin +fun foo() { + val arrayOf = arrayOf(1, 2, 3) + arrayOf.size > 0 +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo() { + val arrayOf = arrayOf(1, 2, 3) + arrayOf.isNotEmpty() +} +``` + +
+ +## ReplaceSizeZeroCheckWithIsEmpty + +**Severity**: Error
+**Highlighting**: Error
+ +
+Size zero check can be replaced with 'isEmpty()' + +Reports `size == 0` checks on `Collections/Array/String` that should be replaced with `isEmpty()`. +Using `isEmpty()` makes your code simpler. + +The quick-fix replaces the size check with `isEmpty()`. + +**Example:** + +```kotlin +fun foo() { + val arrayOf = arrayOf(1, 2, 3) + arrayOf.size == 0 +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo() { + val arrayOf = arrayOf(1, 2, 3) + arrayOf.isEmpty() +} +``` + +
+ +## ReplaceSubstringWithTake + +**Severity**: Error
+**Highlighting**: Strikethrough
+ +
+substring' call should be replaced with 'take' call + +Reports calls like `s.substring(0, x)` that can be replaced with `s.take(x)`. +Using `take()` makes your code simpler. + +The quick-fix replaces the `substring` call with `take()`. + +**Example:** + +```kotlin +fun foo(s: String) { + s.substring(0, 10) +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo(s: String) { + s.take(10) +} +``` + +
+ +## ReplaceToStringWithStringTemplate + +**Severity**: Information
+**Highlighting**: None
+ +
+Call of 'toString' could be replaced with string template + +Reports `toString` function calls that can be replaced with a string template. +Using string templates makes your code simpler. + +The quick-fix replaces `toString` with a string template. + +**Example:** + +```kotlin +fun test(): String { + val x = 1 + return x.toString() +} +``` + +After the quick-fix is applied: + +```kotlin +fun test(): String { + val x = 1 + return "$x" +} +``` + +
+ +## ReplaceWithOperatorAssignment + +**Severity**: Error
+**Highlighting**: Error
+ +
+Assignment can be replaced with operator assignment + +Reports modifications of variables with a simple assignment (such as `y = y + x`) that can be replaced with an operator assignment. +The quick-fix replaces the assignment with an assignment operator. + +**Example:** + +```kotlin +fun foo() { + val list = mutableListOf(1, 2, 3) + list = list + 4 +} +``` + +After the quick-fix is applied: + +```kotlin +fun foo() { + val list = mutableListOf(1, 2, 3) + list += 4 +} +``` + +
+ +## ScopeFunctionConversion + +**Severity**: Information
+**Highlighting**: None
+ +
+Scope function can be converted to another one + +Reports scope functions (`let`, `run`, `apply`, `also`) that can be converted between each other. +Using corresponding functions makes your code simpler. + +The quick-fix replaces the scope function to another one. + +**Example:** + +```kotlin +val x = "".let { + it.length +} +``` + +After the quick-fix is applied: + +```kotlin +val x = "".run { + length +} +``` + +
+ +## SelfAssignment + +**Severity**: Error
+**Highlighting**: Error
+ +
+Redundant assignment + +Reports assignments of a variable to itself. +The quick-fix removes the redundant assignment. + +**Example:** + +```kotlin +fun test() { + var bar = 1 + bar = bar +} +``` + +After the quick-fix is applied: + +```kotlin +fun test() { + var bar = 1 +} +``` + +
+ +## SimplifiableCallChain + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Call chain on collection type can be simplified + +Reports two-call chains replaceable by a single call. +It can help you to avoid redundant code execution. + +The quick-fix replaces the call chain with a single call. + +**Example:** + +```kotlin +fun main() { + listOf(1, 2, 3).filter { it > 1 }.count() +} +``` + +After the quick-fix is applied: + +```kotlin +fun main() { + listOf(1, 2, 3).count { it > 1 } +} +``` + +
+ +## SimplifyBooleanWithConstants + +**Severity**: Error
+**Highlighting**: Error
+ +
+Boolean expression can be simplified + +Reports boolean expression parts that can be reduced to constants. +The quick-fix simplifies the condition. + +**Example:** + +```kotlin +fun use(arg: Boolean) { + if (false == arg) { + + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun use(arg: Boolean) { + if (!arg) { + + } +} +``` + +
+ +## TrailingComma + +**Severity**: Error
+**Highlighting**: Error
+ +
+Trailing comma recommendations + +Reports trailing commas that do not follow the recommended [style guide](https://kotlinlang.org/docs/coding-conventions.html#trailing-commas). + +
+ +## UnlabeledReturnInsideLambda + +**Severity**: Error
+**Highlighting**: Error
+ +
+Unlabeled return inside lambda + +Reports unlabeled `return` expressions inside inline lambda. +Such expressions can be confusing because it might be unclear which scope belongs to `return`. + +**Change to return@…** quick-fix can be used to amend the code automatically. + +Example: + +```kotlin +fun test(list: List) { + list.forEach { + // This return expression returns from the function test + // One can change it to return@forEach to change the scope + if (it == 10) return + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun test(list: List) { + list.forEach { + if (it == 10) return@test + } +} +``` + +
+ +## UnnecessaryVariable + +**Severity**: Weak Warning
+**Highlighting**: Strikethrough
+ +
+Unnecessary local variable + +Reports local variables that are used only in the very next `return` statement or are exact copies of other variables. +Such variables can be safely inlined to make the code more clear. + +**Example:** + +```kotlin +fun sum(a: Int, b: Int): Int { + val c = a + b + return c +} +``` + +After the quick-fix is applied: + +```kotlin +fun sum(a: Int, b: Int): Int { + return a + b +} +``` + +Configure the inspection: + +Use the **Report immediately returned variables** option to report immediately returned variables. +When given descriptive names, such variables may improve the code readability in some cases, that's why this option is disabled by default. + +
+ +## UnusedEquals + +**Severity**: Error
+**Highlighting**: Error
+ +
+Unused equals expression + +Reports unused `equals`(`==`) expressions. + +
+ +## UnusedReceiverParameter + +**Severity**: Warning
+**Highlighting**: Strikethrough
+ +
+Unused receiver parameter + +Reports receiver parameter of extension functions and properties that is not used. +**Remove redundant receiver parameter** can be used to amend the code automatically. + +
+ +## UseExpressionBody + +**Severity**: Warning
+**Highlighting**: Warning
+ +
+Expression body syntax is preferable here + +Reports `return` expressions (one-liners or `when`) that can be replaced with expression body syntax. +Expression body syntax is recommended by the [style guide](https://kotlinlang.org/docs/coding-conventions.html#functions). + +**Convert to expression body** quick-fix can be used to amend the code automatically. + +Example: + +```kotlin +fun sign(x: Int): Int { + return when { // <== can be simplified + x < 0 -> -1 + x > 0 -> 1 + else -> 0 + } +} +``` + +After the quick-fix is applied: + +```kotlin +fun sign(x: Int): Int = when { + x < 0 -> -1 + x > 0 -> 1 + else -> 0 +} +``` + +
+ +## WarningOnMainUnusedParameterMigration + +**Severity**: Weak Warning
+**Highlighting**: Weak Warning
+ +
+Unused 'args' on 'main' since 1.4 + +Reports `main` function with an unused single parameter. +Since Kotlin 1.4, it is possible to use the `main` function without parameter as the entry point to the Kotlin program. +The compiler reports a warning for the `main` function with an unused parameter. + +
+ diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..76c9db1 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.run/Build course with detekt.run.xml b/.run/Build course with detekt.run.xml new file mode 100644 index 0000000..9a8e917 --- /dev/null +++ b/.run/Build course with detekt.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/.run/Build course.run.xml b/.run/Build course.run.xml new file mode 100644 index 0000000..2e0d992 --- /dev/null +++ b/.run/Build course.run.xml @@ -0,0 +1,24 @@ + + + + + + + true + true + false + false + + + \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a6be2e0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 JetBrains Academy + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ed9c002 --- /dev/null +++ b/README.md @@ -0,0 +1,286 @@ +# JetBrains Academy Kotlin Course Template + +[![official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) +[![Gradle Build](https://github.com/jetbrains-academy/kotlin-course-template/actions/workflows/gradle-build.yml/badge.svg)](https://github.com/jetbrains-academy/kotlin-course-template/actions/workflows/gradle-build.yml) +[![Gradle Build With Detekt](https://github.com/jetbrains-academy/kotlin-course-template/actions/workflows/gradle-build-with-detekt.yml/badge.svg)](https://github.com/jetbrains-academy/kotlin-course-template/actions/workflows/gradle-build-with-detekt.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +> ![NOTE] +> Click the Use this template button and clone it in IntelliJ IDEA. + +**JetBrains Academy Kotlin course template** is a repository that provides a +pure template to make it easier to create a new Kotlin course with the [JetBrains Academy +plugin][ref:plugin.marketplace] (check out the [Creating a repository from a template][gh:template] article). + +The main goal of this template is to speed up the setup phase +of Kotlin course development for both new and experienced educators +by preconfiguring the project scaffold and CI, +linking to the proper documentation pages, and keeping everything organized. + +If you're still not quite sure what this is all about, read our introduction: [What is the JetBrains Academy plugin?][docs:intro] + +> ![NOTE] +> Click the Watch button on the top to be notified about releases containing new features and fixes. + +### Table of contents + +In this README, we will highlight the following elements of template-project creation: + +- [Getting started](#getting-started) +- [Gradle configuration](#gradle-configuration) +- [Course info configuration file](#course-info-configuration-file) +- [Course ignore file](#course-ignore-file) +- [Sample code](#sample-code) +- [Adapted inspections](#adapted-inspections) +- [Testing](#testing) +- [Predefined Run/Debug configurations](#predefined-rundebug-configurations) +- [Continuous integration](#continuous-integration) +- [Useful links](#useful-links) + +## Getting started + +Before we dive into course development and everything related to it, it's worth mentioning the benefits of using GitHub Templates. +By creating a new project with the current template, you start with no history or reference to this repository. +This allows you to create a new repository easily, without copying and pasting previous content, cloning repositories, or clearing the history manually. + +All you need to do is click the Use this template button (you must be logged in with your GitHub account). + +![Use this template][file:use-template-blur] + +The most convenient way of getting your new project from GitHub is the Get from VCS action available on the Welcome Screen, +where you can filter your GitHub repository by its name. + +![Use this template][file:use-this-template.png] + + +As the last step, you need to manually review the configuration variables described in the [`gradle.properties`][file:gradle.properties] file and *optionally* move sources from the *org.jetbrains.academy.kotlin.template* package to the one that works best for you. +Then you can get to work and implement your ideas. + +## Gradle configuration + +The recommended method for Kotlin course development involves using the [Gradle][gradle] setup. + +A course built using the JetBrains Academy Kotlin course template includes a Gradle configuration already set up. +This gradle file sets up all base dependencies and plugins for the course. +For each gradle module (each task in the course and extra modules like `common` as well) +it includes [JUnit5][ref:junit5] tests, [Kotlin test framework][ref:kotlin.test.framework], and [Detekt][ref:detekt]. +It also marks the `source` and `test` folders as source- and test- source sets in the project. + +### Gradle properties + +The project-specific configuration file [`gradle.properties`][file:gradle.properties] contains: + +| Property name | Description | +|---------------------|---------------------------------------------------------------| +| `courseGroup` | Package name. | +| `courseVersion` | The current version of the course in [SemVer][semver] format. | +| `gradleVersion` | Version of Gradle used for course development. | +| `jvmVersion` | Version of the JVM used for course development. | + +## Course template structure + +A generated JetBrains Academy Kotlin Course Template repository contains the following content structure: + +``` +. +├── .github/ GitHub Actions workflows +├── .idea/ +│ └── inspectionProfiles/ Adapted inspection files +│ ├── Custom_Inspections.xml Inspection config +│ ├── profiles_settings.xml Inspection profile settings +│ └── README.md Inspection descriptions +├── .run/ Predefined Run/Debug Configurations +├── build/ Output build directory +├── gradle +│ └── wrapper/ Gradle Wrapper +├── common Course sources common for all sections +│ └── src +│ └── main +│ ├── kotlin/ Kotlin production sources +│ └── resources/ Resources - images, icons +├── courseSection/ An example of the course section +│ ├── courseLesson/ An example of the course lesson +│ │ ├── theoryTask/ An example of a theory task +│ │ │ ├── src/ Task sources +│ │ │ │ └── ... +│ │ │ ├── task.md Task/theory description +│ │ │ └── task-info.yaml Task config file +│ │ ├── quizTask/ An example of a quiz task +│ │ │ ├── src/ Task sources +│ │ │ │ └── ... +│ │ │ ├── task.md Task/quiz description +│ │ │ └── task-info.yaml Task config file +│ │ ├── programmingTask/ An example of a programming task +│ │ │ ├── src/ Task sources +│ │ │ │ └── ... +│ │ │ ├── test/ Task tests +│ │ │ │ └── ... +│ │ │ ├── task.md Task description +│ │ │ └── task-info.yaml Task config file +│ │ └── lesson-info.yaml Lesson config file +│ ├── courseFrameworkLesson/ An example of the course framework lesson +│ │ ├── ... Several examples of lessons +│ │ └── lesson-info.yaml Lesson config file +│ └── section-info.yaml Section config file +├── .courseignore Course ignoring rules +├── .gitignore Git ignoring rules +├── build.gradle.kts Gradle configuration +├── course-info.yaml Course info configuration file +├── detekt.yml Detekt configuration file +├── gradle.properties Gradle configuration properties +├── gradlew *nix Gradle Wrapper script +├── gradlew.bat Windows Gradle Wrapper script +├── LICENSE License, MIT by default +├── README.md README +└── settings.gradle.kts Gradle project settings +``` + +## Course info configuration file + +The course info configuration file is the [course-info.yaml][file:course-info.yaml] file located in the root directory. +It provides general information about the course, like description, language, etc. + +```yaml +type: marketplace +title: JetBrains Academy Kotlin course template +language: English +summary: Course description +programming_language: Kotlin +content: + - courseSection +environment_settings: + jvm_language_level: JDK_17 +``` + +## Course ignore file + +The course ignore file is the [.courseignore][file:courseignore] file located in the root directory. +It provides the list of files or directories that will be ignored in the final course preview or archive. + +```text +README.md +/.run +``` + +You can find more information about the course preview in the [Course preview][ref:course.preview] section. Information +about creating a course archive and uploading it to the marketplace is in the [Course distribution][ref:course.distribution] section. + +## Sample code + +The prepared template provides an example of a course with one section, two lessons, and five tasks in total. + +![Course structure in the course creator mode][file:course-structure-author] + +Each course may have an unlimited number of sections, lessons, and tasks. +Students will see almost the same course structure as the educator (course author): + +![Course structure in the course student mode][file:course-structure-student] + +The main difference is in framework lessons, which display +only task files, without intermediate steps. + +You can read more about framework lessons in the official documentation in the [Framework Lessons Creation][ref:framework.lessons.creation] section. + +> [!NOTE] +> Click Course Creator -> Create Course Preview in the context menu in the root of the repository to create a course preview. + + +The JetBrains Academy plugin provides five different types of tasks, +and you can combine them inside one lesson (whether regular or framework). +You can read more about tasks in the official documentation in the [Task][ref:tasks] section. + +## Adapted inspections +The template also includes adapted Kotlin inspections in order to provide better learning experience. +This means that the IDE will highlight the most popular code issue that students encounter. +To learn more about the inspections please refer to [this][file:inspections.readme] README file. + +If you don't want to use this inspections, then just delete the [inspectionProfiles][file:inspections] folder. + +## Testing + +To check the programming exercises for [**edu**][ref:tasks] tasks, you need to write tests. +This repository includes a [Kotlin test framework][ref:kotlin.test.framework] to make the testing process easier. +It contains functionality to test student solutions by using the [Java Reflection API][ref:java.reflection.api] under the hood. +This approach allows you to call students' functions that do not exist yet. +It is a powerful mechanism that enables you to create excesses without predefined classes or function +templates and check their signature and behaviour properly. + +You can find little examples of programming tasks in the repository in the `Tests.kt` files: +in [course lesson][file:course.lesson.tests] and [course framework lesson][file:course.framework.lesson.tests]. + +More examples of using the [Kotlin test framework][ref:kotlin.test.framework] can be found in other Kotlin courses: + +- [Kotlin Onboarding: Introduction][ref:kotlin.onboarding.introduction.marketplace] on [GitHub][ref:kotlin.onboarding.introduction.github] +- [Kotlin Onboarding: Object-Oriented programming][ref:kotlin.onboarding.oop.marketplace] on [GitHub][ref:kotlin.onboarding.oop.github] + +## Predefined Run/Debug configurations + +Within the default project structure, there is a `.run` directory provided, which contains predefined *Run/Debug configurations* that expose corresponding Gradle tasks: + +![Run/Debug configurations][file:run-debug-configurations] + +| Configuration name | Description | +|--------------------------|--------------------------------------------------------------------------------| +| Build course | Runs `:build` Gradle task with tests only. | +| Build course with detekt | Runs `:build` Gradle task with tests and [Detekt][ref:detekt] static analysis. | + +## Continuous integration + +Continuous integration depends on [GitHub Actions][gh:actions], a set of workflows that make it possible to automate your testing and release process. +Thanks to such automation, you can delegate the testing and verification phases to the Continuous Integration (CI) and instead focus on development (and writing more tests). + +In the `.github/workflows` directory, you can find definitions for the following GitHub Actions workflows: +- [Build](.github/workflows/gradle-build.yml) + - Builds your course + - Runs all tests for all tasks +- [Build with Detekt](.github/workflows/gradle-build-with-detekt.yml) + - Builds your course + - Runs all tests for all tasks + - Runs [Detekt][ref:detekt] checks + +## Useful links + +- [JetBrains Academy plugin][ref:plugin.marketplace] +- [Course creator start guide][ref:course.creator.start.guide] +- [Kotlin test framework][ref:kotlin.test.framework] +- [Courses on Marketplace][ref:marketplace] + +[gh:actions]: https://help.github.com/en/actions +[gh:template]: https://docs.github.com/en/repositories/creating-and-managing-repositories/creating-a-repository-from-a-template + +[ref:marketplace]: https://plugins.jetbrains.com/education +[ref:course.creator.start.guide]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/educator-start-guide.html +[ref:plugin.marketplace]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy +[ref:course.preview]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/educator-start-guide.html#preview_course +[ref:course.distribution]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/educator-start-guide.html#course_distribution +[ref:framework.lessons.creation]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/framework-lessons-guide-for-course-creators.html#a81e8983 +[ref:tasks]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/framework-lessons-guide-for-course-creators.html#a81e8983 +[ref:kotlin.test.framework]: https://github.com/jetbrains-academy/kotlin-test-framework +[ref:java.reflection.api]: https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/index.html +[ref:detekt]: https://github.com/detekt/detekt +[ref:junit5]: https://junit.org/junit5/ + +[ref:kotlin.onboarding.introduction.marketplace]: https://plugins.jetbrains.com/plugin/21067-kotlin-onboarding-introduction +[ref:kotlin.onboarding.introduction.github]: https://github.com/jetbrains-academy/kotlin-onboarding-introduction +[ref:kotlin.onboarding.oop.marketplace]: https://plugins.jetbrains.com/plugin/21913-kotlin-onboarding-object-oriented-programming +[ref:kotlin.onboarding.oop.github]: https://github.com/jetbrains-academy/kotlin-onboarding-object-oriented-programming + +[docs:intro]: https://plugins.jetbrains.com/plugin/10081-jetbrains-academy/docs/jetbrains-academy-plugin-faq.html#what_is_the_jetbrains_academy_plugin + +[file:gradle.properties]: ./gradle.properties +[file:course-info.yaml]: ./course-info.yaml +[file:courseignore]: .courseignore +[file:course.lesson.tests]: ./courseSection/courseLesson/programmingTask/test/Tests.kt +[file:course.framework.lesson.tests]: ./courseSection/courseFrameworkLesson/programmingTask/test/Tests.kt +[file:inspections]: ./.idea/inspectionProfiles +[file:inspections.readme]: ./.idea/inspectionProfiles/README.md + +[gradle]: https://gradle.org + +[semver]: https://semver.org + +[file:use-this-template.png]: common/src/main/resources/images/get-from-version-control.png +[file:course-structure-author]: common/src/main/resources/images/course-structure-author.png +[file:course-structure-student]: common/src/main/resources/images/course-structure-student.png +[file:run-debug-configurations]: common/src/main/resources/images/run-debug-configurations.png +[file:use-template-blur]: common/src/main/resources/images/use_template_blur.jpg diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..7b52774 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,125 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +fun gradleProperties(key: String) = providers.gradleProperty(key) + +group = gradleProperties("courseGroup").get() +version = gradleProperties("courseVersion").get() + +plugins { + java + val kotlinVersion = "1.9.0" + id("org.jetbrains.kotlin.jvm") version kotlinVersion apply false + id("io.gitlab.arturbosch.detekt") version "1.23.1" +} + +val detektReportMerge by tasks.registering(io.gitlab.arturbosch.detekt.report.ReportMergeTask::class) { + output.set(rootProject.buildDir.resolve("reports/detekt/merge.sarif")) +} + +allprojects { + repositories { + mavenCentral() + maven { + // To be able to use the Kotlin test framework for the tests - https://github.com/jetbrains-academy/kotlin-test-framework + url = uri("https://packages.jetbrains.team/maven/p/kotlin-test-framework/kotlin-test-framework") + } + } +} + +tasks { + wrapper { + gradleVersion = gradleProperties("gradleVersion").get() + } +} + +// Configure dependencies for all gradle modules +configure(subprojects) { + apply() + + apply { + plugin("java") + plugin("kotlin") + } + + // Configure detekt + configure { + config = rootProject.files("detekt.yml") + buildUponDefaultConfig = true + debug = true + } + tasks.withType { + finalizedBy(detektReportMerge) + reports.sarif.required.set(true) + detektReportMerge.get().input.from(sarifReportFile) + } + tasks.getByPath("detekt").onlyIf { project.hasProperty("runDetekt") } + + // Include dependencies + dependencies { + // By default, only the core module is included + implementation("org.jetbrains.academy.test.system:core:2.0.5") + testImplementation("junit:junit:4.13.2") + + val junitJupiterVersion = "5.9.0" + implementation("org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion") + runtimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion") + implementation("org.junit.jupiter:junit-jupiter-params:$junitJupiterVersion") + runtimeOnly("org.junit.platform:junit-platform-console:1.9.0") + + val detektVersion = "1.22.0" + implementation("io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detektVersion") + implementation("io.gitlab.arturbosch.detekt:detekt-formatting:$detektVersion") + } + + val jvmVersion = gradleProperties("jvmVersion").get() + tasks { + withType { + kotlinOptions { + freeCompilerArgs = listOf("-Xjsr305=strict") + jvmTarget = jvmVersion + } + } + withType { + sourceCompatibility = jvmVersion + targetCompatibility = jvmVersion + } + + // This part is necessary for the JetBrains Academy plugin + withType { + useJUnitPlatform() + + outputs.upToDateWhen { false } + + addTestListener(object : TestListener { + override fun beforeSuite(suite: TestDescriptor) {} + override fun beforeTest(testDescriptor: TestDescriptor) {} + override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { + if (result.resultType == TestResult.ResultType.FAILURE) { + val message = result.exception?.message ?: "Wrong answer" + val lines = message.split("\n") + println("#educational_plugin FAILED + ${lines[0]}") + lines.subList(1, lines.size).forEach { line -> + println("#educational_plugin$line") + } + // we need this to separate output of different tests + println() + } + } + + override fun afterSuite(suite: TestDescriptor, result: TestResult) {} + }) + } + } +} + +// We have to store tests inside test folder directly +configure(subprojects.filter { it.name != "common" }) { + sourceSets { + getByName("main").java.srcDirs("src") + getByName("test").java.srcDirs("test") + } + + dependencies { + implementation(project(":common")) + } +} diff --git a/common/build.gradle.kts b/common/build.gradle.kts new file mode 100644 index 0000000..27c6f70 --- /dev/null +++ b/common/build.gradle.kts @@ -0,0 +1,2 @@ +group = rootProject.group +version = rootProject.version diff --git a/common/src/main/kotlin/org/jetbrains/academy/kotlin/template/CourseUtils.kt b/common/src/main/kotlin/org/jetbrains/academy/kotlin/template/CourseUtils.kt new file mode 100644 index 0000000..4b19529 --- /dev/null +++ b/common/src/main/kotlin/org/jetbrains/academy/kotlin/template/CourseUtils.kt @@ -0,0 +1,51 @@ +package org.jetbrains.academy.kotlin.template + +import java.io.ByteArrayOutputStream +import java.io.PrintStream +import java.nio.charset.StandardCharsets + +val newLineSeparator: String = System.lineSeparator() + +fun throwInternalCourseError(): Nothing = error("Internal course error!") + +fun setSystemIn(input: List? = null) = setSystemIn(input?.joinToString(newLineSeparator)) + +fun String.replaceLineSeparator() = this.lines().joinToString(newLineSeparator) + +fun setSystemIn(input: String? = null) = input?.let { + System.setIn(it.replaceLineSeparator().byteInputStream()) +} + +fun setSystemOut(): ByteArrayOutputStream { + val baos = ByteArrayOutputStream() + val ps = PrintStream(baos, true, StandardCharsets.UTF_8.name()) + System.setOut(ps) + return baos +} + +fun isSystemInEmpty() = String(System.`in`.readBytes()).isEmpty() + +@Suppress("SwallowedException") +fun runMainFunction(mainFunction: () -> Unit, input: String? = null, toAssertSystemIn: Boolean = true): String { + return try { + setSystemIn(input) + val baos = setSystemOut() + mainFunction() + if (toAssertSystemIn) { + assert(isSystemInEmpty()) { "You are asking the user to enter data fewer times than required in the task!" } + } + baos.toString("UTF-8").replaceLineSeparator() + } catch (e: IllegalStateException) { + val userInput = input?.let { "the user input: $it" } ?: "the empty user input" + val errorPrefix = + "Try to run the main function with $userInput, the function must process the input and exit, but the current version of the function" + if ("Your input is incorrect" in (e.message ?: "")) { + assert(false) { "$errorPrefix waits more user inputs, it must be fixed." } + } + assert(false) { "$errorPrefix throws an unexpected error, please, check the function's implementation." } + "" + } catch (e: NotImplementedError) { + assert(false) { "You call not implemented functions (that use TODO()) inside the main function. Please, don't do it until the task asks it" } + "" + } +} diff --git a/common/src/main/resources/images/course-structure-author.png b/common/src/main/resources/images/course-structure-author.png new file mode 100644 index 0000000..cf8ae5f Binary files /dev/null and b/common/src/main/resources/images/course-structure-author.png differ diff --git a/common/src/main/resources/images/course-structure-student.png b/common/src/main/resources/images/course-structure-student.png new file mode 100644 index 0000000..cb91a54 Binary files /dev/null and b/common/src/main/resources/images/course-structure-student.png differ diff --git a/common/src/main/resources/images/files-order.gif b/common/src/main/resources/images/files-order.gif new file mode 100644 index 0000000..b19689e Binary files /dev/null and b/common/src/main/resources/images/files-order.gif differ diff --git a/common/src/main/resources/images/get-from-version-control.png b/common/src/main/resources/images/get-from-version-control.png new file mode 100644 index 0000000..a15cb89 Binary files /dev/null and b/common/src/main/resources/images/get-from-version-control.png differ diff --git a/common/src/main/resources/images/logo.png b/common/src/main/resources/images/logo.png new file mode 100644 index 0000000..e31ce07 Binary files /dev/null and b/common/src/main/resources/images/logo.png differ diff --git a/common/src/main/resources/images/logo_dark.png b/common/src/main/resources/images/logo_dark.png new file mode 100644 index 0000000..a8a5ca6 Binary files /dev/null and b/common/src/main/resources/images/logo_dark.png differ diff --git a/common/src/main/resources/images/run-debug-configurations.png b/common/src/main/resources/images/run-debug-configurations.png new file mode 100644 index 0000000..cd735ce Binary files /dev/null and b/common/src/main/resources/images/run-debug-configurations.png differ diff --git a/common/src/main/resources/images/theme_example.gif b/common/src/main/resources/images/theme_example.gif new file mode 100644 index 0000000..59432dc Binary files /dev/null and b/common/src/main/resources/images/theme_example.gif differ diff --git a/common/src/main/resources/images/use_template_blur.jpg b/common/src/main/resources/images/use_template_blur.jpg new file mode 100644 index 0000000..89f86ed Binary files /dev/null and b/common/src/main/resources/images/use_template_blur.jpg differ diff --git a/course-info.yaml b/course-info.yaml new file mode 100644 index 0000000..8b4e103 --- /dev/null +++ b/course-info.yaml @@ -0,0 +1,9 @@ +type: marketplace +title: JetBrains Academy Kotlin course template +language: English +summary: "Here you can put the course description. You can use HTML tags inside the description." +programming_language: Kotlin +content: + - courseSection +environment_settings: + jvm_language_level: JDK_17 diff --git a/courseSection/courseFrameworkLesson/lesson-info.yaml b/courseSection/courseFrameworkLesson/lesson-info.yaml new file mode 100644 index 0000000..3bfc725 --- /dev/null +++ b/courseSection/courseFrameworkLesson/lesson-info.yaml @@ -0,0 +1,6 @@ +type: framework +custom_name: Course framework lesson +content: + - theoryTask + - programmingTask +is_template_based: false diff --git a/courseSection/courseFrameworkLesson/programmingTask/src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt b/courseSection/courseFrameworkLesson/programmingTask/src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt new file mode 100644 index 0000000..cd63678 --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt @@ -0,0 +1,6 @@ +package org.jetbrains.academy.kotlin.template + +// This file will be hidden in the student mode +// Since this file is invisible, it can be changed between tasks +@Suppress("FunctionOnlyReturningConstant") +fun sayBye() = "Bye" diff --git a/courseSection/courseFrameworkLesson/programmingTask/src/org/jetbrains/academy/kotlin/template/Main.kt b/courseSection/courseFrameworkLesson/programmingTask/src/org/jetbrains/academy/kotlin/template/Main.kt new file mode 100644 index 0000000..1596b0f --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/src/org/jetbrains/academy/kotlin/template/Main.kt @@ -0,0 +1,13 @@ +package org.jetbrains.academy.kotlin.template + +// Since the file from the first lesson will be propagated, +// you can put the solution here +fun invokeSayBye(howManyTimes: Int): String { + return List(howManyTimes) { sayBye() }.joinToString(System.lineSeparator()) +} + +fun main() { + println("How many times should I print Bye?") + val howManyTimes = readln().toInt() + println(invokeSayBye(howManyTimes)) +} diff --git a/courseSection/courseFrameworkLesson/programmingTask/task-info.yaml b/courseSection/courseFrameworkLesson/programmingTask/task-info.yaml new file mode 100644 index 0000000..98134f0 --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/task-info.yaml @@ -0,0 +1,12 @@ +type: edu +files: + - name: src/org/jetbrains/academy/kotlin/template/Main.kt + visible: true + - name: src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt + visible: false + - name: test/InvokeSayByeFunction.kt + visible: false + - name: test/MainClass.kt + visible: false + - name: test/Tests.kt + visible: false diff --git a/courseSection/courseFrameworkLesson/programmingTask/task.md b/courseSection/courseFrameworkLesson/programmingTask/task.md new file mode 100644 index 0000000..109e4fb --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/task.md @@ -0,0 +1,3 @@ +In this task, the user needs to implement some functions. +You can check the user mode – the content of the `Main.kt` file will be propagated from the previous step; +however, `InvisibleFile.kt` will be changed. diff --git a/courseSection/courseFrameworkLesson/programmingTask/test/InvokeSayByeFunction.kt b/courseSection/courseFrameworkLesson/programmingTask/test/InvokeSayByeFunction.kt new file mode 100644 index 0000000..483bfea --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/test/InvokeSayByeFunction.kt @@ -0,0 +1,11 @@ +import org.jetbrains.academy.test.system.core.models.TestKotlinType +import org.jetbrains.academy.test.system.core.models.method.TestMethod +import org.jetbrains.academy.test.system.core.models.variable.TestVariable + +internal val invokeSayByeFunction = TestMethod( + "invokeSayBye", + TestKotlinType("String"), + arguments = listOf( + TestVariable("howManyTimes", "Int"), + ), +) diff --git a/courseSection/courseFrameworkLesson/programmingTask/test/MainClass.kt b/courseSection/courseFrameworkLesson/programmingTask/test/MainClass.kt new file mode 100644 index 0000000..ec121fd --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/test/MainClass.kt @@ -0,0 +1,8 @@ +import org.jetbrains.academy.test.system.core.models.classes.TestClass + +internal val mainClass = TestClass( + classPackage = "org.jetbrains.academy.kotlin.template", + customMethods = listOf( + invokeSayByeFunction, + ), +) diff --git a/courseSection/courseFrameworkLesson/programmingTask/test/Tests.kt b/courseSection/courseFrameworkLesson/programmingTask/test/Tests.kt new file mode 100644 index 0000000..9344d6d --- /dev/null +++ b/courseSection/courseFrameworkLesson/programmingTask/test/Tests.kt @@ -0,0 +1,58 @@ +import org.jetbrains.academy.kotlin.template.main +import org.jetbrains.academy.kotlin.template.newLineSeparator +import org.jetbrains.academy.kotlin.template.runMainFunction +import org.jetbrains.academy.kotlin.template.throwInternalCourseError +import org.jetbrains.academy.test.system.core.invokeWithArgs +import org.jetbrains.academy.test.system.core.models.classes.findClassSafe +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +class Test { + companion object { + private lateinit var mainClazz: Class<*> + + @JvmStatic + @BeforeAll + fun beforeAll() { + mainClazz = mainClass.findClassSafe() ?: throwInternalCourseError() + } + + private const val BYE = "Bye" + + @JvmStatic + fun invokeSayByeArguments() = listOf( + Arguments.of(1, List(1) { BYE }.joinToString(System.lineSeparator())), + Arguments.of(2, List(2) { BYE }.joinToString(System.lineSeparator())), + Arguments.of(3, List(3) { BYE }.joinToString(System.lineSeparator())) + ) + } + @Test + fun invokeSayByeFunction() { + mainClass.checkMethod(mainClazz, invokeSayByeFunction) + } + + @ParameterizedTest + @MethodSource("invokeSayByeArguments") + fun invokeSayByeImplementation( + howManyTimes: Int, + output: String, + ) { + val userMethod = mainClass.findMethod(mainClazz, invokeSayByeFunction) + Assertions.assertEquals( + output, + userMethod.invokeWithArgs(howManyTimes, clazz = mainClazz)?.toString()?.trimIndent(), + "For howManyTimes = $howManyTimes the function ${invokeSayByeFunction.name} should return $output" + ) + } + @Test + fun testSolution() { + Assertions.assertEquals( + "How many times should I print $BYE?${newLineSeparator}$BYE${newLineSeparator}$BYE".trimIndent(), + runMainFunction(::main, "2").trimIndent() + ) + } +} \ No newline at end of file diff --git a/courseSection/courseFrameworkLesson/theoryTask/src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt b/courseSection/courseFrameworkLesson/theoryTask/src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt new file mode 100644 index 0000000..415bdcf --- /dev/null +++ b/courseSection/courseFrameworkLesson/theoryTask/src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt @@ -0,0 +1,6 @@ +package org.jetbrains.academy.kotlin.template + +// This file will be hidden in the student mode +// Since this file is invisible, it can be changed between tasks +@Suppress("FunctionOnlyReturningConstant") +fun sayHello() = "Hello" diff --git a/courseSection/courseFrameworkLesson/theoryTask/src/org/jetbrains/academy/kotlin/template/Main.kt b/courseSection/courseFrameworkLesson/theoryTask/src/org/jetbrains/academy/kotlin/template/Main.kt new file mode 100644 index 0000000..a81bd1c --- /dev/null +++ b/courseSection/courseFrameworkLesson/theoryTask/src/org/jetbrains/academy/kotlin/template/Main.kt @@ -0,0 +1,6 @@ +package org.jetbrains.academy.kotlin.template + +// This file will be visible for the student +fun main() { + // Write your solution here +} diff --git a/courseSection/courseFrameworkLesson/theoryTask/task-info.yaml b/courseSection/courseFrameworkLesson/theoryTask/task-info.yaml new file mode 100644 index 0000000..d577bb8 --- /dev/null +++ b/courseSection/courseFrameworkLesson/theoryTask/task-info.yaml @@ -0,0 +1,6 @@ +type: theory +files: + - name: src/org/jetbrains/academy/kotlin/template/Main.kt + visible: true + - name: src/org/jetbrains/academy/kotlin/template/InvisibleFile.kt + visible: false diff --git a/courseSection/courseFrameworkLesson/theoryTask/task.md b/courseSection/courseFrameworkLesson/theoryTask/task.md new file mode 100644 index 0000000..fb21ec7 --- /dev/null +++ b/courseSection/courseFrameworkLesson/theoryTask/task.md @@ -0,0 +1,27 @@ +This is an example of a framework lesson. +If you create this type of lesson, the user can solve tasks step by step. +Initially, the user sees all the visible files from the first task in the lesson. +Next, all changes made by the user in the visible files +will be propagated to the following tasks. + +Beginning from the second lesson, you can modify the visible files to be able to provide +students with the correct solution from the course author. +Invisible files can be modified between tasks. + +**Note, you need to have the same set of files between all tasks.** +Thus, it is the same for the resources folder, and to avoid double-storing of images, +you can put them into the `common` module: + +

+ Logo +

+ +
+ +You can put the picture for the dark theme with the suffix "_dark" together with the initial one. +In such a case, the plugin will use this picture if the user switched the IDE theme to the dark one: + +

+ Logo +

+
diff --git a/courseSection/courseLesson/lesson-info.yaml b/courseSection/courseLesson/lesson-info.yaml new file mode 100644 index 0000000..7a40575 --- /dev/null +++ b/courseSection/courseLesson/lesson-info.yaml @@ -0,0 +1,6 @@ +custom_name: Course lesson +content: + - theoryTask + - quizTask + - programmingTask + - multiFileTask diff --git a/courseSection/courseLesson/multiFileTask/src/Main.kt b/courseSection/courseLesson/multiFileTask/src/Main.kt new file mode 100644 index 0000000..e5bd56b --- /dev/null +++ b/courseSection/courseLesson/multiFileTask/src/Main.kt @@ -0,0 +1,3 @@ +fun main() { + helloWorld() +} diff --git a/courseSection/courseLesson/multiFileTask/src/MainTaskFile.kt b/courseSection/courseLesson/multiFileTask/src/MainTaskFile.kt new file mode 100644 index 0000000..aef3b3b --- /dev/null +++ b/courseSection/courseLesson/multiFileTask/src/MainTaskFile.kt @@ -0,0 +1 @@ +fun helloWorld(): Unit = println("Hello, world!") \ No newline at end of file diff --git a/courseSection/courseLesson/multiFileTask/task-info.yaml b/courseSection/courseLesson/multiFileTask/task-info.yaml new file mode 100644 index 0000000..dc6bc59 --- /dev/null +++ b/courseSection/courseLesson/multiFileTask/task-info.yaml @@ -0,0 +1,18 @@ +type: output +files: + - name: src/MainTaskFile.kt + visible: true + placeholders: + - offset: 25 + length: 24 + placeholder_text: TODO("Not implemented yet") + - name: src/Main.kt + visible: true + placeholders: + - offset: 17 + length: 12 + placeholder_text: // invoke the implemented functions here + - name: test/output.txt + visible: false + - name: test/input.txt + visible: false diff --git a/courseSection/courseLesson/multiFileTask/task.md b/courseSection/courseLesson/multiFileTask/task.md new file mode 100644 index 0000000..f427fbe --- /dev/null +++ b/courseSection/courseLesson/multiFileTask/task.md @@ -0,0 +1,8 @@ +This is an example of an input/output task. +In this type of task, you can give an expected input and output to the program instead of implementing +your own tests. + +This task also demonstrates how you can set up which file should be opened in the student mode if the task has several files. +You just need to put this file as the first file in the `task-info.yaml` config, e.g. in this task the `MainTaskFile.kt` will be opened: + +![Expected behaviour](../../../common/src/main/resources/images/files-order.gif) diff --git a/courseSection/courseLesson/multiFileTask/test/input.txt b/courseSection/courseLesson/multiFileTask/test/input.txt new file mode 100644 index 0000000..e69de29 diff --git a/courseSection/courseLesson/multiFileTask/test/output.txt b/courseSection/courseLesson/multiFileTask/test/output.txt new file mode 100644 index 0000000..af5626b --- /dev/null +++ b/courseSection/courseLesson/multiFileTask/test/output.txt @@ -0,0 +1 @@ +Hello, world! diff --git a/courseSection/courseLesson/programmingTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt b/courseSection/courseLesson/programmingTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt new file mode 100644 index 0000000..2795778 --- /dev/null +++ b/courseSection/courseLesson/programmingTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt @@ -0,0 +1,5 @@ +package org.jetbrains.academy.kotlin.template + +// This file will be hidden in the student mode +@Suppress("FunctionOnlyReturningConstant") +fun sayHello() = "Hello" diff --git a/courseSection/courseLesson/programmingTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt b/courseSection/courseLesson/programmingTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt new file mode 100644 index 0000000..a8ce7a8 --- /dev/null +++ b/courseSection/courseLesson/programmingTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt @@ -0,0 +1,11 @@ +package org.jetbrains.academy.kotlin.template + +fun invokeSayHello(howManyTimes: Int): String { + return List(howManyTimes) { sayHello() }.joinToString(System.lineSeparator()) +} + +fun main() { + println("How many times should I print Hello?") + val howManyTimes = readln().toInt() + println(invokeSayHello(howManyTimes)) +} diff --git a/courseSection/courseLesson/programmingTask/task-info.yaml b/courseSection/courseLesson/programmingTask/task-info.yaml new file mode 100644 index 0000000..120fb8e --- /dev/null +++ b/courseSection/courseLesson/programmingTask/task-info.yaml @@ -0,0 +1,20 @@ +type: edu +custom_name: Course programming task +files: + - name: test/Tests.kt + visible: false + - name: src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt + visible: true + placeholders: + - offset: 95 + length: 81 + placeholder_text: TODO("Write your solution here") + - offset: 193 + length: 133 + placeholder_text: // Write your solution here + - name: src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt + visible: false + - name: test/InvokeSayHelloFunction.kt + visible: false + - name: test/MainClass.kt + visible: false diff --git a/courseSection/courseLesson/programmingTask/task.md b/courseSection/courseLesson/programmingTask/task.md new file mode 100644 index 0000000..b7f7cb2 --- /dev/null +++ b/courseSection/courseLesson/programmingTask/task.md @@ -0,0 +1,6 @@ +This task asks the user to implement something and runs tests from the [Tests.kt](./test/Tests.kt) file. +You need to add _placeholders_ into the places where the user needs to implement something (see [task-info.yaml](./task-info.yaml)). + +This task also demonstrates how you can use the [kotlin test framework](https://github.com/jetbrains-academy/kotlin-test-framework) +to test functions or classes without using the correct implementation in the tests. +It allows you to avoid using the correct implementation in tests directly. diff --git a/courseSection/courseLesson/programmingTask/test/InvokeSayHelloFunction.kt b/courseSection/courseLesson/programmingTask/test/InvokeSayHelloFunction.kt new file mode 100644 index 0000000..df96949 --- /dev/null +++ b/courseSection/courseLesson/programmingTask/test/InvokeSayHelloFunction.kt @@ -0,0 +1,12 @@ +import org.jetbrains.academy.test.system.core.models.TestKotlinType +import org.jetbrains.academy.test.system.core.models.Visibility +import org.jetbrains.academy.test.system.core.models.method.TestMethod +import org.jetbrains.academy.test.system.core.models.variable.TestVariable + +internal val invokeSayHelloFunction = TestMethod( + "invokeSayHello", + TestKotlinType("String"), + arguments = listOf( + TestVariable("howManyTimes", "Int"), + ), +) diff --git a/courseSection/courseLesson/programmingTask/test/MainClass.kt b/courseSection/courseLesson/programmingTask/test/MainClass.kt new file mode 100644 index 0000000..216684e --- /dev/null +++ b/courseSection/courseLesson/programmingTask/test/MainClass.kt @@ -0,0 +1,8 @@ +import org.jetbrains.academy.test.system.core.models.classes.TestClass + +internal val mainClass = TestClass( + classPackage = "org.jetbrains.academy.kotlin.template", + customMethods = listOf( + invokeSayHelloFunction, + ), +) diff --git a/courseSection/courseLesson/programmingTask/test/Tests.kt b/courseSection/courseLesson/programmingTask/test/Tests.kt new file mode 100644 index 0000000..02855ec --- /dev/null +++ b/courseSection/courseLesson/programmingTask/test/Tests.kt @@ -0,0 +1,58 @@ +import org.jetbrains.academy.kotlin.template.main +import org.jetbrains.academy.kotlin.template.newLineSeparator +import org.jetbrains.academy.kotlin.template.runMainFunction +import org.jetbrains.academy.kotlin.template.throwInternalCourseError +import org.jetbrains.academy.test.system.core.invokeWithArgs +import org.jetbrains.academy.test.system.core.models.classes.findClassSafe +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeAll +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource + +class Test { + companion object { + private lateinit var mainClazz: Class<*> + + @JvmStatic + @BeforeAll + fun beforeAll() { + mainClazz = mainClass.findClassSafe() ?: throwInternalCourseError() + } + + private const val HELLO = "Hello" + + @JvmStatic + fun invokeSayHelloArguments() = listOf( + Arguments.of(1, List(1) { HELLO }.joinToString(System.lineSeparator())), + Arguments.of(2, List(2) { HELLO }.joinToString(System.lineSeparator())), + Arguments.of(3, List(3) { HELLO }.joinToString(System.lineSeparator())) + ) + } + @Test + fun invokeSayHelloFunction() { + mainClass.checkMethod(mainClazz, invokeSayHelloFunction) + } + + @ParameterizedTest + @MethodSource("invokeSayHelloArguments") + fun invokeSayHelloImplementation( + howManyTimes: Int, + output: String, + ) { + val userMethod = mainClass.findMethod(mainClazz, invokeSayHelloFunction) + Assertions.assertEquals( + output, + userMethod.invokeWithArgs(howManyTimes, clazz = mainClazz)?.toString()?.trimIndent(), + "For howManyTimes = $howManyTimes the function ${invokeSayHelloFunction.name} should return $output" + ) + } + @Test + fun testSolution() { + Assertions.assertEquals( + "How many times should I print $HELLO?${newLineSeparator}$HELLO${newLineSeparator}$HELLO".trimIndent(), + runMainFunction(::main, "2").trimIndent() + ) + } +} \ No newline at end of file diff --git a/courseSection/courseLesson/quizTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt b/courseSection/courseLesson/quizTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt new file mode 100644 index 0000000..ee3a607 --- /dev/null +++ b/courseSection/courseLesson/quizTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt @@ -0,0 +1,5 @@ +package org.jetbrains.academy.kotlin.template + +fun main() { + // Write your solution here +} diff --git a/courseSection/courseLesson/quizTask/task-info.yaml b/courseSection/courseLesson/quizTask/task-info.yaml new file mode 100644 index 0000000..fb0a18e --- /dev/null +++ b/courseSection/courseLesson/quizTask/task-info.yaml @@ -0,0 +1,12 @@ +type: choice +is_multiple_choice: false +options: + - text: Correct + is_correct: true + - text: Incorrect + is_correct: false +message_incorrect: This text will be shown if the user made a mistake. +files: + - name: src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt + visible: true +custom_name: Course quiz task diff --git a/courseSection/courseLesson/quizTask/task.md b/courseSection/courseLesson/quizTask/task.md new file mode 100644 index 0000000..a3a4d32 --- /dev/null +++ b/courseSection/courseLesson/quizTask/task.md @@ -0,0 +1,2 @@ +You cannot change the question text **Select one option from the list**; you just need to put your question into this file. +You can set up all quiz options in the [task-info](./task-info.yaml) file. diff --git a/courseSection/courseLesson/theoryTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt b/courseSection/courseLesson/theoryTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt new file mode 100644 index 0000000..2795778 --- /dev/null +++ b/courseSection/courseLesson/theoryTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt @@ -0,0 +1,5 @@ +package org.jetbrains.academy.kotlin.template + +// This file will be hidden in the student mode +@Suppress("FunctionOnlyReturningConstant") +fun sayHello() = "Hello" diff --git a/courseSection/courseLesson/theoryTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt b/courseSection/courseLesson/theoryTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt new file mode 100644 index 0000000..ee3a607 --- /dev/null +++ b/courseSection/courseLesson/theoryTask/src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt @@ -0,0 +1,5 @@ +package org.jetbrains.academy.kotlin.template + +fun main() { + // Write your solution here +} diff --git a/courseSection/courseLesson/theoryTask/task-info.yaml b/courseSection/courseLesson/theoryTask/task-info.yaml new file mode 100644 index 0000000..0fe3de8 --- /dev/null +++ b/courseSection/courseLesson/theoryTask/task-info.yaml @@ -0,0 +1,7 @@ +type: theory +custom_name: Course theory task +files: + - name: src/main/kotlin/org/jetbrains/academy/kotlin/template/Main.kt + visible: true + - name: src/main/kotlin/org/jetbrains/academy/kotlin/template/InvisibleFile.kt + visible: false diff --git a/courseSection/courseLesson/theoryTask/task.md b/courseSection/courseLesson/theoryTask/task.md new file mode 100644 index 0000000..13e51fe --- /dev/null +++ b/courseSection/courseLesson/theoryTask/task.md @@ -0,0 +1,14 @@ +This is an example of a theory lesson. + +
+ +You can use hints to explain something to the students. +Please leave an empty line to be able to apply markdown formatting, like **this**. + +Invisible files are hidden in the student mode. +You can use them to define, for example, the helper functions in the course. + +To mark a file as invisible, you need to set up the `false` value for the corresponding file in the [task-info](./task-info.yaml) file. +
+ +Theory tasks do not contain any tests. diff --git a/courseSection/section-info.yaml b/courseSection/section-info.yaml new file mode 100644 index 0000000..daa299c --- /dev/null +++ b/courseSection/section-info.yaml @@ -0,0 +1,4 @@ +custom_name: Course section +content: + - courseLesson + - courseFrameworkLesson diff --git a/detekt.yml b/detekt.yml new file mode 100644 index 0000000..e9034e6 --- /dev/null +++ b/detekt.yml @@ -0,0 +1,34 @@ +# buildUponDefaultConfig is active; add only the rules that require explicit configuration here +potential-bugs: + Deprecation: + active: true + +style: + MaxLineLength: + active: true + maxLineLength: 180 + # wildcards are used all over the project + WildcardImport: + active: false + ReturnCount: + max: 3 + MagicNumber: + active: false + UnusedPrivateMember: + active: false + +complexity: + TooManyFunctions: + active: true + thresholdInFiles: 20 + +exceptions: + active: true + InstanceOfCheckForException: + active: false + +naming: + MatchingDeclarationName: + active: false + PackageNaming: + active: false \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9728aac --- /dev/null +++ b/gradle.properties @@ -0,0 +1,7 @@ +courseGroup=org.jetbrains.academy.kotlin.template +courseVersion=1.1.0 + +gradleVersion=8.3 +jvmVersion=17 + +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..249e583 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..db9a6b8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..a69d9cb --- /dev/null +++ b/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f127cfd --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..77c7bb2 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,37 @@ +rootProject.name = "JetBrains Academy Kotlin course template" + +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + + // To be able to use the Kotlin test framework for the tests - https://github.com/jetbrains-academy/kotlin-test-framework + maven(url = "https://packages.jetbrains.team/maven/p/kotlin-test-framework/kotlin-test-framework") + } +} + +// Mark all task folders as a Gradle module +rootProject.projectDir.walkTopDown().forEach { + if (!isTaskDir(it) || it.path.contains(".idea") || it.path.contains("build") || it.path.contains("node_modules")) { + return@forEach + } + val taskRelativePath = rootDir.toPath().relativize(it.toPath()) + val parts = mutableListOf() + for (name in taskRelativePath) { + parts.add(sanitizeName(name.toString())) + } + val moduleName = parts.joinToString("-") + include(moduleName) + project(":$moduleName").projectDir = it +} + +fun sanitizeName(name: String) = + name.replace("listOf( /\\\\:<>\"?*|())", "_").replace("(^listOf(.)+)|(listOf(.)+\$)", "") + +fun isTaskDir(dir: File) = File(dir, "src").exists() + +// Include other common resources +include( + "common", +) \ No newline at end of file