From bbb494b8a5907bdb210b8839adf007272afdc349 Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Fri, 20 Nov 2020 12:47:24 +0300 Subject: [PATCH 01/19] diktat-examples refactoring and CI (#548) ### What's done: * Updated maven examples, added gradle-groovy examples * Updated functional test * Removed root build.gradle * Update README.md --- .github/workflows/build_and_test.yml | 3 + .github/workflows/diktat_snapshot.yml | 3 + .github/workflows/functional_tests.yml | 31 +- .github/workflows/metrics_for_master.yml | 3 + .github/workflows/release.yml | 8 +- README.md | 4 +- build.gradle.kts | 53 ---- .../test/funcTest/FunctionalTestFile.kt | 5 - examples/gradle-groovy-dsl/build.gradle | 11 + .../diktat-analysis.yml | 12 +- .../src/main/kotlin/Test.kt | 4 +- examples/gradle-kotlin-dsl/build.gradle.kts | 11 + .../gradle-kotlin-dsl/diktat-analysis.yml | 300 ++++++++++++++++++ .../gradle-kotlin-dsl/src/main/kotlin/Test.kt | 13 + examples/gradle/build.gradle.kts | 44 --- examples/maven/diktat-analysis.yml | 12 +- examples/maven/pom.xml | 53 +--- examples/maven/src/main/kotlin/Test.kt | 10 +- pom.xml | 13 - 19 files changed, 420 insertions(+), 173 deletions(-) delete mode 100644 build.gradle.kts delete mode 100644 diktat-rules/src/test/resources/test/funcTest/FunctionalTestFile.kt create mode 100644 examples/gradle-groovy-dsl/build.gradle rename examples/{gradle => gradle-groovy-dsl}/diktat-analysis.yml (93%) rename examples/{gradle => gradle-groovy-dsl}/src/main/kotlin/Test.kt (86%) create mode 100644 examples/gradle-kotlin-dsl/build.gradle.kts create mode 100644 examples/gradle-kotlin-dsl/diktat-analysis.yml create mode 100644 examples/gradle-kotlin-dsl/src/main/kotlin/Test.kt delete mode 100644 examples/gradle/build.gradle.kts diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 822297b063..9ac717ed6f 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -3,6 +3,9 @@ name: Build and test on: pull_request +env: + GRADLE_OPTS: -Dorg.gradle.daemon=false + jobs: test: name: Unit Test diff --git a/.github/workflows/diktat_snapshot.yml b/.github/workflows/diktat_snapshot.yml index 37fa345dfb..7e9789c40a 100644 --- a/.github/workflows/diktat_snapshot.yml +++ b/.github/workflows/diktat_snapshot.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ master ] +env: + GRADLE_OPTS: -Dorg.gradle.daemon=false + jobs: diktat_snapshot_check: runs-on: ubuntu-latest diff --git a/.github/workflows/functional_tests.yml b/.github/workflows/functional_tests.yml index 84027cee7d..01728aafb0 100644 --- a/.github/workflows/functional_tests.yml +++ b/.github/workflows/functional_tests.yml @@ -7,7 +7,7 @@ on: branches: [ master ] env: - DIKTAT_VERSION: 0.1.2 + DIKTAT_VERSION: 0.1.5 KTLINT_VERSION: 0.39.0 GRADLE_OPTS: -Dorg.gradle.daemon=false # to speed up gradle run @@ -21,7 +21,7 @@ jobs: - name: Setup environment run: | cat pom.xml | grep ".*" | head -1 |awk -F'[><]' '{ENVIRON[DIKTAT_VERSION]=$3 ; print ENVIRON[DIKTAT_VERSION]}' - cat pom.xml | grep ".*" | head -1 |awk -F'[><]' '{ENVIRON[KTLINE_VERSION]=$3 ; print ENVIRON[KTLINE_VERSION]}' + cat pom.xml | grep ".*" | head -1 |awk -F'[><]' '{ENVIRON[KTLINT_VERSION]=$3 ; print ENVIRON[KTLINT_VERSION]}' curl -sSLO https://github.com/pinterest/ktlint/releases/download/$KTLINT_VERSION/ktlint && chmod a+x ktlint curl -sSLO https://github.com/cqfn/diKTat/releases/download/v$DIKTAT_VERSION/diktat-$DIKTAT_VERSION.jar @@ -36,7 +36,7 @@ jobs: - name: Run diKTat from cli continue-on-error: true run: | - ./ktlint -R diktat-$DIKTAT_VERSION.jar "diktat-rules/src/test/resources/test/funcTest/FunctionalTestFile.kt" &> out + ./ktlint -R diktat-$DIKTAT_VERSION.jar "examples/maven/src/main/kotlin/Test.kt" &> out - name: Check output id: cli-check @@ -44,20 +44,37 @@ jobs: cat out grep -E "\[VARIABLE_NAME_INCORRECT_FORMAT\]" out - - name: Run diKTat from gradle + - name: Run diKTat from gradle kotlin DSL continue-on-error: true - run: gradle diktatFunctionalTest &> out + working-directory: ./examples/gradle-kotlin-dsl + run: | + gradle diktatCheck &> out + + - name: Check gradle from kotlin DSL + working-directory: ./examples/gradle-kotlin-dsl + run: | + cat out + grep -E "\[VARIABLE_NAME_INCORRECT_FORMAT\]" out + + - name: Run diKTat from gradle groovy DSL + continue-on-error: true + working-directory: ./examples/gradle-groovy-dsl + run: | + gradle diktatCheck &> out - - name: Check gradle + - name: Check gradle from groovy DSL + working-directory: ./examples/gradle-groovy-dsl run: | cat out grep -E "\[VARIABLE_NAME_INCORRECT_FORMAT\]" out - name: Run diKTat from maven + working-directory: ./examples/maven continue-on-error: true - run: mvn -B diktat:check@functional-test &> out + run: mvn -B diktat:check &> out - name: Check maven + working-directory: ./examples/maven run: | cat out grep -E "\[VARIABLE_NAME_INCORRECT_FORMAT\]" out diff --git a/.github/workflows/metrics_for_master.yml b/.github/workflows/metrics_for_master.yml index b5509b6ee5..21b2ca5ee6 100644 --- a/.github/workflows/metrics_for_master.yml +++ b/.github/workflows/metrics_for_master.yml @@ -5,6 +5,9 @@ on: branches: - 'master' +env: + GRADLE_OPTS: -Dorg.gradle.daemon=false + jobs: master_flow: name: Master branch update diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4e76dc0a35..4fffc01f9a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,13 +67,19 @@ jobs: asset_path: ./diktat-ruleset/target/diktat-${{ env.RELEASE_VERSION }}.jar asset_name: diktat-${{ env.RELEASE_VERSION }}.jar asset_content_type: application/zip - - name: Update versions in pom.xml and documentation + - name: Update versions in pom.xml and documentation, update examples id: update-versions run: | mvn -B versions:set -DnextSnapshot=true -DprocessAllModules=true versions:commit mvn versions:set-property -Dproperty=diktat-check.version -DnewVersion=${{ env.RELEASE_VERSION }} sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" README.md sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" build.gradle.kts + sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" .github/workflows/functional_tests.yml + for file in examples/maven/pom.xml examples/gradle-groovy-dsl/build.gradle examples/gradle-kotlin-dsl/build.gradle.kts + do + sed -i "s/$PREVIOUS_VERSION/$RELEASE_VERSION/g" $file + cp diktat-rules/src/main/resources/diktat-analysis.yml $(dirname $file) + done - name: Create pull request uses: peter-evans/create-pull-request@v3 with: diff --git a/README.md b/README.md index 49eeae55d2..3c1c4779cb 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ To run diktat in **only-check** mode use command `$ mvn diktat:check@diktat`. To run diktat in **autocorrect** mode use command `$ mvn diktat:fix@diktat`. ## Run with Gradle using diktat-gradle-plugin -This plugin is available since version 0.1.5. You can see how the plugin is configured in our project for self-checks: [build.gradle.kts](build.gradle.kts). +This plugin is available since version 0.1.5. You can see how the plugin is configured in our examples: [build.gradle.kts](examples/gradle-kotlin-dsl/build.gradle.kts). Add this plugin to your `build.gradle.kts`: ```kotlin plugins { @@ -153,7 +153,7 @@ For example: # all rules are enabled by the default. To disable add 'enabled: false' to the config. enabled: true configuration: - isCopyrightMandatory: true, + isCopyrightMandatory: true copyrightText: Copyright (c) Jeff Lebowski, 2012-2020. All rights reserved. ``` Note, that you can specify and put `diktat-analysis.yml` that contains configuration of diktat in the parent directory of your project on the same level where `build.gradle/pom.xml` is stored. \ diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index ac2e6e6c86..0000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,53 +0,0 @@ -val ktlint by configurations.creating - -repositories { - mavenCentral() - jcenter() -} -dependencies { - ktlint("com.pinterest:ktlint:0.39.0") { - // need to exclude standard ruleset to use only diktat rules - exclude("com.pinterest.ktlint", "ktlint-ruleset-standard") - } - - // diktat ruleset - ktlint("org.cqfn.diktat:diktat-rules:0.1.5") -} - -val outputDir = "${project.buildDir}/reports/diktat/" -val inputFiles = project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt")) - -val diktatCheck by tasks.creating(JavaExec::class) { - inputs.files(inputFiles) - outputs.dir(outputDir) - - description = "Check Kotlin code style." - classpath = ktlint - main = "com.pinterest.ktlint.Main" - - // specify proper path to sources that should be checked here - args = listOf("**/src/main/kotlin/**/*.kt") -} - -val diktatFormat by tasks.creating(JavaExec::class) { - inputs.files(inputFiles) - outputs.dir(outputDir) - - description = "Fix Kotlin code style deviations." - classpath = ktlint - main = "com.pinterest.ktlint.Main" - - // specify proper path to sources that should be checked here - args = listOf("-F", "**/src/main/kotlin/**/*.kt") -} - -val diktatFunctionalTest by tasks.creating(JavaExec::class) { - inputs.files(inputFiles) - outputs.dir(outputDir) - - description = "Diktat functional test" - classpath = ktlint - main = "com.pinterest.ktlint.Main" - - args = listOf("diktat-rules/src/test/resources/test/funcTest/FunctionalTestFile.kt") -} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/funcTest/FunctionalTestFile.kt b/diktat-rules/src/test/resources/test/funcTest/FunctionalTestFile.kt deleted file mode 100644 index 85168cfe7d..0000000000 --- a/diktat-rules/src/test/resources/test/funcTest/FunctionalTestFile.kt +++ /dev/null @@ -1,5 +0,0 @@ -package test.funcTest - -class FunctionalTestFile { - val AAAA = 5 -} diff --git a/examples/gradle-groovy-dsl/build.gradle b/examples/gradle-groovy-dsl/build.gradle new file mode 100644 index 0000000000..35da1ddf8c --- /dev/null +++ b/examples/gradle-groovy-dsl/build.gradle @@ -0,0 +1,11 @@ +plugins { + id "org.cqfn.diktat.diktat-gradle-plugin" version "0.1.5" +} + +repositories { + mavenCentral() +} + +diktat { + inputs = files("src/**/*.kt") +} diff --git a/examples/gradle/diktat-analysis.yml b/examples/gradle-groovy-dsl/diktat-analysis.yml similarity index 93% rename from examples/gradle/diktat-analysis.yml rename to examples/gradle-groovy-dsl/diktat-analysis.yml index 99cb7048bf..17d166c0b1 100644 --- a/examples/gradle/diktat-analysis.yml +++ b/examples/gradle-groovy-dsl/diktat-analysis.yml @@ -282,9 +282,19 @@ # Use extra functions for it instead. - name: CUSTOM_GETTERS_SETTERS enabled: true +# Checks if null-check was used explicitly (for example: if (a == null)) +# Try to avoid explicit null checks (explicit comparison with `null`) +# Kotlin is declared as [Null-safe](https://kotlinlang.org/docs/reference/null-safety.html) language. +# But Kotlin architects wanted Kotlin to be fully compatible with Java, that's why `null` keyword was also introduced in Kotlin. +# There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c +- name: AVOID_NULL_CHECKS + enabled: true # Checks if class instantiation can be wrapped in `apply` for better readability - name: COMPACT_OBJECT_INITIALIZATION enabled: true # Checks explicit supertype qualification - name: USELESS_SUPERTYPE - enabled: true \ No newline at end of file + enabled: true +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE + enabled: true diff --git a/examples/gradle/src/main/kotlin/Test.kt b/examples/gradle-groovy-dsl/src/main/kotlin/Test.kt similarity index 86% rename from examples/gradle/src/main/kotlin/Test.kt rename to examples/gradle-groovy-dsl/src/main/kotlin/Test.kt index 885e2f69c9..ed2b851143 100644 --- a/examples/gradle/src/main/kotlin/Test.kt +++ b/examples/gradle-groovy-dsl/src/main/kotlin/Test.kt @@ -7,5 +7,7 @@ class incorrectname: Exception { // fun myCommentedFunction() { // } - + +val Incorrect_Val = 5 + } diff --git a/examples/gradle-kotlin-dsl/build.gradle.kts b/examples/gradle-kotlin-dsl/build.gradle.kts new file mode 100644 index 0000000000..bd6af41e63 --- /dev/null +++ b/examples/gradle-kotlin-dsl/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + id("org.cqfn.diktat.diktat-gradle-plugin") version "0.1.5" +} + +repositories { + mavenCentral() +} + +diktat { + inputs = files("src/**/*.kt") +} diff --git a/examples/gradle-kotlin-dsl/diktat-analysis.yml b/examples/gradle-kotlin-dsl/diktat-analysis.yml new file mode 100644 index 0000000000..17d166c0b1 --- /dev/null +++ b/examples/gradle-kotlin-dsl/diktat-analysis.yml @@ -0,0 +1,300 @@ +- name: DIKTAT_COMMON + configuration: + # put your package name here - it will be autofixed and checked + domainName: your.name.here + testDirs: test +- name: CLASS_NAME_INCORRECT + enabled: true +- name: CONSTANT_UPPERCASE + enabled: true +- name: ENUM_VALUE + enabled: true +- name: EXCEPTION_SUFFIX + enabled: true +- name: FILE_NAME_INCORRECT + enabled: true +- name: FILE_NAME_MATCH_CLASS + enabled: true +- name: FUNCTION_BOOLEAN_PREFIX + enabled: true +- name: FUNCTION_NAME_INCORRECT_CASE + enabled: true +- name: GENERIC_NAME + enabled: true +- name: IDENTIFIER_LENGTH + enabled: true +- name: OBJECT_NAME_INCORRECT + enabled: true +- name: PACKAGE_NAME_INCORRECT_CASE + enabled: true # configuration domainName is taken from DIKTAT_COMMON +- name: PACKAGE_NAME_INCORRECT_PREFIX + enabled: false +- name: PACKAGE_NAME_INCORRECT_SYMBOLS + enabled: true +- name: PACKAGE_NAME_INCORRECT_PATH + enabled: false # configuration domainName is taken from DIKTAT_COMMON +- name: PACKAGE_NAME_MISSING + enabled: true +- name: VARIABLE_HAS_PREFIX + enabled: true +- name: VARIABLE_NAME_INCORRECT + enabled: true +- name: VARIABLE_NAME_INCORRECT_FORMAT + enabled: true +- name: MISSING_KDOC_ON_FUNCTION + enabled: true +- name: MISSING_KDOC_TOP_LEVEL + enabled: true +- name: MISSING_KDOC_CLASS_ELEMENTS + enabled: true +- name: KDOC_WITHOUT_PARAM_TAG + enabled: true +- name: KDOC_WITHOUT_RETURN_TAG + enabled: true +- name: KDOC_WITHOUT_THROWS_TAG + enabled: true +- name: KDOC_EMPTY_KDOC + enabled: true +- name: INCORRECT_PACKAGE_SEPARATOR + enabled: true +- name: KDOC_NO_DEPRECATED_TAG + enabled: true +- name: KDOC_NO_EMPTY_TAGS + enabled: true +- name: KDOC_WRONG_SPACES_AFTER_TAG + enabled: true +- name: KDOC_WRONG_TAGS_ORDER + enabled: true +- name: KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS + enabled: true +- name: KDOC_NEWLINES_BEFORE_BASIC_TAGS + enabled: true +- name: KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS + enabled: true +- name: KDOC_TRIVIAL_KDOC_ON_FUNCTION + enabled: 'true' +- name: HEADER_WRONG_FORMAT + enabled: true +- name: HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE + enabled: true +- name: HEADER_MISSING_OR_WRONG_COPYRIGHT + enabled: true + configuration: + isCopyrightMandatory: true + copyrightText: 'Copyright (c) Your Company Name Here. 2010-2020' +- name: HEADER_NOT_BEFORE_PACKAGE + enabled: true +- name: HEADER_CONTAINS_DATE_OR_AUTHOR + enabled: true +- name: FILE_IS_TOO_LONG + enabled: true + configuration: + maxSize: '2000' + ignoreFolders: '' +- name: COMMENTED_OUT_CODE + enabled: true +- name: FILE_CONTAINS_ONLY_COMMENTS + enabled: true + # order imports alphabetically +- name: FILE_UNORDERED_IMPORTS + enabled: true + configuration: + # use logical imports grouping with sorting inside of a group + useRecommendedImportsOrder: true +- name: FILE_INCORRECT_BLOCKS_ORDER + enabled: true +- name: FILE_NO_BLANK_LINE_BETWEEN_BLOCKS + enabled: true +# Check: warns if wildcard imports are used except allows. (e.g. import org.cqfn.diktat.*) +- name: FILE_WILDCARD_IMPORTS + enabled: true + configuration: + allowedWildcards: "" # Allowed wildcards for imports (e.g. "import org.cqfn.diktat.*, import org.jetbrains.kotlin.*") + useRecommendedImportsOrder: true +- name: NO_BRACES_IN_CONDITIONALS_AND_LOOPS + enabled: true +- name: WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES + enabled: true +- name: BLANK_LINE_BETWEEN_PROPERTIES + enabled: true +- name: BRACES_BLOCK_STRUCTURE_ERROR + enabled: true + configuration: + openBraceNewline: 'True' + closeBraceNewline: 'True' +- name: WRONG_INDENTATION + enabled: true + configuration: + newlineAtEnd: true + extendedIndentOfParameters: true + alignedParameters: true + extendedIndentAfterOperators: true + indentationSize: 4 +- name: EMPTY_BLOCK_STRUCTURE_ERROR + enabled: true + configuration: + styleEmptyBlockWithNewline: 'True' + allowEmptyBlocks: 'False' +- name: MORE_THAN_ONE_STATEMENT_PER_LINE + enabled: true +- name: LONG_LINE + enabled: true + configuration: + lineLength: '120' +- name: REDUNDANT_SEMICOLON + enabled: true +- name: WRONG_NEWLINES + enabled: true + configuration: + maxParametersInOneLine: 2 +- name: TOO_MANY_CONSECUTIVE_SPACES + enabled: true + configuration: + max_spaces: '1' + saveInitialFormattingForEnums: false +- name: TOO_MANY_BLANK_LINES + enabled: true +- name: WRONG_WHITESPACE + enabled: true +- name: BACKTICKS_PROHIBITED + enabled: true +- name: STRING_CONCATENATION + enabled: true +- name: WHEN_WITHOUT_ELSE + enabled: true +- name: ANNOTATION_NEW_LINE + enabled: true +- name: ENUMS_SEPARATED + enabled: true +- name: LONG_NUMERICAL_VALUES_SEPARATED + enabled: true + configuration: + maxNumberLength: '5' + maxBlockLength: '3' +- name: WRONG_DECLARATIONS_ORDER + enabled: true + configuration: + sortEnum: true + sortProperty: true +- name: WRONG_MULTIPLE_MODIFIERS_ORDER + enabled: true +- name: CONFUSING_IDENTIFIER_NAMING + enabled: true +# Inspection that checks if there is a blank line before kDoc and none after +- name: WRONG_NEWLINES_AROUND_KDOC + enabled: true +# Inspection that checks if there is no blank lines before first comment +- name: FIRST_COMMENT_NO_SPACES + enabled: true +# Inspection that checks if there are blank lines between code and comment and between code start token and comment's text +- name: COMMENT_WHITE_SPACE + enabled: true + configuration: + maxSpacesBeforeComment: 2 + maxSpacesInComment: 1 +# Inspection that checks if all comment's are inside if-else code blocks. Exception is general if comment +- name: IF_ELSE_COMMENTS + enabled: true +- name: WRONG_COPYRIGHT_YEAR + enabled: true +# Inspection that checks if local variables are declared close to the first usage site +- name: LOCAL_VARIABLE_EARLY_DECLARATION + enabled: true +# Try to avoid initialize val by null (e.g. val a: Int? = null -> val a: Int = 0) +- name: NULLABLE_PROPERTY_TYPE + enabled: true +# Type aliases provide alternative names for existing types when type's reference text is longer 25 chars +- name: TYPE_ALIAS + enabled: true + configuration: + typeReferenceLength: '25' # max length of type reference +- name: SMART_CAST_NEEDED + enabled: true +- name: GENERIC_VARIABLE_WRONG_DECLARATION + enabled: true +# Inspection that checks if string template has redundant curly braces +- name: STRING_TEMPLATE_CURLY_BRACES + enabled: true +# Variables with `val` modifier - are immutable (read-only). Usage of such variables instead of `var` variables increases +# robustness and readability of code, because `var` variables can be reassigned several times in the business logic. +# This rule prohibits usage of `var`s as local variables - the only exception is accumulators and counters +- name: SAY_NO_TO_VAR + enabled: true +# Inspection that checks if string template has redundant quotes +- name: STRING_TEMPLATE_QUOTES + enabled: true +# Checks that floating-point values are not used in arithmetic expressions +- name: FLOAT_IN_ACCURATE_CALCULATIONS + enabled: true +# Checks that function length isn't too long +- name: TOO_LONG_FUNCTION + enabled: true + configuration: + maxFunctionLength: '30' # max length of function + isIncludeHeader: 'false' # count function's header +# Warns if there are nested functions +- name: AVOID_NESTED_FUNCTIONS + enabled: true +# Checks that lambda inside function parameters is in the end +- name: LAMBDA_IS_NOT_LAST_PARAMETER + enabled: true +# Checks that function doesn't contains too many parameters +- name: TOO_MANY_PARAMETERS + enabled: true + configuration: + maxParameterListSize: '5' # max parameters size +# Checks that function doesn't have too many nested blocks +- name: NESTED_BLOCK + enabled: true + configuration: + maxNestedBlockQuantity: '4' +# Checks that function use default values, instead overloading +- name: WRONG_OVERLOADING_FUNCTION_ARGUMENTS + enabled: true +# Checks that KDoc in constructor has property tag +- name: KDOC_NO_CONSTRUCTOR_PROPERTY + enabled: true +# Checks that KDoc in constructor has property tag but with comment inside constructor +- name: KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT + enabled: true +# if a class has single constructor, it should be converted to a primary constructor +- name: SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY + enabled: true +# Checks if class can be made as data class +- name: USE_DATA_CLASS + enabled: true +# Checks that never use the name of a variable in the custom getter or setter +- name: WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR + enabled: true +# Checks that classes have only one init block +- name: MULTIPLE_INIT_BLOCKS + enabled: true +# Checks that there are abstract functions in abstract class +- name: CLASS_SHOULD_NOT_BE_ABSTRACT + enabled: true +# Checks if there are any trivial getters or setters +- name: TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED + enabled: true +# Checks that no custom getters and setters are used for properties. It is a more wide rule than TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED +# Kotlin compiler automatically generates `get` and `set` methods for properties and also lets the possibility to override it. +# But in all cases it is very confusing when `get` and `set` are overridden for a developer who uses this particular class. +# Developer expects to get the value of the property, but receives some unknown value and some extra side effect hidden by the custom getter/setter. +# Use extra functions for it instead. +- name: CUSTOM_GETTERS_SETTERS + enabled: true +# Checks if null-check was used explicitly (for example: if (a == null)) +# Try to avoid explicit null checks (explicit comparison with `null`) +# Kotlin is declared as [Null-safe](https://kotlinlang.org/docs/reference/null-safety.html) language. +# But Kotlin architects wanted Kotlin to be fully compatible with Java, that's why `null` keyword was also introduced in Kotlin. +# There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c +- name: AVOID_NULL_CHECKS + enabled: true +# Checks if class instantiation can be wrapped in `apply` for better readability +- name: COMPACT_OBJECT_INITIALIZATION + enabled: true +# Checks explicit supertype qualification +- name: USELESS_SUPERTYPE + enabled: true +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE + enabled: true diff --git a/examples/gradle-kotlin-dsl/src/main/kotlin/Test.kt b/examples/gradle-kotlin-dsl/src/main/kotlin/Test.kt new file mode 100644 index 0000000000..80975f0974 --- /dev/null +++ b/examples/gradle-kotlin-dsl/src/main/kotlin/Test.kt @@ -0,0 +1,13 @@ +package incorrect + +class incorrectname: Exception { + fun INCORRECT_FUNCTION() { + throw Exception() + } + + // fun myCommentedFunction() { + // } + + val Incorrect_Val = 5 + +} diff --git a/examples/gradle/build.gradle.kts b/examples/gradle/build.gradle.kts deleted file mode 100644 index 365f2749a1..0000000000 --- a/examples/gradle/build.gradle.kts +++ /dev/null @@ -1,44 +0,0 @@ -val ktlint by configurations.creating - -repositories { - mavenCentral() - jcenter() -} - -dependencies { - ktlint("com.pinterest:ktlint:0.39.0") { - // need to exclude standard ruleset to use only diktat rules - exclude("com.pinterest.ktlint", "ktlint-ruleset-standard") - } - - // diktat ruleset - ktlint("org.cqfn.diktat:diktat-rules:0.1.3") { - } -} - -val outputDir = "${project.buildDir}/reports/diktat/" -val inputFiles = project.fileTree(mapOf("dir" to "src", "include" to "**/*.kt")) - -val diktatCheck by tasks.creating(JavaExec::class) { - inputs.files(inputFiles) - outputs.dir(outputDir) - - description = "Check Kotlin code style." - classpath = ktlint - main = "com.pinterest.ktlint.Main" - - // specify proper path to sources that should be checked here - args = listOf("src/main/kotlin/**/*.kt") -} - -val diktatFormat by tasks.creating(JavaExec::class) { - inputs.files(inputFiles) - outputs.dir(outputDir) - - description = "Fix Kotlin code style deviations." - classpath = ktlint - main = "com.pinterest.ktlint.Main" - - // specify proper path here - args = listOf("-F","src/main/kotlin/**/*.kt") -} diff --git a/examples/maven/diktat-analysis.yml b/examples/maven/diktat-analysis.yml index 99cb7048bf..17d166c0b1 100644 --- a/examples/maven/diktat-analysis.yml +++ b/examples/maven/diktat-analysis.yml @@ -282,9 +282,19 @@ # Use extra functions for it instead. - name: CUSTOM_GETTERS_SETTERS enabled: true +# Checks if null-check was used explicitly (for example: if (a == null)) +# Try to avoid explicit null checks (explicit comparison with `null`) +# Kotlin is declared as [Null-safe](https://kotlinlang.org/docs/reference/null-safety.html) language. +# But Kotlin architects wanted Kotlin to be fully compatible with Java, that's why `null` keyword was also introduced in Kotlin. +# There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c +- name: AVOID_NULL_CHECKS + enabled: true # Checks if class instantiation can be wrapped in `apply` for better readability - name: COMPACT_OBJECT_INITIALIZATION enabled: true # Checks explicit supertype qualification - name: USELESS_SUPERTYPE - enabled: true \ No newline at end of file + enabled: true +# Checks if extension function with the same signature don't have related classes +- name: EXTENSION_FUNCTION_SAME_SIGNATURE + enabled: true diff --git a/examples/maven/pom.xml b/examples/maven/pom.xml index b96bdd69fa..96a3500c0b 100644 --- a/examples/maven/pom.xml +++ b/examples/maven/pom.xml @@ -3,59 +3,30 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.cqfn.diktat - diktat-maven + diktat-examples-maven pom 1.0.0-SNAPSHOT - - - - ossrh - https://oss.sonatype.org/content/repositories/snapshots - - - - org.apache.maven.plugins - maven-antrun-plugin - 3.0.0 + org.cqfn.diktat + diktat-maven-plugin + 0.1.5 + + diktat-analysis.yml + + ${project.basedir}/src/main/kotlin + + - diktat - none - - - - - - - + diktat-check - run + check - - - com.pinterest - ktlint - 0.39.0 - - - com.pinterest.ktlint - ktlint-ruleset-standard - - - - - org.cqfn.diktat - diktat-rules - 0.1.3 - - diff --git a/examples/maven/src/main/kotlin/Test.kt b/examples/maven/src/main/kotlin/Test.kt index 885e2f69c9..80975f0974 100644 --- a/examples/maven/src/main/kotlin/Test.kt +++ b/examples/maven/src/main/kotlin/Test.kt @@ -1,11 +1,13 @@ package incorrect class incorrectname: Exception { - fun INCORRECT_FUNCTION() { - throw Exception() + fun INCORRECT_FUNCTION() { + throw Exception() } - + // fun myCommentedFunction() { // } - + + val Incorrect_Val = 5 + } diff --git a/pom.xml b/pom.xml index ffac531fdd..06edf5e88c 100644 --- a/pom.xml +++ b/pom.xml @@ -330,19 +330,6 @@ diktat-analysis.yml - - functional-test - none - - check - - - - ${project.basedir}/src/test/resources/test/funcTest - - diktat-analysis.yml - - From 205dbfc5023366fde24cbaa157bc60f6990b0661 Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Fri, 20 Nov 2020 13:12:26 +0300 Subject: [PATCH 02/19] Removing complex cases from the warning related to null checks (#532) Removing complex cases from the warning related to null checks ### What's done: Null check will not trigger on the following code: 1) if (myVar == null || otherValue == 5 && isValid) {} 2) if (myVar != null) { println("not null") } else if (true) { println("Other condition") } --- .../diktat/ruleset/rules/NullChecksRule.kt | 37 ++++++++++--- .../chapter4/NullChecksRuleWarnTest.kt | 53 ++++++++++++++++++- info/guide/guide-chapter-8.md | 16 ++++++ 3 files changed, 99 insertions(+), 7 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt index dc31c9d782..d4ea4ea891 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/NullChecksRule.kt @@ -6,6 +6,7 @@ import com.pinterest.ktlint.core.ast.ElementType import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.CONDITION import com.pinterest.ktlint.core.ast.ElementType.IF +import com.pinterest.ktlint.core.ast.ElementType.IF_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.NULL import com.pinterest.ktlint.core.ast.parent import org.cqfn.diktat.common.config.rules.RulesConfig @@ -13,6 +14,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.AVOID_NULL_CHECKS import org.cqfn.diktat.ruleset.utils.* import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtIfExpression /** * This rule check and fixes explicit null checks (explicit comparison with `null`) @@ -30,8 +32,11 @@ class NullChecksRule(private val configRules: List) : Rule("null-ch if (node.elementType == CONDITION) { node.parent(IF)?.let { - // this can be autofixed as the condition stays in if-statement - conditionInIfStatement(node) + // excluding complex cases with else-if statements, because they look better with explicit null-check + if (!isComplexIfStatement(it)) { + // this can be autofixed as the condition stays in simple if-statement + conditionInIfStatement(node) + } } } @@ -44,6 +49,16 @@ class NullChecksRule(private val configRules: List) : Rule("null-ch } } + /** + * checks that if-statement is a complex condition + * You can name a statement - "complex if-statement" if it has other if in the else branch (else-if structure) + */ + private fun isComplexIfStatement(parentIf: ASTNode): Boolean { + val parentIfPsi = parentIf.psi + require(parentIfPsi is KtIfExpression) + return (parentIfPsi.`else`?.node?.firstChildNode?.elementType == IF_KEYWORD) + } + private fun conditionInIfStatement(node: ASTNode) { node.findAllNodesWithSpecificType(BINARY_EXPRESSION).forEach { binaryExprNode -> val condition = (binaryExprNode.psi as KtBinaryExpression) @@ -69,11 +84,21 @@ class NullChecksRule(private val configRules: List) : Rule("null-ch @Suppress("UnsafeCallOnNullableType") private fun isNullCheckBinaryExpession(condition: KtBinaryExpression): Boolean = - // check that binary expession has `null` as right or left operand - setOf(condition.right, condition.left).map { it!!.node.elementType }.contains(NULL) && - // checks that it is the comparison condition - setOf(ElementType.EQEQ, ElementType.EQEQEQ, ElementType.EXCLEQ, ElementType.EXCLEQEQEQ).contains(condition.operationToken) + // check that binary expession has `null` as right or left operand + setOf(condition.right, condition.left).map { it!!.node.elementType }.contains(NULL) && + // checks that it is the comparison condition + setOf(ElementType.EQEQ, ElementType.EQEQEQ, ElementType.EXCLEQ, ElementType.EXCLEQEQEQ).contains(condition.operationToken) && + // no need to raise warning or fix null checks in complex expressions + !condition.isComplexCondition() + /** + * checks if condition is a complex expression. For example: + * (a == 5) - is not a complex condition, but (a == 5 && b != 6) is a complex condition + */ + private fun KtBinaryExpression.isComplexCondition(): Boolean { + // KtBinaryExpression is complex if it has a parent that is also a binary expression + return this.parent is KtBinaryExpression + } private fun warnAndFixOnNullCheck(condition: KtBinaryExpression, canBeAutoFixed: Boolean, autofix: () -> Unit) { AVOID_NULL_CHECKS.warnAndFix( diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NullChecksRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NullChecksRuleWarnTest.kt index 0cd9ebc139..c34596ea5b 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NullChecksRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter4/NullChecksRuleWarnTest.kt @@ -37,7 +37,7 @@ class NullChecksRuleWarnTest : LintTestBase(::NullChecksRule) { """ | fun foo() { | var myVar: Int? = null - | if ((myVar == null) && (true)) { + | if ((myVar == null) && (true) || isValid) { | println("null") | return | } @@ -116,4 +116,55 @@ class NullChecksRuleWarnTest : LintTestBase(::NullChecksRule) { LintError(5, 19, ruleId, "${Warnings.AVOID_NULL_CHECKS.warnText()} myVar == null", false), ) } + + @Test + @Tag(WarningNames.AVOID_NULL_CHECKS) + fun `equals to null, but in complex else-if statement`() { + lintMethod( + """ + | fun foo0() { + | if (myVar != null) { + | println("not null") + | } else if (true) { + | println() + | } + | } + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.AVOID_NULL_CHECKS) + fun `equals to null, but in complex else-if statement with dummy comment`() { + lintMethod( + """ + | fun foo0() { + | if (myVar != null) { + | println("not null") + | } else /* test comment */ if (true) { + | println() + | } + | } + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.AVOID_NULL_CHECKS) + fun `equals to null, but the expression is not a else-if`() { + lintMethod( + """ + | fun foo0() { + | if (myVar != null) { + | println("not null") + | } else { + | if (true) { + | println() + | } + | } + | } + """.trimMargin(), + LintError(2, 10, ruleId, "${Warnings.AVOID_NULL_CHECKS.warnText()} myVar != null", true), + ) + } } diff --git a/info/guide/guide-chapter-8.md b/info/guide/guide-chapter-8.md index b133365b85..7c19482263 100644 --- a/info/guide/guide-chapter-8.md +++ b/info/guide/guide-chapter-8.md @@ -65,3 +65,19 @@ myVar?.let { println("null") } ?: run { println("not null") } ``` + +**Exceptions:** in case of complex expressions like multiple `else-if` structures, or a long conditional statement there is a common sense to use explicit comparison with `null`. + +**Valid examples:** + +```kotlin +if (myVar != null) { + println("not null") +} else if (anotherCondition) { + println("Other condition") +} +``` + +```kotlin +if (myVar == null || otherValue == 5 && isValid) {} +``` From 052bd9de61a0f4e371805dde0c5e8af0f0064eaf Mon Sep 17 00:00:00 2001 From: Andrey Kuleshov Date: Mon, 23 Nov 2020 10:51:22 +0300 Subject: [PATCH 03/19] Adding warnings to rules mapping in enum (#558) Adding warnings to rules mapping in enum ### What's done: 1) cosmetic changes in codestyle 2) warnings enum extended 3) small generator and test --- .../org/cqfn/diktat/common/cli/CliArgument.kt | 2 +- .../org/cqfn/diktat/test/ConfigReaderTest.kt | 3 +- .../cqfn/diktat/ruleset/constants/Warnings.kt | 210 +++++++++--------- .../constants/WarningsTableGenerator.kt | 32 +++ .../ruleset/utils/WarningsGenerationTest.kt | 13 ++ info/guide/guide-chapter-1.md | 3 +- info/guide/guide-chapter-2.md | 6 +- info/guide/guide-chapter-3.md | 4 +- info/guide/guide-chapter-4.md | 2 +- info/rules-mapping.md | 104 +++++++++ 10 files changed, 266 insertions(+), 113 deletions(-) create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/WarningsTableGenerator.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/WarningsGenerationTest.kt create mode 100644 info/rules-mapping.md diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt index c391df76c0..8e7176dd4a 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/cli/CliArgument.kt @@ -2,7 +2,7 @@ package org.cqfn.diktat.common.cli import org.apache.commons.cli.Option -import kotlinx.serialization.* +import kotlinx.serialization.Serializable /** * This class is used to serialize/deserialize json representation diff --git a/diktat-common/src/test/kotlin/org/cqfn/diktat/test/ConfigReaderTest.kt b/diktat-common/src/test/kotlin/org/cqfn/diktat/test/ConfigReaderTest.kt index 5a07324021..f989351a28 100644 --- a/diktat-common/src/test/kotlin/org/cqfn/diktat/test/ConfigReaderTest.kt +++ b/diktat-common/src/test/kotlin/org/cqfn/diktat/test/ConfigReaderTest.kt @@ -7,7 +7,8 @@ import org.junit.jupiter.api.Test class ConfigReaderTest { @Test fun `testing json reading`() { - val rulesConfigList: List? = RulesConfigReader(javaClass.classLoader).readResource("src/test/resources/test-rules-config.yml") + val rulesConfigList: List? = RulesConfigReader(javaClass.classLoader) + .readResource("src/test/resources/test-rules-config.yml") require(rulesConfigList != null) assert(rulesConfigList.any { it.name == "CLASS_NAME_INCORRECT" && it.enabled }) assert(rulesConfigList.find { it.name == "CLASS_NAME_INCORRECT" }?.configuration == mapOf()) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt index 7bc8ea670c..29eb0cf743 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt @@ -12,126 +12,124 @@ typealias EmitType = ((offset: Int, errorMessage: String, canBeAutoCorrected: Bo * This class represent individual inspections of diktat code style. * A [Warnings] entry contains rule name, warning message and is used in code check. */ -@Suppress("ForbiddenComment", "MagicNumber", "WRONG_DECLARATIONS_ORDER") -enum class Warnings(private val canBeAutoCorrected: Boolean, private val warn: String) : Rule { +@Suppress("ForbiddenComment", "MagicNumber", "WRONG_DECLARATIONS_ORDER", "MaxLineLength") +enum class Warnings(val canBeAutoCorrected: Boolean, val ruleId: String, private val warn: String) : Rule { // ======== chapter 1 ======== - PACKAGE_NAME_MISSING(true, "no package name declared in a file"), - PACKAGE_NAME_INCORRECT_CASE(true, "package name should be completely in a lower case"), - PACKAGE_NAME_INCORRECT_PREFIX(true, "package name should start from company's domain"), + PACKAGE_NAME_MISSING(true, "1.2.1", "no package name declared in a file"), + PACKAGE_NAME_INCORRECT_CASE(true, "1.2.1", "package name should be completely in a lower case"), + PACKAGE_NAME_INCORRECT_PREFIX(true, "1.2.1", "package name should start from company's domain"), // FixMe: should add autofix - PACKAGE_NAME_INCORRECT_SYMBOLS(false, "package name should contain only latin (ASCII) letters or numbers. For separation of words use dot"), - PACKAGE_NAME_INCORRECT_PATH(true, "package name does not match the directory hierarchy for this file, the real package name should be"), - INCORRECT_PACKAGE_SEPARATOR(true, "package name parts should be separated only by dots - there should be no other symbols like underscores (_)"), - CLASS_NAME_INCORRECT(true, "class/enum/interface name should be in PascalCase and should contain only latin (ASCII) letters or numbers"), - OBJECT_NAME_INCORRECT(true, "object structure name should be in PascalCase and should contain only latin (ASCII) letters or numbers"), - VARIABLE_NAME_INCORRECT_FORMAT(true, "variable name should be in camel case (correct: checkIpConfig, incorrect: CheckIPConfig)" + - " should contain only latin (ASCII) letters or numbers and should start from lower letter"), - VARIABLE_NAME_INCORRECT(false, "variable name should contain more than one letter"), - CONSTANT_UPPERCASE(true, " properties from companion object or on file level mostly in all cases are constants - please use upper snake case for them"), - VARIABLE_HAS_PREFIX(true, "variable has prefix (like mVariable or M_VARIABLE), generally it is a bad code style (Android - is the only exception)"), - IDENTIFIER_LENGTH(false, "identifier's length is incorrect, it should be in range of [2, 64] symbols"), - ENUM_VALUE(true, "enum values should be in selected UPPER_CASE snake/PascalCase format"), - GENERIC_NAME(true, "generic name should contain only one single capital letter, it can be followed by a number"), - FUNCTION_NAME_INCORRECT_CASE(true, "function/method name should be in lowerCamelCase"), - FUNCTION_BOOLEAN_PREFIX(true, "functions that return the value of Boolean type should have or prefix"), - FILE_NAME_INCORRECT(true, "file name is incorrect - it should end with .kt extension and be in PascalCase"), - FILE_NAME_MATCH_CLASS(true, "file name is incorrect - it should match with the class described in it if there is the only one class declared"), - EXCEPTION_SUFFIX(true, "all exception classes should have \"Exception\" suffix"), - CONFUSING_IDENTIFIER_NAMING(false, "it's a bad name for identifier"), + PACKAGE_NAME_INCORRECT_SYMBOLS(false, "1.2.1", "package name should contain only latin (ASCII) letters or numbers. For separation of words use dot"), + PACKAGE_NAME_INCORRECT_PATH(true, "1.2.1", "package name does not match the directory hierarchy for this file, the real package name should be"), + INCORRECT_PACKAGE_SEPARATOR(true, "1.2.1", "package name parts should be separated only by dots - there should be no other symbols like underscores (_)"), + CLASS_NAME_INCORRECT(true, "1.3.1", "class/enum/interface name should be in PascalCase and should contain only latin (ASCII) letters or numbers"), + OBJECT_NAME_INCORRECT(true, "1.3.1", "object structure name should be in PascalCase and should contain only latin (ASCII) letters or numbers"), + VARIABLE_NAME_INCORRECT_FORMAT(true, "1.6.1", "variable name should be in lowerCamelCase and should contain only latin (ASCII) letters or numbers and should start from lower letter"), + VARIABLE_NAME_INCORRECT(false, "1.1.1", "variable name should contain more than one letter"), + CONSTANT_UPPERCASE(true, "1.5.1", " properties from companion object or on file level mostly in all cases are constants - please use upper snake case for them"), + VARIABLE_HAS_PREFIX(true, "1.1.1", "variable has prefix (like mVariable or M_VARIABLE), generally it is a bad code style (Android - is the only exception)"), + IDENTIFIER_LENGTH(false, "1.1.1", "identifier's length is incorrect, it should be in range of [2, 64] symbols"), + ENUM_VALUE(true, "1.3.1", "enum values should be in selected UPPER_CASE snake/PascalCase format"), + GENERIC_NAME(true, "1.1.1", "generic name should contain only one single capital letter, it can be followed by a number"), + BACKTICKS_PROHIBITED(false, "1.1.1", "backticks should not be used in identifier's naming. The only exception test methods marked with @Test annotation"), + FUNCTION_NAME_INCORRECT_CASE(true, "1.4.1", "function/method name should be in lowerCamelCase"), + FUNCTION_BOOLEAN_PREFIX(true, "1.6.2", "functions that return the value of Boolean type should have or prefix"), + FILE_NAME_INCORRECT(true, "1.1.1", "file name is incorrect - it should end with .kt extension and be in PascalCase"), + EXCEPTION_SUFFIX(true, "1.1.1", "all exception classes should have \"Exception\" suffix"), + CONFUSING_IDENTIFIER_NAMING(false, "1.1.1", "it's a bad name for identifier"), // ======== chapter 2 ======== - MISSING_KDOC_TOP_LEVEL(false, "all public and internal top-level classes and functions should have Kdoc"), - MISSING_KDOC_CLASS_ELEMENTS(false, "all public, internal and protected classes, functions and variables inside the class should have Kdoc"), - MISSING_KDOC_ON_FUNCTION(true, "all public, internal and protected functions should have Kdoc with proper tags"), - KDOC_TRIVIAL_KDOC_ON_FUNCTION(false, "KDocs should not be trivial (e.g. method getX should not de documented as 'returns X')"), - KDOC_WITHOUT_PARAM_TAG(true, "all methods which take arguments should have @param tags in KDoc"), - KDOC_WITHOUT_RETURN_TAG(true, "all methods which return values should have @return tag in KDoc"), - KDOC_WITHOUT_THROWS_TAG(true, "all methods which throw exceptions should have @throws tag in KDoc"), - KDOC_EMPTY_KDOC(false, "KDoc should never be empty"), - KDOC_WRONG_SPACES_AFTER_TAG(true, "there should be exactly one white space after tag name in KDoc"), - KDOC_WRONG_TAGS_ORDER(true, "in KDoc standard tags are arranged in order @param, @return, @throws, but are"), - KDOC_NEWLINES_BEFORE_BASIC_TAGS(true, "in KDoc block of standard tags @param, @return, @throws should contain newline before only if there is other content before it"), - KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS(true, "in KDoc standard tags @param, @return, @throws should not containt newline between them, but these tags do"), - KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS(true, "in KDoc there should be exactly one empty line after special tags"), - KDOC_NO_EMPTY_TAGS(false, "no empty descriptions in tag blocks are allowed"), - KDOC_NO_DEPRECATED_TAG(true, "KDoc doesn't support @deprecated tag, use @Deprecated annotation instead"), - KDOC_NO_CONSTRUCTOR_PROPERTY(true, "all properties from the primary constructor should be documented in a @property tag in KDoc"), - KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT(true, "replace comment before property with @property tag in class KDoc"), - HEADER_WRONG_FORMAT(true, "file header comments should be properly formatted"), - HEADER_MISSING_OR_WRONG_COPYRIGHT(true, "file header comment must include copyright information inside a block comment"), - WRONG_COPYRIGHT_YEAR(true, "year defined in copyright and current year are different"), - HEADER_CONTAINS_DATE_OR_AUTHOR(false, "file header comment should not contain creation date and author name"), - HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE(false, "files that contain multiple or no classes should contain description of what is inside of this file"), - HEADER_NOT_BEFORE_PACKAGE(true, "header KDoc should be placed before package and imports"), - COMMENTED_OUT_CODE(false, "you should not comment out code, use VCS to save it in history and delete this block"), - WRONG_NEWLINES_AROUND_KDOC(true, "there should be a blank line above the kDoc and there should not be no blank lines after kDoc"), - FIRST_COMMENT_NO_SPACES(true, "there should not be any spaces before first comment"), - COMMENT_WHITE_SPACE(true, "there should be a white space between code and comment also between code start token and comment text"), - IF_ELSE_COMMENTS(true, "invalid comments structure. Comment should be inside the block"), + MISSING_KDOC_TOP_LEVEL(false, "2.1.1", "all public and internal top-level classes and functions should have Kdoc"), + MISSING_KDOC_CLASS_ELEMENTS(false, "2.1.1", "all public, internal and protected classes, functions and variables inside the class should have Kdoc"), + MISSING_KDOC_ON_FUNCTION(true, "2.1.1", "all public, internal and protected functions should have Kdoc with proper tags"), + KDOC_TRIVIAL_KDOC_ON_FUNCTION(false, "2.3.1", "KDocs should not be trivial (e.g. method getX should not de documented as 'returns X')"), + KDOC_WITHOUT_PARAM_TAG(true, "2.1.2", "all methods which take arguments should have @param tags in KDoc"), + KDOC_WITHOUT_RETURN_TAG(true, "2.1.2", "all methods which return values should have @return tag in KDoc"), + KDOC_WITHOUT_THROWS_TAG(true, "2.1.2", "all methods which throw exceptions should have @throws tag in KDoc"), + KDOC_EMPTY_KDOC(false, "2.1.3", "KDoc should never be empty"), + KDOC_WRONG_SPACES_AFTER_TAG(true, "2.1.3", "there should be exactly one white space after tag name in KDoc"), + KDOC_WRONG_TAGS_ORDER(true, "2.1.3", "in KDoc standard tags are arranged in order @param, @return, @throws, but are"), + KDOC_NEWLINES_BEFORE_BASIC_TAGS(true, "2.1.3", "in KDoc block of standard tags @param, @return, @throws should contain newline before only if there is other content before it"), + KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS(true, "2.1.3", "in KDoc standard tags @param, @return, @throws should not containt newline between them, but these tags do"), + KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS(true, "2.1.3", "in KDoc there should be exactly one empty line after special tags"), + KDOC_NO_EMPTY_TAGS(false, "2.2.1", "no empty descriptions in tag blocks are allowed"), + KDOC_NO_DEPRECATED_TAG(true, "2.1.3", "KDoc doesn't support @deprecated tag, use @Deprecated annotation instead"), + KDOC_NO_CONSTRUCTOR_PROPERTY(true, "2.1.1", "all properties from the primary constructor should be documented in a @property tag in KDoc"), + KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT(true, "2.1.1", "replace comment before property with @property tag in class KDoc"), + HEADER_WRONG_FORMAT(true, "2.2.1", "file header comments should be properly formatted"), + HEADER_MISSING_OR_WRONG_COPYRIGHT(true, "2.2.1", "file header comment must include copyright information inside a block comment"), + WRONG_COPYRIGHT_YEAR(true, "2.2.1", "year defined in copyright and current year are different"), + HEADER_CONTAINS_DATE_OR_AUTHOR(false, "2.2.1", "file header comment should not contain creation date and author name"), + HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE(false, "2.2.1", "files that contain multiple or no classes should contain description of what is inside of this file"), + HEADER_NOT_BEFORE_PACKAGE(true, "2.2.1", "header KDoc should be placed before package and imports"), + COMMENTED_OUT_CODE(false, "2.4.2", "you should not comment out code, use VCS to save it in history and delete this block"), + WRONG_NEWLINES_AROUND_KDOC(true, "2.4.1", "there should be a blank line above the kDoc and there should not be no blank lines after kDoc"), + FIRST_COMMENT_NO_SPACES(true, "2.4.1", "there should not be any spaces before first comment"), + COMMENT_WHITE_SPACE(true, "2.4.1", "there should be a white space between code and comment also between code start token and comment text"), + IF_ELSE_COMMENTS(true, "2.4.1", "invalid comments structure. Comment should be inside the block"), // ======== chapter 3 ======== - FILE_IS_TOO_LONG(false, "file has more number of lines than expected"), - FILE_CONTAINS_ONLY_COMMENTS(false, "source code files which contain only comments should be avoided"), - FILE_INCORRECT_BLOCKS_ORDER(true, "general structure of kotlin source file is wrong, parts are in incorrect order"), - FILE_NO_BLANK_LINE_BETWEEN_BLOCKS(true, "general structure of kotlin source file is wrong, general code blocks sohuld be separated by empty lines"), - FILE_UNORDERED_IMPORTS(true, "imports should be ordered alphabetically and shouldn't be separated by newlines"), - FILE_WILDCARD_IMPORTS(false, "wildcard imports should not be used"), - NO_BRACES_IN_CONDITIONALS_AND_LOOPS(true, "in if, else, when, for, do, and while statements braces should be used. Exception: single line if statement."), - WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES(true, "the declaration part of a class-like code structures (class/interface/etc.) should be in the proper order"), - BLANK_LINE_BETWEEN_PROPERTIES(true, "there should be no blank lines between properties without comments; comment or KDoc on property should have blank line before"), - BRACES_BLOCK_STRUCTURE_ERROR(true, "braces should follow 1TBS style"), - WRONG_INDENTATION(true, "only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed)"), - EMPTY_BLOCK_STRUCTURE_ERROR(true, "incorrect format of empty block"), - MORE_THAN_ONE_STATEMENT_PER_LINE(true, "There should not be more than one code statement in one line"), - LONG_LINE(true, "This line is longer than allowed"), - BACKTICKS_PROHIBITED(false, "Backticks should not be used in identifier's naming. The only exception test methods marked with @Test annotation"), - REDUNDANT_SEMICOLON(true, "there should be no redundant semicolon at the end of lines"), - WRONG_NEWLINES(true, "incorrect line breaking"), + FILE_IS_TOO_LONG(false, "3.1.1", "file has more number of lines than expected"), + FILE_CONTAINS_ONLY_COMMENTS(false, "3.1.2", "empty files or files that contain only comments should be avoided"), + FILE_INCORRECT_BLOCKS_ORDER(true, "3.1.2", "general structure of kotlin source file is wrong, parts are in incorrect order"), + FILE_NO_BLANK_LINE_BETWEEN_BLOCKS(true, "3.1.2", "general structure of kotlin source file is wrong, general code blocks sohuld be separated by empty lines"), + FILE_UNORDERED_IMPORTS(true, "3.1.2", "imports should be ordered alphabetically and shouldn't be separated by newlines"), + FILE_WILDCARD_IMPORTS(false, "3.1.2", "wildcard imports should not be used"), + NO_BRACES_IN_CONDITIONALS_AND_LOOPS(true, "3.2.1", "in if, else, when, for, do, and while statements braces should be used. Exception: single line if statement."), + WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES(true, "3.1.4", "the declaration part of a class-like code structures (class/interface/etc.) should be in the proper order"), + BLANK_LINE_BETWEEN_PROPERTIES(true, "3.1.4", "there should be no blank lines between properties without comments; comment or KDoc on property should have blank line before"), + BRACES_BLOCK_STRUCTURE_ERROR(true, "3.2.2", "braces should follow 1TBS style"), + WRONG_INDENTATION(true, "3.3.1", "only spaces are allowed for indentation and each indentation should equal to 4 spaces (tabs are not allowed)"), + EMPTY_BLOCK_STRUCTURE_ERROR(true, "3.4.1", "incorrect format of empty block"), + MORE_THAN_ONE_STATEMENT_PER_LINE(true, "3.6.1", "there should not be more than one code statement in one line"), + LONG_LINE(true, "3.5.1", "this line is longer than allowed"), + REDUNDANT_SEMICOLON(true, "3.6.2", "there should be no redundant semicolon at the end of lines"), + WRONG_NEWLINES(true, "3.6.2", "incorrect line breaking"), // FixMe: autofixing will be added for this rule - STRING_CONCATENATION(false, "strings should not be concatenated using plus operator - use string templates instead if the statement fits one line"), - TOO_MANY_BLANK_LINES(true, "too many consecutive blank lines"), - WRONG_WHITESPACE(true, "incorrect usage of whitespaces for code separation"), - TOO_MANY_CONSECUTIVE_SPACES(true, "too many consecutive spaces"), - ANNOTATION_NEW_LINE(true, "annotations must be on new line"), - ENUMS_SEPARATED(true, "split enumeration error"), - WHEN_WITHOUT_ELSE(true, "each when statement must have else at the end"), - LONG_NUMERICAL_VALUES_SEPARATED(true, "long numerical values should be separated with underscore"), - WRONG_DECLARATIONS_ORDER(true, "declarations of constants and enum members should be sorted alphabetically"), - WRONG_MULTIPLE_MODIFIERS_ORDER(true, "sequence of modifiers is incorrect"), - LOCAL_VARIABLE_EARLY_DECLARATION(false, "local variables should be declared close to the line where they are first used"), + STRING_CONCATENATION(false, "3.15.1", "strings should not be concatenated using plus operator - use string templates instead if the statement fits one line"), + TOO_MANY_BLANK_LINES(true, "3.7.1", "too many consecutive blank lines"), + WRONG_WHITESPACE(true, "3.8.1", "incorrect usage of whitespaces for code separation"), + TOO_MANY_CONSECUTIVE_SPACES(true, "3.8.1", "too many consecutive spaces"), + ANNOTATION_NEW_LINE(true, "3.12.1", "annotations must be on new line"), + ENUMS_SEPARATED(true, "3.9.1", "enum is incorrectly formatted"), + WHEN_WITHOUT_ELSE(true, "3.11.1", "each 'when' statement must have else at the end"), + LONG_NUMERICAL_VALUES_SEPARATED(true, "3.14.2", "long numerical values should be separated with underscore"), + WRONG_DECLARATIONS_ORDER(true, "3.1.4", "declarations of constants and enum members should be sorted alphabetically"), + WRONG_MULTIPLE_MODIFIERS_ORDER(true, "3.14.1", "sequence of modifier-keywords is incorrect"), + LOCAL_VARIABLE_EARLY_DECLARATION(false, "3.10.2", "local variables should be declared close to the line where they are first used"), + STRING_TEMPLATE_CURLY_BRACES(true, "3.15.2", "string template has redundant curly braces"), + STRING_TEMPLATE_QUOTES(true, "3.15.2", "string template has redundant quotes"), + FILE_NAME_MATCH_CLASS(true, "3.1.2", "file name is incorrect - it should match with the class described in it if there is the only one class declared"), // ======== chapter 4 ======== - NULLABLE_PROPERTY_TYPE(true, "try to avoid use of nullable types"), - TYPE_ALIAS(false, "variable's type is too complex and should be replaced with typealias"), - SMART_CAST_NEEDED(true, "You can omit explicit casting"), - SAY_NO_TO_VAR(false, "Usage of a mutable variables with [var] modifier - is a bad style, use [val] instead"), - GENERIC_VARIABLE_WRONG_DECLARATION(true, "variable should have explicit type declaration"), - STRING_TEMPLATE_CURLY_BRACES(true, "string template has redundant curly braces"), - STRING_TEMPLATE_QUOTES(true, "string template has redundant quotes"), + NULLABLE_PROPERTY_TYPE(true, "4.3.1", "try to avoid use of nullable types"), + TYPE_ALIAS(false, "4.2.2", "variable's type is too complex and should be replaced with typealias"), + SMART_CAST_NEEDED(true, "4.2.1", "you can omit explicit casting"), + SAY_NO_TO_VAR(false, "4.1.3", "Usage of a mutable variables with [var] modifier - is a bad style, use [val] instead"), + GENERIC_VARIABLE_WRONG_DECLARATION(true, "4.3.2", "variable should have explicit type declaration"), // FixMe: change float literal to BigDecimal? Or kotlin equivalent? - FLOAT_IN_ACCURATE_CALCULATIONS(false, "floating-point values shouldn't be used in accurate calculations"), - AVOID_NULL_CHECKS(false, "Try to avoid explicit null-checks. Use '.let/.also/?:/e.t.c' instead of"), - + FLOAT_IN_ACCURATE_CALCULATIONS(false, "4.1.1", "floating-point values shouldn't be used in accurate calculations"), + AVOID_NULL_CHECKS(false, "4.3.3", "Try to avoid explicit null-checks. Use '.let/.also/?:/e.t.c' instead of"), // ======== chapter 5 ======== - TOO_LONG_FUNCTION(false, "function is too long: split it or make more primitive"), - AVOID_NESTED_FUNCTIONS(true, "try to avoid using nested functions"), - LAMBDA_IS_NOT_LAST_PARAMETER(false, "lambda inside function parameters should be in the end"), - TOO_MANY_PARAMETERS(false, "function has too many parameters"), - NESTED_BLOCK(false, "function has too many nested blocks and should be simplified"), - WRONG_OVERLOADING_FUNCTION_ARGUMENTS(false, "use default argument instead of function overloading"), + TOO_LONG_FUNCTION(false, "5.1.1", "function is too long: split it or make more primitive"), + AVOID_NESTED_FUNCTIONS(true, "5.1.3", "try to avoid using nested functions"), + LAMBDA_IS_NOT_LAST_PARAMETER(false, "5.2.1", "lambda inside function parameters should be in the end"), + TOO_MANY_PARAMETERS(false, "5.2.2", "function has too many parameters"), + NESTED_BLOCK(false, "5.1.2", "function has too many nested blocks and should be simplified"), + WRONG_OVERLOADING_FUNCTION_ARGUMENTS(false, "5.2.3", "use default argument instead of function overloading"), // ======== chapter 6 ======== - SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY(true, "if a class has single constructor, it should be converted to a primary constructor"), - USE_DATA_CLASS(false, "this class can be converted to a data class"), - WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR(false, "Use `field` keyword instead of property name inside property accessors"), - MULTIPLE_INIT_BLOCKS(true, "Avoid using multiple `init` blocks, this logic can be moved to constructors or properties declarations"), - CLASS_SHOULD_NOT_BE_ABSTRACT(true, "class should not be abstract, because it has no abstract functions"), - CUSTOM_GETTERS_SETTERS(false, "Custom getters and setters are not recommended, use class methods instead"), - COMPACT_OBJECT_INITIALIZATION(true, "class instance can be initialized in `apply` block"), - USELESS_SUPERTYPE(true,"unnecessary supertype specification"), - TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "trivial property accessors are not recommended"), - EXTENSION_FUNCTION_SAME_SIGNATURE(false, "extension functions should not have same signature if their receiver classes are related"), + SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY(true, "6.1.1", "if a class has single constructor, it should be converted to a primary constructor"), + USE_DATA_CLASS(false, "6.1.2", "this class can be converted to a data class"), + WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR(false, "6.1.9", "use `field` keyword instead of property name inside property accessors"), + MULTIPLE_INIT_BLOCKS(true, "6.1.4", "avoid using multiple `init` blocks, this logic can be moved to constructors or properties declarations"), + CLASS_SHOULD_NOT_BE_ABSTRACT(true, "6.1.6", "class should not be abstract, because it has no abstract functions"), + CUSTOM_GETTERS_SETTERS(false, "6.1.8", "custom getters and setters are not recommended, use class methods instead"), + COMPACT_OBJECT_INITIALIZATION(true, "6.1.11", "class instance can be initialized in `apply` block"), + USELESS_SUPERTYPE(true, "6.1.5", "unnecessary supertype specification"), + TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "6.1.10", "trivial property accessors are not recommended"), + EXTENSION_FUNCTION_SAME_SIGNATURE(false, "6.2.2", "extension functions should not have same signature if their receiver classes are related"), ; /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/WarningsTableGenerator.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/WarningsTableGenerator.kt new file mode 100644 index 0000000000..c20c83f731 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/WarningsTableGenerator.kt @@ -0,0 +1,32 @@ +package cqfn.diktat.ruleset.constants + +import org.cqfn.diktat.ruleset.constants.Warnings +import java.io.File + +const val DIKTAT_GUIDE: String = "guide/diktat-coding-convention.md#" + +@Suppress("MagicNumber") +fun main() { + val allWarnings = Warnings.values() + allWarnings.sortBy { warn -> + val numbers = warn.ruleId.split(".") + val chapter = numbers[0].toInt() + val subChapter = numbers[1].toInt() + val rule = numbers[2].toInt() + + // small hacky trick to compare rules like 1.1.13 properly (sorting using numbers instead of lexicographically) + chapter * 100000 + subChapter * 100 + rule + } + + val maxRuleIdLength = allWarnings.maxBy { it.ruleId.length }?.ruleId?.length ?: 0 + val maxRuleNameLength = allWarnings.maxBy { it.name.length }?.name?.length ?: 0 + val separator = "| ${"-".repeat(maxRuleNameLength)} | ${"-".repeat(maxRuleIdLength)} | --- |\n" + + val header = "| Diktat Rule | Code Style | Auto-fixed? |\n" + + val tableWithWarnings = allWarnings.map { warn -> + "| ${warn.name} | [${warn.ruleId}](${DIKTAT_GUIDE}r${warn.ruleId}) | ${if (warn.canBeAutoCorrected) "yes" else "no"} |" + }.joinToString("\n") + + File("info/rules-mapping.md").writeText("$header$separator$tableWithWarnings") +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/WarningsGenerationTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/WarningsGenerationTest.kt new file mode 100644 index 0000000000..fddc80fb86 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/WarningsGenerationTest.kt @@ -0,0 +1,13 @@ +package org.cqfn.diktat.ruleset.utils + +import org.cqfn.diktat.ruleset.constants.Warnings +import org.junit.jupiter.api.Test + +class WarningsGenerationTest { + @Test + fun `checking that warnings has all proper fields filled`() { + Warnings.values().forEach { warn -> + assert(warn.ruleId.split(".").size == 3) + } + } +} diff --git a/info/guide/guide-chapter-1.md b/info/guide/guide-chapter-1.md index 74f60c863d..24af29a974 100644 --- a/info/guide/guide-chapter-1.md +++ b/info/guide/guide-chapter-1.md @@ -66,6 +66,7 @@ Note that prefixing can also negatively affect the style, as well as the auto ge ### 1.2 Packages names ### Rule 1.2.1: Package names are in lower case and separated by dots. Code developed within your company should start with `your.company.domain`, and numbers are permitted in package names. +Each file should have a `package` directive. Package names are all written in lowercase, and consecutive words are simply concatenated together (no underscores). Package names should contain both the product and module names, as well as the department or team name to prevent conflicts with other teams. Numbers are not permitted. For example: `org.apache.commons.lang3`, `xxx.yyy.v2`. **Exceptions:** @@ -160,7 +161,7 @@ fun addKeyListener(Listener) ### 1.5 Constants ### Rule 1.5.1 Constant names should be in UPPER case, words separated by underscore -1. Constants are attributes created with the const keyword, or top-level/`val` local variables of an object that holds immutable data. In most cases, constants can be identified as a `const val` property from the `object`/`companion object`/file top level. These variables contain a fixed constant value that typically should never be changed by programmers. This includes basic types, strings, immutable types, and immutable collections of immutable types. If an object state can be changed, the value is not a constant. +1. Constants are attributes created with the const keyword, or top-level/`val` local variables of an object that holds immutable data. In most cases, constants can be identified as a `const val` property from the `object`/`companion object`/file top level. These variables contain a fixed constant value that typically should never be changed by programmers. This includes basic types, strings, immutable types, and immutable collections of immutable types. If an object state can be changed, the value is not a constant. 2. Constant names should contain only uppercase letters separated by underscores. They should have a val or const val modifier to explicitly make them final. In most cases, if you need to specify a constant value, then you need to create it with the "const val" modifier. Note that not all `val` variables are constants. diff --git a/info/guide/guide-chapter-2.md b/info/guide/guide-chapter-2.md index 1b3e5cdba4..07d55b63e9 100644 --- a/info/guide/guide-chapter-2.md +++ b/info/guide/guide-chapter-2.md @@ -33,8 +33,10 @@ When the entire KDoc block can be stored in one line (and there is no KDoc mark ### Rule 2.1.1: KDoc is used for each public, protected or internal code element At a minimum, KDoc should be used for every public, protected, or internal decorated class, interface, enumeration, method, and member field (property). Other code blocks can also have KDocs if needed. +Instead of using comments before properties in class - use `@property` tag in a KDoc of a class. +All properties of the primary constructor should be also documented in a KDoc with a `@property` tag. -Exceptions: +**Exceptions:** 1. For setters/getters of properties obvious comments are optional. (Note that simple `get/set` methods are generated by Kotlin under the hood). For example, getFoo can also be `return foo`. @@ -53,7 +55,7 @@ fun isEmptyList(list: List) = list.size == 0 3. You can skip KDocs for a method's override if the method is almost like the super class method. -### Rule 2.1.2: When the method has arguments, return value, can throw exceptions, etc., it must be described in the KDoc block: with @param, @return, @throws, etc. +### Rule 2.1.2: When the method has arguments, return value, can throw exceptions, etc., it must be described in the KDoc block: with @param, @return, @throws, etc. **Valid examples**: diff --git a/info/guide/guide-chapter-3.md b/info/guide/guide-chapter-3.md index 01f440d06a..a0913bf562 100644 --- a/info/guide/guide-chapter-3.md +++ b/info/guide/guide-chapter-3.md @@ -24,6 +24,8 @@ c) Import statements are alphabetically arranged, without using line breaks and d) **Recommendation**: One `.kt` source file should contain only one class declaration, and its name should match the filename +e) Avoid empty files that do not contain the code or contain only imports/comments/package name + ### Recommendation 3.1.3: Import statements should appear in the following order: Android, internal company imports, external imports, java core dependencies, and Kotlin standard library. Each group should be separated by a blank line. From top to bottom, the order is the following: @@ -636,7 +638,7 @@ val n1: Int; val n2: Int ### Recommendation 3.10.2: Variables should be declared close to the line where they are first used. To minimize their scope, local variables should be declared close to the point where they are first used. This will also increase readability of the code. Local variables are usually initialized during declaration or initialized immediately after. -The member fields of the class should be declared collectively (see [Rule 3.1.2](#s3.1.2) for details on class structure). +The member fields of the class should be declared collectively (see [Rule 3.1.2](#r3.1.2) for details on class structure). ### 3.11 When expression diff --git a/info/guide/guide-chapter-4.md b/info/guide/guide-chapter-4.md index 0a497bbe01..d8d54b1344 100644 --- a/info/guide/guide-chapter-4.md +++ b/info/guide/guide-chapter-4.md @@ -166,7 +166,7 @@ val a: Int = 0 Nevertheless, if you use Java libraries extensively, you will have to use nullable types and enrich your code with `!!` and `?` symbols. Avoid using nullable types for Kotlin stdlib (declared in [official documentation](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/)) avoid using nullable types. -Try to use initializers for empty collections. ), and try using initializers for empty collections. For example: If you want to initialize a list instead of using `null` use `emptyList()`. +Try to use initializers for empty collections), and try using initializers for empty collections. For example: If you want to initialize a list instead of using `null` use `emptyList()`. **Invalid example**: ```kotlin diff --git a/info/rules-mapping.md b/info/rules-mapping.md new file mode 100644 index 0000000000..7ed2128362 --- /dev/null +++ b/info/rules-mapping.md @@ -0,0 +1,104 @@ +| Diktat Rule | Code Style | Auto-fixed? | +| ----------------------------------------- | ------ | --- | +| VARIABLE_NAME_INCORRECT | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | no | +| VARIABLE_HAS_PREFIX | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | yes | +| IDENTIFIER_LENGTH | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | no | +| GENERIC_NAME | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | yes | +| BACKTICKS_PROHIBITED | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | no | +| FILE_NAME_INCORRECT | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | yes | +| EXCEPTION_SUFFIX | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | yes | +| CONFUSING_IDENTIFIER_NAMING | [1.1.1](guide/diktat-coding-convention.md#r1.1.1) | no | +| PACKAGE_NAME_MISSING | [1.2.1](guide/diktat-coding-convention.md#r1.2.1) | yes | +| PACKAGE_NAME_INCORRECT_CASE | [1.2.1](guide/diktat-coding-convention.md#r1.2.1) | yes | +| PACKAGE_NAME_INCORRECT_PREFIX | [1.2.1](guide/diktat-coding-convention.md#r1.2.1) | yes | +| PACKAGE_NAME_INCORRECT_SYMBOLS | [1.2.1](guide/diktat-coding-convention.md#r1.2.1) | no | +| PACKAGE_NAME_INCORRECT_PATH | [1.2.1](guide/diktat-coding-convention.md#r1.2.1) | yes | +| INCORRECT_PACKAGE_SEPARATOR | [1.2.1](guide/diktat-coding-convention.md#r1.2.1) | yes | +| CLASS_NAME_INCORRECT | [1.3.1](guide/diktat-coding-convention.md#r1.3.1) | yes | +| OBJECT_NAME_INCORRECT | [1.3.1](guide/diktat-coding-convention.md#r1.3.1) | yes | +| ENUM_VALUE | [1.3.1](guide/diktat-coding-convention.md#r1.3.1) | yes | +| FUNCTION_NAME_INCORRECT_CASE | [1.4.1](guide/diktat-coding-convention.md#r1.4.1) | yes | +| CONSTANT_UPPERCASE | [1.5.1](guide/diktat-coding-convention.md#r1.5.1) | yes | +| VARIABLE_NAME_INCORRECT_FORMAT | [1.6.1](guide/diktat-coding-convention.md#r1.6.1) | yes | +| FUNCTION_BOOLEAN_PREFIX | [1.6.2](guide/diktat-coding-convention.md#r1.6.2) | yes | +| MISSING_KDOC_TOP_LEVEL | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | no | +| MISSING_KDOC_CLASS_ELEMENTS | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | no | +| MISSING_KDOC_ON_FUNCTION | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | +| KDOC_NO_CONSTRUCTOR_PROPERTY | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | +| KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT | [2.1.1](guide/diktat-coding-convention.md#r2.1.1) | yes | +| KDOC_WITHOUT_PARAM_TAG | [2.1.2](guide/diktat-coding-convention.md#r2.1.2) | yes | +| KDOC_WITHOUT_RETURN_TAG | [2.1.2](guide/diktat-coding-convention.md#r2.1.2) | yes | +| KDOC_WITHOUT_THROWS_TAG | [2.1.2](guide/diktat-coding-convention.md#r2.1.2) | yes | +| KDOC_EMPTY_KDOC | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | no | +| KDOC_WRONG_SPACES_AFTER_TAG | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_WRONG_TAGS_ORDER | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_NEWLINES_BEFORE_BASIC_TAGS | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_NO_DEPRECATED_TAG | [2.1.3](guide/diktat-coding-convention.md#r2.1.3) | yes | +| KDOC_NO_EMPTY_TAGS | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | no | +| HEADER_WRONG_FORMAT | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | +| HEADER_MISSING_OR_WRONG_COPYRIGHT | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | +| WRONG_COPYRIGHT_YEAR | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | +| HEADER_CONTAINS_DATE_OR_AUTHOR | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | no | +| HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | no | +| HEADER_NOT_BEFORE_PACKAGE | [2.2.1](guide/diktat-coding-convention.md#r2.2.1) | yes | +| KDOC_TRIVIAL_KDOC_ON_FUNCTION | [2.3.1](guide/diktat-coding-convention.md#r2.3.1) | no | +| WRONG_NEWLINES_AROUND_KDOC | [2.4.1](guide/diktat-coding-convention.md#r2.4.1) | yes | +| FIRST_COMMENT_NO_SPACES | [2.4.1](guide/diktat-coding-convention.md#r2.4.1) | yes | +| COMMENT_WHITE_SPACE | [2.4.1](guide/diktat-coding-convention.md#r2.4.1) | yes | +| IF_ELSE_COMMENTS | [2.4.1](guide/diktat-coding-convention.md#r2.4.1) | yes | +| COMMENTED_OUT_CODE | [2.4.2](guide/diktat-coding-convention.md#r2.4.2) | no | +| FILE_IS_TOO_LONG | [3.1.1](guide/diktat-coding-convention.md#r3.1.1) | no | +| FILE_CONTAINS_ONLY_COMMENTS | [3.1.2](guide/diktat-coding-convention.md#r3.1.2) | no | +| FILE_INCORRECT_BLOCKS_ORDER | [3.1.2](guide/diktat-coding-convention.md#r3.1.2) | yes | +| FILE_NO_BLANK_LINE_BETWEEN_BLOCKS | [3.1.2](guide/diktat-coding-convention.md#r3.1.2) | yes | +| FILE_UNORDERED_IMPORTS | [3.1.2](guide/diktat-coding-convention.md#r3.1.2) | yes | +| FILE_WILDCARD_IMPORTS | [3.1.2](guide/diktat-coding-convention.md#r3.1.2) | no | +| FILE_NAME_MATCH_CLASS | [3.1.2](guide/diktat-coding-convention.md#r3.1.2) | yes | +| WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES | [3.1.4](guide/diktat-coding-convention.md#r3.1.4) | yes | +| BLANK_LINE_BETWEEN_PROPERTIES | [3.1.4](guide/diktat-coding-convention.md#r3.1.4) | yes | +| WRONG_DECLARATIONS_ORDER | [3.1.4](guide/diktat-coding-convention.md#r3.1.4) | yes | +| NO_BRACES_IN_CONDITIONALS_AND_LOOPS | [3.2.1](guide/diktat-coding-convention.md#r3.2.1) | yes | +| BRACES_BLOCK_STRUCTURE_ERROR | [3.2.2](guide/diktat-coding-convention.md#r3.2.2) | yes | +| WRONG_INDENTATION | [3.3.1](guide/diktat-coding-convention.md#r3.3.1) | yes | +| EMPTY_BLOCK_STRUCTURE_ERROR | [3.4.1](guide/diktat-coding-convention.md#r3.4.1) | yes | +| LONG_LINE | [3.5.1](guide/diktat-coding-convention.md#r3.5.1) | yes | +| MORE_THAN_ONE_STATEMENT_PER_LINE | [3.6.1](guide/diktat-coding-convention.md#r3.6.1) | yes | +| REDUNDANT_SEMICOLON | [3.6.2](guide/diktat-coding-convention.md#r3.6.2) | yes | +| WRONG_NEWLINES | [3.6.2](guide/diktat-coding-convention.md#r3.6.2) | yes | +| TOO_MANY_BLANK_LINES | [3.7.1](guide/diktat-coding-convention.md#r3.7.1) | yes | +| WRONG_WHITESPACE | [3.8.1](guide/diktat-coding-convention.md#r3.8.1) | yes | +| TOO_MANY_CONSECUTIVE_SPACES | [3.8.1](guide/diktat-coding-convention.md#r3.8.1) | yes | +| ENUMS_SEPARATED | [3.9.1](guide/diktat-coding-convention.md#r3.9.1) | yes | +| LOCAL_VARIABLE_EARLY_DECLARATION | [3.10.2](guide/diktat-coding-convention.md#r3.10.2) | no | +| WHEN_WITHOUT_ELSE | [3.11.1](guide/diktat-coding-convention.md#r3.11.1) | yes | +| ANNOTATION_NEW_LINE | [3.12.1](guide/diktat-coding-convention.md#r3.12.1) | yes | +| WRONG_MULTIPLE_MODIFIERS_ORDER | [3.14.1](guide/diktat-coding-convention.md#r3.14.1) | yes | +| LONG_NUMERICAL_VALUES_SEPARATED | [3.14.2](guide/diktat-coding-convention.md#r3.14.2) | yes | +| STRING_CONCATENATION | [3.15.1](guide/diktat-coding-convention.md#r3.15.1) | no | +| STRING_TEMPLATE_CURLY_BRACES | [3.15.2](guide/diktat-coding-convention.md#r3.15.2) | yes | +| STRING_TEMPLATE_QUOTES | [3.15.2](guide/diktat-coding-convention.md#r3.15.2) | yes | +| FLOAT_IN_ACCURATE_CALCULATIONS | [4.1.1](guide/diktat-coding-convention.md#r4.1.1) | no | +| SAY_NO_TO_VAR | [4.1.3](guide/diktat-coding-convention.md#r4.1.3) | no | +| SMART_CAST_NEEDED | [4.2.1](guide/diktat-coding-convention.md#r4.2.1) | yes | +| TYPE_ALIAS | [4.2.2](guide/diktat-coding-convention.md#r4.2.2) | no | +| NULLABLE_PROPERTY_TYPE | [4.3.1](guide/diktat-coding-convention.md#r4.3.1) | yes | +| GENERIC_VARIABLE_WRONG_DECLARATION | [4.3.2](guide/diktat-coding-convention.md#r4.3.2) | yes | +| AVOID_NULL_CHECKS | [4.3.3](guide/diktat-coding-convention.md#r4.3.3) | no | +| TOO_LONG_FUNCTION | [5.1.1](guide/diktat-coding-convention.md#r5.1.1) | no | +| NESTED_BLOCK | [5.1.2](guide/diktat-coding-convention.md#r5.1.2) | no | +| AVOID_NESTED_FUNCTIONS | [5.1.3](guide/diktat-coding-convention.md#r5.1.3) | yes | +| LAMBDA_IS_NOT_LAST_PARAMETER | [5.2.1](guide/diktat-coding-convention.md#r5.2.1) | no | +| TOO_MANY_PARAMETERS | [5.2.2](guide/diktat-coding-convention.md#r5.2.2) | no | +| WRONG_OVERLOADING_FUNCTION_ARGUMENTS | [5.2.3](guide/diktat-coding-convention.md#r5.2.3) | no | +| SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY | [6.1.1](guide/diktat-coding-convention.md#r6.1.1) | yes | +| USE_DATA_CLASS | [6.1.2](guide/diktat-coding-convention.md#r6.1.2) | no | +| MULTIPLE_INIT_BLOCKS | [6.1.4](guide/diktat-coding-convention.md#r6.1.4) | yes | +| USELESS_SUPERTYPE | [6.1.5](guide/diktat-coding-convention.md#r6.1.5) | yes | +| CLASS_SHOULD_NOT_BE_ABSTRACT | [6.1.6](guide/diktat-coding-convention.md#r6.1.6) | yes | +| CUSTOM_GETTERS_SETTERS | [6.1.8](guide/diktat-coding-convention.md#r6.1.8) | no | +| WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR | [6.1.9](guide/diktat-coding-convention.md#r6.1.9) | no | +| TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED | [6.1.10](guide/diktat-coding-convention.md#r6.1.10) | yes | +| COMPACT_OBJECT_INITIALIZATION | [6.1.11](guide/diktat-coding-convention.md#r6.1.11) | yes | +| EXTENSION_FUNCTION_SAME_SIGNATURE | [6.2.2](guide/diktat-coding-convention.md#r6.2.2) | no | \ No newline at end of file From 4d17fe057e8160218185de3535898cd66a06500d Mon Sep 17 00:00:00 2001 From: Peter Trifanov Date: Mon, 23 Nov 2020 11:43:12 +0300 Subject: [PATCH 04/19] Created tests for diktat-gradle-plugin (#561) ### What's done: * Added tests * Added jacoco config --- diktat-gradle-plugin/build.gradle.kts | 34 +++++++++++-- diktat-gradle-plugin/pom.xml | 23 ++++++++- .../plugin/gradle/DiktatGradlePluginTest.kt | 31 +++++++++++ .../plugin/gradle/DiktatJavaExecTaskTest.kt | 51 +++++++++++++++++++ .../cqfn/diktat/plugin/gradle/UtilsTest.kt | 24 +++++++++ 5 files changed, 157 insertions(+), 6 deletions(-) create mode 100644 diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt create mode 100644 diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt create mode 100644 diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/UtilsTest.kt diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index 9837d19807..e6f0258bd0 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -3,6 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { `java-gradle-plugin` kotlin("jvm") version "1.4.10" + jacoco } repositories { @@ -14,8 +15,9 @@ repositories { jcenter() } -val ktlintVersion: String by project -val diktatVersion = project.version +val ktlintVersion = project.properties.getOrDefault("ktlintVersion", "0.39.0") as String +val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.1.6-SNAPSHOT" +val junitVersion = project.properties.getOrDefault("junitVersion", "5.7.0") as String dependencies { implementation(kotlin("gradle-plugin-api")) @@ -23,11 +25,14 @@ dependencies { exclude("com.pinterest.ktlint", "ktlint-ruleset-standard") } implementation("com.pinterest.ktlint:ktlint-reporter-plain:$ktlintVersion") - implementation("org.cqfn.diktat:diktat-rules:$version") + implementation("org.cqfn.diktat:diktat-rules:$diktatVersion") + + testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion") } val generateVersionsFile by tasks.registering { - val versionsFile = File("$buildDir/generated/src/main/generated/Versions.kt") + val versionsFile = File("$buildDir/generated/src/generated/Versions.kt") outputs.file(versionsFile) @@ -43,14 +48,16 @@ val generateVersionsFile by tasks.registering { ) } } -sourceSets.main.get().java.srcDir("$buildDir/generated/src/main") +sourceSets.main.get().java.srcDir("$buildDir/generated/src") tasks.withType { kotlinOptions { // fixme: kotlin 1.3 is required for gradle <6.8 languageVersion = "1.3" apiVersion = "1.3" + jvmTarget = "1.8" } + dependsOn.add(generateVersionsFile) } @@ -66,3 +73,20 @@ gradlePlugin { java { withSourcesJar() } + +// === testing & code coverage, consistent with maven +tasks.withType { + useJUnitPlatform() + extensions.configure(JacocoTaskExtension::class) { + setDestinationFile(file("target/jacoco.exec")) + } +} + +tasks.jacocoTestReport { + dependsOn(tasks.test) + reports { + // xml report is used by codecov + xml.isEnabled = true + xml.destination = file("target/site/jacoco/jacoco.xml") + } +} diff --git a/diktat-gradle-plugin/pom.xml b/diktat-gradle-plugin/pom.xml index 2fb387649f..27ffb001f6 100644 --- a/diktat-gradle-plugin/pom.xml +++ b/diktat-gradle-plugin/pom.xml @@ -62,18 +62,39 @@ exec-maven-plugin false + + gradle-test + test + + ${gradle.executable} + + clean + jacocoTestReport + -Pgroup=${project.groupId} + -Pversion=${project.version} + -Pdescription=${project.description} + -PktlintVersion=${ktlint.version} + -PjunitVersion=${junit.version} + -S + + ${skip.gradle.build} + + + exec + + gradle prepare-package ${gradle.executable} - clean ${gradle.task} -Pgroup=${project.groupId} -Pversion=${project.version} -Pdescription=${project.description} -PktlintVersion=${ktlint.version} + -PjunitVersion=${junit.version} -S ${skip.gradle.build} diff --git a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt new file mode 100644 index 0000000000..d489d99dec --- /dev/null +++ b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatGradlePluginTest.kt @@ -0,0 +1,31 @@ +package org.cqfn.diktat.plugin.gradle + +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test + +class DiktatGradlePluginTest { + private val projectBuilder = ProjectBuilder.builder() + private lateinit var project: Project + + @BeforeEach + fun setUp() { + project = projectBuilder.build() + project.pluginManager.apply(DiktatGradlePlugin::class.java) + } + + @Test + fun `check that tasks are registered`() { + Assertions.assertTrue(project.tasks.findByName("diktatCheck") != null) + Assertions.assertTrue(project.tasks.findByName("diktatFix") != null) + } + + @Test + fun `check default extension properties`() { + val diktatExtension = project.extensions.getByName("diktat") as DiktatExtension + Assertions.assertFalse(diktatExtension.debug) + Assertions.assertIterableEquals(project.fileTree("src").files, diktatExtension.inputs.files) + } +} diff --git a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt new file mode 100644 index 0000000000..24f34cdc93 --- /dev/null +++ b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskTest.kt @@ -0,0 +1,51 @@ +package org.cqfn.diktat.plugin.gradle + +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.io.File + +class DiktatJavaExecTaskTest { + private val projectBuilder = ProjectBuilder.builder() + private lateinit var project: Project + + @BeforeEach + fun setUp() { + project = projectBuilder.build() + } + + @Test + fun `check command line for various inputs`() { + val pwd = project.file(".") + assertCommandLineEquals( + listOf(null, "$pwd" + listOf("src", "**", "*.kt").joinToString(File.separator, prefix = File.separator)), + DiktatExtension().apply { + inputs = project.files("src/**/*.kt") + } + ) + } + + @Test + fun `check command line in debug mode`() { + val pwd = project.file(".") + assertCommandLineEquals( + listOf(null, "--debug", "$pwd${listOf("src", "**", "*.kt").joinToString(File.separator, prefix = File.separator)}"), + DiktatExtension().apply { + inputs = project.files("src/**/*.kt") + debug = true + } + ) + } + + private fun registerDiktatTask(extension: DiktatExtension) = project.tasks.register( + "test", DiktatJavaExecTaskBase::class.java, + "6.7", extension, project.configurations.create("diktat") + ) + + private fun assertCommandLineEquals(expected: List, extension: DiktatExtension) { + val task = registerDiktatTask(extension).get() + Assertions.assertIterableEquals(expected, task.commandLine) + } +} diff --git a/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/UtilsTest.kt b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/UtilsTest.kt new file mode 100644 index 0000000000..5347b7e123 --- /dev/null +++ b/diktat-gradle-plugin/src/test/kotlin/org/cqfn/diktat/plugin/gradle/UtilsTest.kt @@ -0,0 +1,24 @@ +package org.cqfn.diktat.plugin.gradle + +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Test + +class UtilsTest { + @Test + fun `test gradle version`() { + Assertions.assertEquals( + GradleVersion.fromString("6.6.1"), + GradleVersion(6, 6, 1, null) + ) + + Assertions.assertEquals( + GradleVersion.fromString("6.7"), + GradleVersion(6, 7, 0, null) + ) + + Assertions.assertEquals( + GradleVersion.fromString("6.7-rc-5"), + GradleVersion(6, 7, 0, "rc-5") + ) + } +} From 3888e940a842d38c2ecc4d9f4fcdc78ef342fd83 Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Mon, 23 Nov 2020 15:23:34 +0300 Subject: [PATCH 05/19] Bugfix. ClassCastException in IndentationRule when blank line is added after @implNote tag in KDoc (#527) * bugfix/class-cast-exception-kdoc(#480) ### What's done: * Fixed bugs --- .../diktat/ruleset/rules/kdoc/KdocFormatting.kt | 8 +++----- .../cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt | 6 ++++++ .../kdoc/KdocFormattingFullExpected.kt | 7 +++++++ .../paragraph2/kdoc/KdocFormattingFullTest.kt | 7 +++++++ .../paragraph2/kdoc/SpecialTagsInKdocExpected.kt | 1 - .../smoke/src/main/kotlin/Example3Expected.kt | 7 +++++++ .../test/smoke/src/main/kotlin/Example3Test.kt | 9 +++++++++ .../smoke/src/main/kotlin/Example4Expected.kt | 16 ++++++++++++++++ .../test/smoke/src/main/kotlin/Example4Test.kt | 15 +++++++++++++++ 9 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt create mode 100644 diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt index 1cbf3bd793..77e036eb4d 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/kdoc/KdocFormatting.kt @@ -76,8 +76,6 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo emitWarn = emit fileName = node.getRootNode().getFileName() - val declarationTypes = setOf(CLASS, FUN, PROPERTY) - if (node.elementType == KDOC && isKdocNotEmpty(node)) { checkNoDeprecatedTag(node) checkEmptyTags(node.kDocTags()) @@ -278,7 +276,7 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo } } - @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION", "ComplexMethod") private fun checkNewLineAfterSpecialTags(node: ASTNode) { val presentSpecialTagNodes = node .getFirstChildWithType(KDOC_SECTION) @@ -304,13 +302,13 @@ class KdocFormatting(private val configRules: List) : Rule("kdoc-fo node.removeChild(node.lastChildNode) // KDOC_LEADING_ASTERISK node.removeChild(node.lastChildNode) // WHITE_SPACE } - if (node.lastChildNode.elementType != KDOC_LEADING_ASTERISK) { + if (node.treeParent.lastChildNode != node && node.lastChildNode.elementType != KDOC_LEADING_ASTERISK) { val indent = node .prevSibling { it.elementType == WHITE_SPACE } ?.text ?.substringAfter('\n') ?.count { it == ' ' } ?: 0 - node.addChild(LeafPsiElement(WHITE_SPACE, "\n${" ".repeat(indent)}"), null) + node.addChild(PsiWhiteSpaceImpl("\n${" ".repeat(indent)}"), null) node.addChild(LeafPsiElement(KDOC_LEADING_ASTERISK, "*"), null) } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt index 2dceac75a0..3f85f87cb3 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/smoke/DiktatSmokeTest.kt @@ -96,6 +96,12 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin", fixAndCompare("Example3Expected.kt", "Example3Test.kt") } + @Test + @Tag("DiktatRuleSetProvider") + fun `smoke test #4`() { + fixAndCompare("Example4Expected.kt", "Example4Test.kt") + } + @Test @Tag("DiktatRuleSetProvider") fun `regression - should not fail if package is not set`() { diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullExpected.kt index 54b5583235..aa36ac1821 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullExpected.kt @@ -17,3 +17,10 @@ class Example { @Deprecated(message = "Use testNew") fun test(a: Int): Int = 2 * a } + +class Foo { + /** + * @implNote lorem ipsum + */ + private fun foo() {} +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullTest.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullTest.kt index 78ffb4bdcc..daaa408802 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingFullTest.kt @@ -17,3 +17,10 @@ class Example { */ fun test(a: Int): Int = 2 * a } + +class Foo { + /** + * @implNote lorem ipsum + */ + private fun foo() {} +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt index 8410afedcd..c50aaf35a8 100644 --- a/diktat-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/SpecialTagsInKdocExpected.kt @@ -9,7 +9,6 @@ class SpecialTagsInKdoc { * @implSpec bar * * @implNote baz - * */ fun test() = Unit } diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Expected.kt index 231a5813b9..32d8804eda 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Expected.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Expected.kt @@ -45,3 +45,10 @@ class Example { } } +class Foo { + /** + * @implNote lorem ipsum + */ + private fun foo() {} +} + diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Test.kt index cefa503487..ddd801edf8 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Test.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example3Test.kt @@ -40,3 +40,12 @@ class Example { foo() } } + + +class Foo { + /** + * @implNote lorem ipsum + */ + private fun foo() {} +} + diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt new file mode 100644 index 0000000000..79589c1926 --- /dev/null +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat + +class SpecialTagsInKdoc { + /** + * Empty function to test KDocs + * @apiNote foo + * + * @implSpec bar + * + * @implNote baz + * + * @return + */ + fun test() = Unit +} + diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt new file mode 100644 index 0000000000..df836479f9 --- /dev/null +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt @@ -0,0 +1,15 @@ +package org.cqfn.diktat + +class SpecialTagsInKdoc { + + /** + * Empty function to test KDocs + * @apiNote foo + * @implSpec bar + * + * + * @implNote baz + */ + fun test() = Unit +} + From 090e575d75fe82f76f1cd5be162b579f8eed0c19 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 23 Nov 2020 16:29:19 +0300 Subject: [PATCH 06/19] Bump kotlin.version from 1.4.10 to 1.4.20 (#562) Bumps `kotlin.version` from 1.4.10 to 1.4.20. Updates `kotlin-stdlib-jdk8` from 1.4.10 to 1.4.20 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.4.20/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.4.10...v1.4.20) Updates `kotlin-compiler-embeddable` from 1.4.10 to 1.4.20 - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v1.4.20/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v1.4.10...v1.4.20) - Fixed compilation errors Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Peter Trifanov --- diktat-gradle-plugin/build.gradle.kts | 4 ++-- .../kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt | 7 ++++--- .../cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt | 7 ++++--- .../org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt | 7 ++++--- pom.xml | 2 +- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index e6f0258bd0..7ddd98bd68 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -2,7 +2,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { `java-gradle-plugin` - kotlin("jvm") version "1.4.10" + kotlin("jvm") version "1.4.20" jacoco } @@ -16,7 +16,7 @@ repositories { } val ktlintVersion = project.properties.getOrDefault("ktlintVersion", "0.39.0") as String -val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.1.6-SNAPSHOT" +val diktatVersion = project.version.takeIf { it.toString() != Project.DEFAULT_VERSION } ?: "0.1.5" val junitVersion = project.properties.getOrDefault("junitVersion", "5.7.0") as String dependencies { implementation(kotlin("gradle-plugin-api")) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt index 3c47a097ae..e33625edb1 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/PackageNaming.kt @@ -16,7 +16,6 @@ import org.cqfn.diktat.ruleset.constants.Warnings.PACKAGE_NAME_INCORRECT_PREFIX import org.cqfn.diktat.ruleset.constants.Warnings.PACKAGE_NAME_INCORRECT_SYMBOLS import org.cqfn.diktat.ruleset.constants.Warnings.PACKAGE_NAME_MISSING import org.cqfn.diktat.ruleset.utils.* -import org.jetbrains.kotlin.backend.common.onlyIf import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl @@ -44,8 +43,10 @@ class PackageNaming(private val configRules: List) : Rule("package- emitWarn = emit val configuration by configRules.getCommonConfiguration() - domainName = configuration.onlyIf({ isDefault }) { - log.error("Not able to find an external configuration for domain name in the common configuration (is it missing in yml config?)") + domainName = configuration.also { + if (it.isDefault) { + log.error("Not able to find an external configuration for domain name in the common configuration (is it missing in yml config?)") + } } .domainName diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt index 43c4f5d7bd..256c8760c2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/classes/SingleInitRule.kt @@ -12,7 +12,6 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getIdentifierName -import org.jetbrains.kotlin.backend.common.onlyIf import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl @@ -44,7 +43,8 @@ class SingleInitRule(private val configRule: List) : Rule("multiple node.children() .filter { it.elementType == CLASS_INITIALIZER } .toList() - .onlyIf({ size > 1 }) { initBlocks -> + .takeIf { it.size > 1 } + ?.let { initBlocks -> val className = node.treeParent.getIdentifierName()?.text Warnings.MULTIPLE_INIT_BLOCKS.warnAndFix(configRule, emitWarn, isFixMode, "in class <$className> found ${initBlocks.size} `init` blocks", node.startOffset, node) { @@ -97,7 +97,8 @@ class SingleInitRule(private val configRule: List) : Rule("multiple .filter { (property, assignments) -> !(property.psi as KtProperty).hasBody() && assignments.size == 1 } - .onlyIf({ isNotEmpty() }) { + .takeIf { it.isNotEmpty() } + ?.let { Warnings.MULTIPLE_INIT_BLOCKS.warnAndFix(configRule, emitWarn, isFixMode, "`init` block has assignments that can be moved to declarations", initBlock.startOffset, initBlock ) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt index 70b34eed57..64c8c1cc04 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt @@ -70,7 +70,6 @@ import org.cqfn.diktat.ruleset.utils.isFollowedByNewline import org.cqfn.diktat.ruleset.utils.isSingleLineIfElse import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine import org.cqfn.diktat.ruleset.utils.log -import org.jetbrains.kotlin.backend.common.onlyIf import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.psi.PsiElement import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement @@ -340,7 +339,8 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" .takeWhile { !it.textContains('\n') } .filter { it.elementType == VALUE_PARAMETER } .toList() - .onlyIf({ size > 1 }) { + .takeIf { it.size > 1 } + ?.let { WRONG_NEWLINES.warnAndFix(configRules, emitWarn, isFixMode, "first parameter should be placed on a separate line " + "or all other parameters should be aligned with it in declaration of <${node.getParentIdentifier()}>", node.startOffset, node) { node.appendNewlineMergingWhiteSpace(it.first().treePrev.takeIf { it.elementType == WHITE_SPACE }, it.first()) @@ -354,7 +354,8 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" !it.treeNext.run { elementType == WHITE_SPACE && textContains('\n') } } .toList() - .onlyIf({ isNotEmpty() }) { invalidCommas -> + .takeIf { it.isNotEmpty() } + ?.let { invalidCommas -> WRONG_NEWLINES.warnAndFix(configRules, emitWarn, isFixMode, "$entryType should be placed on different lines in declaration of <${node.getParentIdentifier()}>", node.startOffset, node) { invalidCommas.forEach { comma -> diff --git a/pom.xml b/pom.xml index 06edf5e88c..6ab5ba1a0b 100644 --- a/pom.xml +++ b/pom.xml @@ -42,7 +42,7 @@ 1.8 1.8 UTF-8 - 1.4.10 + 1.4.20 true 1.0.1 0.39.0 From 0bf93cf69d88331db10d1063ad84858261a8ffeb Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Mon, 23 Nov 2020 16:52:27 +0300 Subject: [PATCH 07/19] Bugfix. Newline at the beginning of code block is not removed if there are more than one (#537) * bugfix/newline-is-not-removed(#519) ### What's done: * Fixed bugs --- .../src/main/kotlin/generated/WarningNames.kt | 16 ++++++++-------- .../diktat/ruleset/rules/files/BlankLinesRule.kt | 12 ++++++++++-- .../CodeBlockWithBlankLinesExpected.kt | 6 ++++++ .../blank_lines/CodeBlockWithBlankLinesTest.kt | 8 ++++++++ .../blank_lines/RedundantBlankLinesExpected.kt | 8 +++++++- .../blank_lines/RedundantBlankLinesTest.kt | 8 ++++++++ 6 files changed, 47 insertions(+), 11 deletions(-) diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index c101070ae1..4cce40c1bb 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -35,14 +35,14 @@ public object WarningNames { public const val GENERIC_NAME: String = "GENERIC_NAME" + public const val BACKTICKS_PROHIBITED: String = "BACKTICKS_PROHIBITED" + public const val FUNCTION_NAME_INCORRECT_CASE: String = "FUNCTION_NAME_INCORRECT_CASE" public const val FUNCTION_BOOLEAN_PREFIX: String = "FUNCTION_BOOLEAN_PREFIX" public const val FILE_NAME_INCORRECT: String = "FILE_NAME_INCORRECT" - public const val FILE_NAME_MATCH_CLASS: String = "FILE_NAME_MATCH_CLASS" - public const val EXCEPTION_SUFFIX: String = "EXCEPTION_SUFFIX" public const val CONFUSING_IDENTIFIER_NAMING: String = "CONFUSING_IDENTIFIER_NAMING" @@ -137,8 +137,6 @@ public object WarningNames { public const val LONG_LINE: String = "LONG_LINE" - public const val BACKTICKS_PROHIBITED: String = "BACKTICKS_PROHIBITED" - public const val REDUNDANT_SEMICOLON: String = "REDUNDANT_SEMICOLON" public const val WRONG_NEWLINES: String = "WRONG_NEWLINES" @@ -165,6 +163,12 @@ public object WarningNames { public const val LOCAL_VARIABLE_EARLY_DECLARATION: String = "LOCAL_VARIABLE_EARLY_DECLARATION" + public const val STRING_TEMPLATE_CURLY_BRACES: String = "STRING_TEMPLATE_CURLY_BRACES" + + public const val STRING_TEMPLATE_QUOTES: String = "STRING_TEMPLATE_QUOTES" + + public const val FILE_NAME_MATCH_CLASS: String = "FILE_NAME_MATCH_CLASS" + public const val NULLABLE_PROPERTY_TYPE: String = "NULLABLE_PROPERTY_TYPE" public const val TYPE_ALIAS: String = "TYPE_ALIAS" @@ -176,10 +180,6 @@ public object WarningNames { public const val GENERIC_VARIABLE_WRONG_DECLARATION: String = "GENERIC_VARIABLE_WRONG_DECLARATION" - public const val STRING_TEMPLATE_CURLY_BRACES: String = "STRING_TEMPLATE_CURLY_BRACES" - - public const val STRING_TEMPLATE_QUOTES: String = "STRING_TEMPLATE_QUOTES" - public const val FLOAT_IN_ACCURATE_CALCULATIONS: String = "FLOAT_IN_ACCURATE_CALCULATIONS" public const val AVOID_NULL_CHECKS: String = "AVOID_NULL_CHECKS" diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/BlankLinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/BlankLinesRule.kt index f5f6c22260..023da07666 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/BlankLinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/BlankLinesRule.kt @@ -3,11 +3,16 @@ package org.cqfn.diktat.ruleset.rules.files import com.pinterest.ktlint.core.Rule import com.pinterest.ktlint.core.ast.ElementType.BLOCK import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY +import com.pinterest.ktlint.core.ast.ElementType.FILE +import com.pinterest.ktlint.core.ast.ElementType.FUNCTION_LITERAL +import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.LBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.TOO_MANY_BLANK_LINES +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.leaveExactlyNumNewLines import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine import org.cqfn.diktat.ruleset.utils.numNewLines @@ -39,7 +44,7 @@ class BlankLinesRule(private val configRules: List) : Rule("blank-l } private fun handleBlankLine(node: ASTNode) { - if (node.treeParent.elementType.let { it == BLOCK || it == CLASS_BODY }) { + if (node.treeParent.elementType.let { it == BLOCK || it == CLASS_BODY || it == FUNCTION_LITERAL }) { if ((node.treeNext.elementType == RBRACE) xor (node.treePrev.elementType == LBRACE)) { // if both are present, this is not beginning or end // if both are null, then this block is empty and is handled in another rule @@ -53,7 +58,10 @@ class BlankLinesRule(private val configRules: List) : Rule("blank-l private fun handleTooManyBlankLines(node: ASTNode) { TOO_MANY_BLANK_LINES.warnAndFix(configRules, emitWarn, isFixMode, "do not use more than two consecutive blank lines", node.startOffset, node) { - node.leaveExactlyNumNewLines(2) + if (node.treeParent.elementType != FILE && node.treeParent.getFirstChildWithType(WHITE_SPACE) == node) + node.leaveExactlyNumNewLines(1) + else + node.leaveExactlyNumNewLines(2) } } } diff --git a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesExpected.kt index e6835f1341..2a5b877daf 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesExpected.kt @@ -4,4 +4,10 @@ class Example { fun foo() { bar() } + + fun bar() { + println() + println() + } } + diff --git a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesTest.kt b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesTest.kt index 49e09402a2..902d0a26bf 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/CodeBlockWithBlankLinesTest.kt @@ -7,4 +7,12 @@ class Example { bar() } + + fun bar() { + + + println() + println() + } } + diff --git a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesExpected.kt index d227da0a2c..e71d8117dd 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesExpected.kt @@ -1,8 +1,14 @@ package test.paragraph3.blank_lines class Example { - val foo = 0 fun bar() { } + + fun foo() { + list.map { + bar() + } + } } + diff --git a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesTest.kt b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesTest.kt index c875259e9d..7848551585 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/blank_lines/RedundantBlankLinesTest.kt @@ -8,4 +8,12 @@ class Example { fun bar() { } + + fun foo() { + list.map { + + bar() + } + } } + From a1ccdbd45e1f2c3dc32e2599da3014a76b00a29d Mon Sep 17 00:00:00 2001 From: Denis Kumar Date: Mon, 23 Nov 2020 17:30:28 +0300 Subject: [PATCH 08/19] WhiteSpaceRule after NewlinesRule (#549) * WhiteSpaceRule after NewlinesRule ### What's done: Fixed bug and added tests --- .../org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt | 2 +- .../org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt | 2 +- .../test/paragraph3/newlines/FunctionalStyleExpected.kt | 2 ++ .../resources/test/paragraph3/newlines/FunctionalStyleTest.kt | 3 +++ .../resources/test/smoke/src/main/kotlin/Example2Expected.kt | 3 +++ .../test/resources/test/smoke/src/main/kotlin/Example2Test.kt | 4 ++++ 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index 7148055352..14fae1bc4d 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -107,10 +107,10 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy ::ExtensionFunctionsSameNameRule, // formatting: moving blocks, adding line breaks, indentations etc. ::ConsecutiveSpacesRule, - ::WhiteSpaceRule, // this rule should be after other rules that can cause wrong spacing ::HeaderCommentRule, ::FileStructureRule, // this rule should be right before indentation because it should operate on already valid code ::NewlinesRule, // newlines need to be inserted right before fixing indentation + ::WhiteSpaceRule, // this rule should be after other rules that can cause wrong spacing ::IndentationRule // indentation rule should be the last because it fixes formatting after all the changes done by previous rules ) .map { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt index 64c8c1cc04..2b54696df0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/files/NewlinesRule.kt @@ -297,7 +297,7 @@ class NewlinesRule(private val configRules: List) : Rule("newlines" val colon = funNode.findChildByType(COLON)!! val expression = node.findChildByType(RETURN_KEYWORD)!!.nextCodeSibling()!! funNode.apply { - removeRange(colon, null) + removeRange(if (colon.treePrev.elementType == WHITE_SPACE) colon.treePrev else colon, null) addChild(PsiWhiteSpaceImpl(" "), null) addChild(LeafPsiElement(EQ, "="), null) addChild(PsiWhiteSpaceImpl(" "), null) diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleExpected.kt index 94641ee4b1..e7eccb61dc 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleExpected.kt @@ -10,3 +10,5 @@ fun foo(list: List?) { ?.qux() ?:foobar } + +fun bar(x :Int,y:Int) = x+ y diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleTest.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleTest.kt index cf029827e2..4315b7dadc 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/FunctionalStyleTest.kt @@ -7,3 +7,6 @@ fun foo(list: List?) { ?: foobar } + +fun bar(x :Int,y:Int) :Int { + return x+ y } diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt index 4f6be67d67..b95f7d0413 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Expected.kt @@ -24,5 +24,8 @@ private fun foo(node: ASTNode) { // this is a generated else block } } + val qwe = a && b + val qwe = a && + b } diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt index cd34b64f85..47c6406a26 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example2Test.kt @@ -15,4 +15,8 @@ private fun foo (node: ASTNode) { when (node.elementType) { CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node) } + val qwe = a + && b + val qwe = a && + b } \ No newline at end of file From 0dd9f908ef97a9d30323b7bbc858e343345f6393 Mon Sep 17 00:00:00 2001 From: Denis Kumar Date: Mon, 23 Nov 2020 17:37:37 +0300 Subject: [PATCH 09/19] different functions due to semicolon (#557) ### What's done: Fixed bug --- .../org/cqfn/diktat/ruleset/utils/ASTConstants.kt | 3 ++- .../ruleset/chapter3/files/NewlinesRuleFixTest.kt | 6 ++++++ .../paragraph3/newlines/OneLineFunctionExpected.kt | 6 ++++++ .../test/paragraph3/newlines/OneLineFunctionTest.kt | 10 ++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionExpected.kt create mode 100644 diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionTest.kt diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/ASTConstants.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/ASTConstants.kt index 1d4c8c40cf..d3f9867d6d 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/ASTConstants.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/ASTConstants.kt @@ -2,6 +2,7 @@ package org.cqfn.diktat.ruleset.utils import com.pinterest.ktlint.core.ast.ElementType.LBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACE +import com.pinterest.ktlint.core.ast.ElementType.SEMICOLON import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE /** @@ -15,7 +16,7 @@ internal const val SET_PREFIX = "set" /** * List of element types present in empty code block `{ }` */ -val emptyBlockList = listOf(LBRACE, WHITE_SPACE, RBRACE) +val emptyBlockList = listOf(LBRACE, WHITE_SPACE, SEMICOLON, RBRACE) internal const val EMPTY_BLOCK_TEXT = "{}" diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleFixTest.kt index cf2062db62..7ba385f029 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleFixTest.kt @@ -54,4 +54,10 @@ class NewlinesRuleFixTest : FixTestBase("test/paragraph3/newlines", ::NewlinesRu fun `should insert newlines in a long parameter or supertype list`() { fixAndCompare("ParameterListExpected.kt", "ParameterListTest.kt") } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `should fix one line function with and without semicolon`() { + fixAndCompare("OneLineFunctionExpected.kt", "OneLineFunctionTest.kt") + } } diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionExpected.kt new file mode 100644 index 0000000000..f68b0c37ec --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionExpected.kt @@ -0,0 +1,6 @@ +package test.paragraph3.newlines + +class Example { + fun doubleA() = 2 * a + fun doubleA() = 2 * a +} diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionTest.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionTest.kt new file mode 100644 index 0000000000..bdcdc2f84a --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/OneLineFunctionTest.kt @@ -0,0 +1,10 @@ +package test.paragraph3.newlines + +class Example { + fun doubleA(): Int { + return 2 * a; + } + fun doubleA(): Int { + return 2 * a + } +} From d8651d2009879e17167ec2c370e0d5023835f482 Mon Sep 17 00:00:00 2001 From: Denis Kumar Date: Mon, 23 Nov 2020 19:07:36 +0300 Subject: [PATCH 10/19] Rule 6.1.3 Do not use the primary constructor if it is empty and has no sense (#528) * Rule 6.1.3 ### What's done: Made rule, added tests and documentations --- diktat-analysis.yml | 3 + .../src/main/kotlin/generated/WarningNames.kt | 2 + .../cqfn/diktat/ruleset/constants/Warnings.kt | 1 + .../rules/AvoidEmptyPrimaryConstructor.kt | 35 +++++++++++ .../ruleset/rules/DiktatRuleSetProvider.kt | 1 + .../main/resources/diktat-analysis-huawei.yml | 3 + .../src/main/resources/diktat-analysis.yml | 3 + .../EmptyPrimaryConstructorFixTest.kt | 16 +++++ .../EmptyPrimaryConstructorWarnTest.kt | 62 +++++++++++++++++++ .../primary_constructor/EmptyPCExpected.kt | 18 ++++++ .../primary_constructor/EmptyPCTest.kt | 18 ++++++ info/available-rules.md | 1 + 12 files changed, 163 insertions(+) create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt create mode 100644 diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt create mode 100644 diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 4fdbdf87cf..df66840744 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -297,4 +297,7 @@ enabled: true # Checks if extension function with the same signature don't have related classes - name: EXTENSION_FUNCTION_SAME_SIGNATURE + enabled: true +# Checks if there is empty primary constructor +- name: EMPTY_PRIMARY_CONSTRUCTOR enabled: true \ No newline at end of file diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 4cce40c1bb..387e6f33a4 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -219,4 +219,6 @@ public object WarningNames { "TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED" public const val EXTENSION_FUNCTION_SAME_SIGNATURE: String = "EXTENSION_FUNCTION_SAME_SIGNATURE" + + public const val EMPTY_PRIMARY_CONSTRUCTOR: String = "EMPTY_PRIMARY_CONSTRUCTOR" } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt index 29eb0cf743..6bf1d364fe 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt @@ -130,6 +130,7 @@ enum class Warnings(val canBeAutoCorrected: Boolean, val ruleId: String, private USELESS_SUPERTYPE(true, "6.1.5", "unnecessary supertype specification"), TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "6.1.10", "trivial property accessors are not recommended"), EXTENSION_FUNCTION_SAME_SIGNATURE(false, "6.2.2", "extension functions should not have same signature if their receiver classes are related"), + EMPTY_PRIMARY_CONSTRUCTOR(true,"6.1.3", "avoid empty primary constructor"), ; /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt new file mode 100644 index 0000000000..00694a13be --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/AvoidEmptyPrimaryConstructor.kt @@ -0,0 +1,35 @@ +package org.cqfn.diktat.ruleset.rules + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.CLASS +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_PRIMARY_CONSTRUCTOR +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtClass + +class AvoidEmptyPrimaryConstructor(private val configRules: List) : Rule("avoid-empty-primary-constructor") { + + + private var isFixMode: Boolean = false + private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) + + override fun visit(node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emitWarn = emit + isFixMode = autoCorrect + + if (node.elementType == CLASS) + checkCLass(node.psi as KtClass) + } + + @Suppress("UnsafeCallOnNullableType") + private fun checkCLass(ktClass: KtClass) { + if(ktClass.primaryConstructor?.valueParameters?.isNotEmpty() != false || ktClass.primaryConstructorModifierList != null) + return + EMPTY_PRIMARY_CONSTRUCTOR.warnAndFix(configRules, emitWarn, isFixMode, ktClass.nameIdentifier!!.text, + ktClass.node.startOffset, ktClass.node) { + ktClass.node.removeChild(ktClass.primaryConstructor!!.node) + } + } +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index 14fae1bc4d..64d98bf7e9 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -71,6 +71,7 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy ::BracesInConditionalsAndLoopsRule, ::BlockStructureBraces, ::EmptyBlock, + ::AvoidEmptyPrimaryConstructor, ::EnumsSeparated, ::SingleLineStatementsRule, ::MultipleModifiersSequence, diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 6a09997c0d..9be9160d39 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -296,3 +296,6 @@ # Checks if extension function with the same signature don't have related classes - name: EXTENSION_FUNCTION_SAME_SIGNATURE enabled: true +# Checks if there is empty primary constructor +- name: EMPTY_PRIMARY_CONSTRUCTOR + enabled: true \ No newline at end of file diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml index 17d166c0b1..9b5b082ba9 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -298,3 +298,6 @@ # Checks if extension function with the same signature don't have related classes - name: EXTENSION_FUNCTION_SAME_SIGNATURE enabled: true +# Checks if there is empty primary constructor +- name: EMPTY_PRIMARY_CONSTRUCTOR + enabled: true \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt new file mode 100644 index 0000000000..2498d08324 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorFixTest.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import generated.WarningNames +import org.cqfn.diktat.ruleset.rules.AvoidEmptyPrimaryConstructor +import org.cqfn.diktat.util.FixTestBase +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class EmptyPrimaryConstructorFixTest: FixTestBase("test/chapter6/primary_constructor", ::AvoidEmptyPrimaryConstructor) { + + @Test + @Tag(WarningNames.EMPTY_PRIMARY_CONSTRUCTOR) + fun `should remove empty primary constructor`() { + fixAndCompare("EmptyPCExpected.kt", "EmptyPCTest.kt") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt new file mode 100644 index 0000000000..ec2e7d244c --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/EmptyPrimaryConstructorWarnTest.kt @@ -0,0 +1,62 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames +import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_PRIMARY_CONSTRUCTOR +import org.cqfn.diktat.ruleset.rules.AvoidEmptyPrimaryConstructor +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.util.LintTestBase +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class EmptyPrimaryConstructorWarnTest: LintTestBase(::AvoidEmptyPrimaryConstructor) { + private val ruleId = "$DIKTAT_RULE_SET_ID:avoid-empty-primary-constructor" + + @Test + @Tag(WarningNames.EMPTY_PRIMARY_CONSTRUCTOR) + fun `simple classes with empty primary constructor`() { + lintMethod( + """ + |class Some() { + | val a = 10 + | constructor(a: String): this() { + | this.a = a + | } + |} + | + |class Some1() { + | val a = 10 + | companion object {} + |} + | + |class Some2 { + | val a = 10 + | constructor(a: String): this() { + | this.a = a + | } + |} + | + |class Some3 private constructor () { + | + |} + """.trimMargin(), + LintError(1,1,ruleId, "${EMPTY_PRIMARY_CONSTRUCTOR.warnText()} Some", true), + LintError(8,1,ruleId, "${EMPTY_PRIMARY_CONSTRUCTOR.warnText()} Some1", true) + ) + } + + @Test + @Tag(WarningNames.EMPTY_PRIMARY_CONSTRUCTOR) + fun `correct example with empty primary constructor and modifiers`() { + lintMethod( + """ + |class Some1 private constructor () { + | + |} + | + |class Some2 @Inject constructor() { + |} + """.trimMargin() + ) + } +} diff --git a/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt new file mode 100644 index 0000000000..750cd85cc3 --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCExpected.kt @@ -0,0 +1,18 @@ +package test.chapter6.primary_constructor + +class Test { + var a: Int = 0 + var b: Int = 0 +} + +class Test { + var a = "Property" + + init { + println("some init") + } + + constructor(a: String): this() { + this.a = a + } +} diff --git a/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt new file mode 100644 index 0000000000..04369eab8b --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/primary_constructor/EmptyPCTest.kt @@ -0,0 +1,18 @@ +package test.chapter6.primary_constructor + +class Test() { + var a: Int = 0 + var b: Int = 0 +} + +class Test() { + var a = "Property" + + init { + println("some init") + } + + constructor(a: String): this() { + this.a = a + } +} diff --git a/info/available-rules.md b/info/available-rules.md index 02aeda2454..e1e15fdf8d 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -95,6 +95,7 @@ | 5 | 5.2.3 | WRONG_OVERLOADING_FUNCTION_ARGUMENTS | Check: function has overloading instead use default arguments | no | -| | 6 | 6.1.1 | SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY | Check: warns if there is only one secondary constructor in a class
Fix: converts it to a primary constructor | yes | no | Support more complicated logic of constructor conversion | | 6 | 6.1.2 | USE_DATA_CLASS | Check: if class can be made as data class | no | - | yes | +| 6 | 6.1.3 | EMPTY_PRIMARY_CONSTRUCTOR | Check: if there is empty primary constructor | yes| - | yes | | 6 | 6.1.4 | MULTIPLE_INIT_BLOCKS | Checks that classes have only one init block | yes | no | - | | 6 | 6.1.5 | USELESS_SUPERTYPE | Check: if override function can be removed | yes| - | | | 6 | 6.1.6 | CLASS_SHOULD_NOT_BE_ABSTRACT | Checks: if abstract class has any abstract method. If not, warns that class should not be abstract
Fix: deletes abstract modifier | yes | - | - | From cd8375413df5214429b8af553b2d602c60f9fa36 Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Mon, 23 Nov 2020 19:20:05 +0300 Subject: [PATCH 11/19] Bugfix. False-positive LOCAL_VARIABLE_EARLY_DECLARATION (#535) * bugfix/false-positive-local-variable-early-declaration(#488) ### What's done: * Fixed bugs --- .../chapter3/LocalVariablesWarnTest.kt | 20 +++++++++++++++++++ .../smoke/src/main/kotlin/Example4Expected.kt | 10 ++++++++++ .../smoke/src/main/kotlin/Example4Test.kt | 10 ++++++++++ 3 files changed, 40 insertions(+) diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LocalVariablesWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LocalVariablesWarnTest.kt index 9de6f71e15..2b9d8a6136 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LocalVariablesWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/LocalVariablesWarnTest.kt @@ -549,4 +549,24 @@ class LocalVariablesWarnTest : LintTestBase(::LocalVariablesRule) { """.trimMargin() ) } + + @Test + @Tag(WarningNames.LOCAL_VARIABLE_EARLY_DECLARATION) + fun `should not trigger on triple quoted strings`() { + lintMethod( + """ + |class Example { + | fun some() { + | val code = ${"\"\"\""} + | class Some { + | fun for() : String { + | } + | } + | ${"\"\"\""}.trimIndent() + | bar(code) + | } + |} + """.trimMargin() + ) + } } diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt index 79589c1926..cab9a2e5d2 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Expected.kt @@ -14,3 +14,13 @@ class SpecialTagsInKdoc { fun test() = Unit } +fun `method name incorrect, part 4`() { + val code = """ + class TestPackageName { + fun methODTREE(): String { + } + } + """.trimIndent() + lintMethod(code, LintError(2, 7, ruleId, "${FUNCTION_NAME_INCORRECT_CASE.warnText()} methODTREE", true)) +} + diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt index df836479f9..4ccb47ed9b 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example4Test.kt @@ -13,3 +13,13 @@ class SpecialTagsInKdoc { fun test() = Unit } +fun `method name incorrect, part 4`() { + val code = """ + class TestPackageName { + fun methODTREE(): String { + } + } + """.trimIndent() + lintMethod(code, LintError(2, 7, ruleId, "${FUNCTION_NAME_INCORRECT_CASE.warnText()} methODTREE", true)) +} + From 4ab49e18cc189b9ee4adf31438668a6085519cfe Mon Sep 17 00:00:00 2001 From: Alexander Tsay <48321920+aktsay6@users.noreply.github.com> Date: Mon, 23 Nov 2020 19:40:22 +0300 Subject: [PATCH 12/19] Rule 6.1.7 implicit backing property(#443) (#465) * feature/rule-6.1.7-implicit-backing-property(#443) ### What's done: * Logic made * Added warn tests --- diktat-analysis.yml | 5 + .../src/main/kotlin/generated/WarningNames.kt | 2 + .../cqfn/diktat/ruleset/constants/Warnings.kt | 1 + .../ruleset/rules/DiktatRuleSetProvider.kt | 1 + .../rules/ImplicitBackingPropertyRule.kt | 110 ++++++++++++ .../main/resources/diktat-analysis-huawei.yml | 5 + .../src/main/resources/diktat-analysis.yml | 5 + .../ImplicitBackingPropertyWarnTest.kt | 165 ++++++++++++++++++ info/available-rules.md | 1 + 9 files changed, 295 insertions(+) create mode 100644 diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImplicitBackingPropertyRule.kt create mode 100644 diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ImplicitBackingPropertyWarnTest.kt diff --git a/diktat-analysis.yml b/diktat-analysis.yml index df66840744..4d6ea1c15d 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -300,4 +300,9 @@ enabled: true # Checks if there is empty primary constructor - name: EMPTY_PRIMARY_CONSTRUCTOR + enabled: true +# In case of not using field keyword in property accessors, +# there should be explicit backing property with the name of real property +# Example: val table get() {if (_table == null) ...} -> table should have _table +- name: NO_CORRESPONDING_PROPERTY enabled: true \ No newline at end of file diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index 387e6f33a4..9a7f3d4ce0 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -221,4 +221,6 @@ public object WarningNames { public const val EXTENSION_FUNCTION_SAME_SIGNATURE: String = "EXTENSION_FUNCTION_SAME_SIGNATURE" public const val EMPTY_PRIMARY_CONSTRUCTOR: String = "EMPTY_PRIMARY_CONSTRUCTOR" + + public const val NO_CORRESPONDING_PROPERTY: String = "NO_CORRESPONDING_PROPERTY" } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt index 6bf1d364fe..14449b4a9b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/constants/Warnings.kt @@ -131,6 +131,7 @@ enum class Warnings(val canBeAutoCorrected: Boolean, val ruleId: String, private TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED(true, "6.1.10", "trivial property accessors are not recommended"), EXTENSION_FUNCTION_SAME_SIGNATURE(false, "6.2.2", "extension functions should not have same signature if their receiver classes are related"), EMPTY_PRIMARY_CONSTRUCTOR(true,"6.1.3", "avoid empty primary constructor"), + NO_CORRESPONDING_PROPERTY(false, "6.1.7", "backing property should have the same name, but there is no corresponding property") ; /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt index 64d98bf7e9..d84fb58c48 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/DiktatRuleSetProvider.kt @@ -79,6 +79,7 @@ class DiktatRuleSetProvider(private val diktatConfigFile: String = "diktat-analy ::CustomGetterSetterRule, ::CompactInitialization, // other rules + ::ImplicitBackingPropertyRule, ::StringTemplateFormatRule, ::DataClassesRule, ::LocalVariablesRule, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImplicitBackingPropertyRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImplicitBackingPropertyRule.kt new file mode 100644 index 0000000000..2677036941 --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/ImplicitBackingPropertyRule.kt @@ -0,0 +1,110 @@ +package org.cqfn.diktat.ruleset.rules + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.BLOCK +import com.pinterest.ktlint.core.ast.ElementType.CLASS_BODY +import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.FILE +import com.pinterest.ktlint.core.ast.ElementType.GET_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER +import com.pinterest.ktlint.core.ast.ElementType.PROPERTY +import com.pinterest.ktlint.core.ast.ElementType.PROPERTY_ACCESSOR +import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.RETURN +import com.pinterest.ktlint.core.ast.ElementType.SET_KEYWORD +import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings.NO_CORRESPONDING_PROPERTY +import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.getIdentifierName +import org.cqfn.diktat.ruleset.utils.hasAnyChildOfTypes +import org.cqfn.diktat.ruleset.utils.hasChildOfType +import org.cqfn.diktat.ruleset.utils.prettyPrint +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtProperty + +/** + * This rule checks if there is a backing property for field with property accessors, in case they don't use field keyword + */ +class ImplicitBackingPropertyRule(private val configRules: List) : Rule("implicit-backing-property") { + private lateinit var emitWarn: ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) + private var isFixMode: Boolean = false + + override fun visit(node: ASTNode, + autoCorrect: Boolean, + emit: (offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) { + emitWarn = emit + isFixMode = autoCorrect + + if (node.elementType == CLASS_BODY) { + findAllProperties(node) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun findAllProperties(node: ASTNode) { + val properties = node.getChildren(null).filter { it.elementType == PROPERTY } + + val propsWithBackSymbol = properties + .filter { it.getFirstChildWithType(IDENTIFIER)!!.text.startsWith("_") } + .map { + it.getFirstChildWithType(IDENTIFIER)!!.text + } + + properties.filter { it.hasAnyChildOfTypes(PROPERTY_ACCESSOR) }.forEach { + validateAccessors(it, propsWithBackSymbol) + } + } + + private fun validateAccessors(node: ASTNode, propsWithBackSymbol: List) { + val accessors = node.findAllNodesWithSpecificType(PROPERTY_ACCESSOR).filter { it.hasChildOfType(BLOCK) } // exclude get with expression body + + accessors.filter { it.hasChildOfType(GET_KEYWORD) }.forEach { handleGetAccessors(it, node, propsWithBackSymbol) } + accessors.filter { it.hasChildOfType(SET_KEYWORD) }.forEach { handleSetAccessors(it, node, propsWithBackSymbol) } + } + + @Suppress("UnsafeCallOnNullableType") + private fun handleGetAccessors(accessor: ASTNode, node: ASTNode, propsWithBackSymbol: List) { + val refExprs = accessor + .findAllNodesWithSpecificType(RETURN) + .filterNot { it.hasChildOfType(DOT_QUALIFIED_EXPRESSION) } + .flatMap { it.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) } + + val localProps = accessor + .findAllNodesWithSpecificType(PROPERTY) + .map { (it.psi as KtProperty).name!! } + // If refExprs is empty then we assume that it returns some constant + if (refExprs.isNotEmpty()) { + handleReferenceExpressions(node, refExprs, propsWithBackSymbol, localProps) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun handleSetAccessors(accessor: ASTNode, node: ASTNode, propsWithBackSymbol: List) { + val refExprs = accessor.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + + // In set we don't check for local properties. At least one reference expression should contain field or _prop + if (refExprs.isNotEmpty()) { + handleReferenceExpressions(node, refExprs, propsWithBackSymbol, null) + } + } + + @Suppress("UnsafeCallOnNullableType") + private fun handleReferenceExpressions(node: ASTNode, + expressions: List, + backingPropertiesNames: List, + localProperties: List?) { + if (expressions.none { + backingPropertiesNames.contains(it.text) || it.text == "field" || localProperties?.contains(it.text) == true + }) { + raiseWarning(node, node.getFirstChildWithType(IDENTIFIER)!!.text) + } + } + + private fun raiseWarning(node: ASTNode, propName: String) { + NO_CORRESPONDING_PROPERTY.warn(configRules, emitWarn, isFixMode, + "$propName has no corresponding property with name _$propName", node.startOffset, node) + } + +} diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index 9be9160d39..abacac11c0 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -298,4 +298,9 @@ enabled: true # Checks if there is empty primary constructor - name: EMPTY_PRIMARY_CONSTRUCTOR + enabled: true +# In case of not using field keyword in property accessors, +# there should be explicit backing property with the name of real property +# Example: val table get() {if (_table == null) ...} -> table should have _table +- name: NO_CORRESPONDING_PROPERTY enabled: true \ No newline at end of file diff --git a/diktat-rules/src/main/resources/diktat-analysis.yml b/diktat-rules/src/main/resources/diktat-analysis.yml index 9b5b082ba9..385c80565e 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -300,4 +300,9 @@ enabled: true # Checks if there is empty primary constructor - name: EMPTY_PRIMARY_CONSTRUCTOR + enabled: true +# In case of not using field keyword in property accessors, +# there should be explicit backing property with the name of real property +# Example: val table get() {if (_table == null) ...} -> table should have _table +- name: NO_CORRESPONDING_PROPERTY enabled: true \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ImplicitBackingPropertyWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ImplicitBackingPropertyWarnTest.kt new file mode 100644 index 0000000000..54cc0cf0b2 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/ImplicitBackingPropertyWarnTest.kt @@ -0,0 +1,165 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames.NO_CORRESPONDING_PROPERTY +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.ImplicitBackingPropertyRule +import org.cqfn.diktat.util.LintTestBase +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class ImplicitBackingPropertyWarnTest: LintTestBase(::ImplicitBackingPropertyRule) { + private val ruleId = "$DIKTAT_RULE_SET_ID:implicit-backing-property" + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `not trigger on backing property`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | private var _table: Map? = null + | val table:Map + | get() { + | if (_table == null) { + | _table = HashMap() + | } + | return _table ?: throw AssertionError("Set to null by another thread") + | } + | set(value) { field = value } + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `trigger on backing property`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | private var a: Map? = null + | val table:Map + | get() { + | if (a == null) { + | a = HashMap() + | } + | return a ?: throw AssertionError("Set to null by another thread") + | } + | set(value) { field = value } + |} + """.trimMargin(), + LintError(3,4,ruleId, "${Warnings.NO_CORRESPONDING_PROPERTY.warnText()} table has no corresponding property with name _table") + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `don't trigger on regular backing property`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | private var _a: Map? = null + | private val _some:Int? = null + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `don't trigger on regular property`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | private var a: Map? = null + | private val some:Int? = null + | private val _prop: String? = null + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `should not trigger if property has field in accessor`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | val table:Map + | set(value) { field = value } + | val _table: Map? = null + | + | val some: Int + | get() = 3 + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `should not trigger on property with constant return`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | val table:Int + | get() { + | return 3 + | } + | set(value) { field = value } + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `should not trigger on property with chain call return`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | val table:Int + | get() { + | val some = listOf(1,2,3) + | return some.filter { it -> it == 3}.first() + | } + | set(value) { field = value } + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `should not trigger set accessor`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | val foo + | set(value) { + | if(isDelegate) log.debug(value) + | field = value + | } + |} + """.trimMargin() + ) + } + + @Test + @Tag(NO_CORRESPONDING_PROPERTY) + fun `should trigger set accessor`() { + lintMethod( + """ + |class Some(val a: Int = 5) { + | val foo + | set(value) { + | if(isDelegate) log.debug(value) + | a = value + | } + |} + """.trimMargin(), + LintError(2,4 ,ruleId, "${Warnings.NO_CORRESPONDING_PROPERTY.warnText()} foo has no corresponding property with name _foo") + ) + } +} diff --git a/info/available-rules.md b/info/available-rules.md index e1e15fdf8d..7f513d8c1d 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -99,6 +99,7 @@ | 6 | 6.1.4 | MULTIPLE_INIT_BLOCKS | Checks that classes have only one init block | yes | no | - | | 6 | 6.1.5 | USELESS_SUPERTYPE | Check: if override function can be removed | yes| - | | | 6 | 6.1.6 | CLASS_SHOULD_NOT_BE_ABSTRACT | Checks: if abstract class has any abstract method. If not, warns that class should not be abstract
Fix: deletes abstract modifier | yes | - | - | +| 6 | 6.1.7 | NO_CORRESPONDING_PROPERTY | Checks: if in case of using "backing property" scheme, the name of real and back property are the same | no | - | - | | 6 | 6.1.9 | WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR | Check: used the name of a variable in the custom getter or setter | no | - | | 6 | 6.1.10 | TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED | Check: if there are any trivial getters or setters
Fix: Delete trivial getter or setter | yes | - | - | | 6 | 6.1.8 | CUSTOM_GETTERS_SETTERS | Check: Inspection that checks that no custom getters and setters are used for properties | no | - | - | From d622fdc1b6854c3ee3b0706a66aec74a0a178323 Mon Sep 17 00:00:00 2001 From: Denis Kumar Date: Tue, 24 Nov 2020 15:53:18 +0300 Subject: [PATCH 13/19] White-paper (#438) White-paper ### What's done: Created White-paper Co-authored-by: Andrey Kuleshov Co-authored-by: Petr Trifanov Co-authored-by: aktsay6 --- wp/pictures/class.PNG | Bin 0 -> 61130 bytes wp/pictures/data_flow.PNG | Bin 0 -> 30951 bytes wp/pictures/detekt.png | Bin 0 -> 20812 bytes wp/pictures/diktat.png | Bin 0 -> 15618 bytes wp/pictures/kotlinRating.png | Bin 0 -> 109403 bytes wp/pictures/ktfmt.png | Bin 0 -> 22394 bytes wp/pictures/ktlint.png | Bin 0 -> 20001 bytes wp/{ => pictures}/logo.png | Bin wp/pictures/sequence.jpg | Bin 0 -> 152996 bytes wp/pictures/web-example.png | Bin 0 -> 94361 bytes wp/references.bib | 85 ++++++++ wp/sections/appendix.tex | 8 + wp/sections/chapter 4/cli.tex | 24 --- wp/sections/chapter 4/gradle.tex | 44 ---- wp/sections/chapter 4/maven.tex | 37 ---- .../chapter 4/rulesetConfiguration.tex | 1 - wp/sections/compare.tex | 79 +++++++ wp/sections/conclusion.tex | 2 + wp/sections/definition.tex | 7 + wp/sections/diKTat.tex | 5 + wp/sections/download.tex | 8 + wp/sections/examples.tex | 1 + wp/sections/feature.tex | 137 +++++++++++++ wp/sections/introduction.tex | 27 +-- wp/sections/kotlin.tex | 34 +++ wp/sections/work.tex | 194 ++++++++++++++++++ wp/wp.tex | 171 ++++++++++----- 27 files changed, 678 insertions(+), 186 deletions(-) create mode 100644 wp/pictures/class.PNG create mode 100644 wp/pictures/data_flow.PNG create mode 100644 wp/pictures/detekt.png create mode 100644 wp/pictures/diktat.png create mode 100644 wp/pictures/kotlinRating.png create mode 100644 wp/pictures/ktfmt.png create mode 100644 wp/pictures/ktlint.png rename wp/{ => pictures}/logo.png (100%) create mode 100644 wp/pictures/sequence.jpg create mode 100644 wp/pictures/web-example.png create mode 100644 wp/references.bib create mode 100644 wp/sections/appendix.tex delete mode 100644 wp/sections/chapter 4/cli.tex delete mode 100644 wp/sections/chapter 4/gradle.tex delete mode 100644 wp/sections/chapter 4/maven.tex delete mode 100644 wp/sections/chapter 4/rulesetConfiguration.tex create mode 100644 wp/sections/compare.tex create mode 100644 wp/sections/conclusion.tex create mode 100644 wp/sections/definition.tex create mode 100644 wp/sections/diKTat.tex create mode 100644 wp/sections/download.tex create mode 100644 wp/sections/examples.tex create mode 100644 wp/sections/feature.tex create mode 100644 wp/sections/kotlin.tex create mode 100644 wp/sections/work.tex diff --git a/wp/pictures/class.PNG b/wp/pictures/class.PNG new file mode 100644 index 0000000000000000000000000000000000000000..f21aef475c46d79b51a3acb1c77838062aadee0c GIT binary patch literal 61130 zcmeFZcT`hb6E_@1K}A3=D$-TFpdz4D0i~)40ycUJh;%}4(xM_wl&DAx(t8WN1tAIw z(t8ad0#ZU3NFWJ;_Z-xF?{goY^?qx8-=AOBa=Cz$efFN2{hQe{zu94Tv{ac6aUOy| zAWUjEuib+{=yf5Gy-Ek@f!|0JhSmZ9?Qy%OstiGOa1($Z_E{@wDnTH{2*xe*{lL!$ zoo^brK_Gv)ga7u7S@C&8AWKlSYf9SQrVF(UvD}}ZVEkKTQ1$1n52hSG%W&qD;+_=6 zqJ1}Sp4@lythGh`9(vZ7#d{)Od=xlA#~kYr2w{D8P%C74*}0LP4c0ciDUT<25Wfpm z1doiNhR6Ebw3geb@|{$T3>0&(_tCNgK7IgK^XsFid_sTEuOEQ_Zyul% z`p1uugXU)7G9byk-6)GVatxfze(lqnJNQT7FKq*#90UJa-xcZcaYAbb@3S9HEfuC%j9p9aj;?<{5j=9en zJI^#k*Dl9K_|wN_3LC}uZ(ttuYN5%jy)&yeR%&w* zc*}kn3tA9~`07tWqT6VrM&0y(+QDWR@SvYdBpBGtYE{m5*SOA z8g7t01_o|ftW8e1j6a$&v5v;A#xhkdbmiQO%MKV*tGr^@V8U|ZSUlfL>Uej!?oiQy z@9N_1k(E~s#~((k^vQrrcqL_%0#Q_QVebzOCBON54Z}J3Bfal6<$5dL#|)Eu;JVF- z@TMTI1?qrK1SO8$=2c)(R4s7E1M)ff6bjHsa)Ni8U#&fqE7ryhk7R~0XP+&Cyp z!^eo%QQ6i+nK)xnDmScF<0~9sYtr)HAR10-{d@Xutg;fsq<;uMa&3spi-2bhYST2t%xJxEGn@$c@ z{)oC_a)37l4R8PCAC=YQGt10^BXnolvy^VGI(>>>YmkilG;o_m)2wEl!k|I!x|FFF zuA6BvUy-?!-q1e60A&{7EL_cH_+?XxZF} zo&<%tJ4pD)mldO-WWkZC8{8gGUv^?vGBtSAp=&g__*GTSBCOBK_dKDgAX0af3-)UT z(*~4B@9~3r|D=;>JTv>U^!JNggUgu{ZXI=vW#{J~ET8efn4|p8E;OaJ)@)T-MpUng zaPeKSsCy}jUYM`ICF9Y>w+6M(FNY9oE>Fka*N7LvEiP|;$SH+!HTH-9dplf`rtlnF z;vjGy32XHl=l3+Wg-X6UBFrzD?OM;of)vMW+>b|Dr!+;aP5u(fd?E&=nsA|-R3kNN z@u@1mbyi2$u+pnZeS_5^&!N>t4{ss0^|g|ue-@>cp4PGRoXOt#`cA!$erM63E3DOb zWD~6#U<=LYVbGB231&BT&6A<5K?~mM;YethGd7r77$Ky1DaB28$yoK;g&SlZz~&xH zD1&%&C_$xKr>U2xH=Rz%$05{|1t0JzO6*=Bz0W)S7^Q~uQ`cLotHcRsDgu0L_8M)l z7}G2&cG3;3#vTpEY5I<}p=^TX7U`um#pp@cZPYnc!h*78Q(QE zz$;0iX1>gqTP#0`nk#CpbhK|ky9rWeqvk3GVRX}4pmvmJ+()Y;LiJy)$}s~QVi#PG z9+eGo&5OvA%Ut??FM+~$B!0P`fyWE!zaC7loNAfQ`!Qn^K*PMZ?7}k>k_n#%a>P;& zI_S(Cl)mzi*}yf^Upus=igrkNa&SIm^2KzGtmK!Jrl;pdbMy1s-4LXnH9Rrb|K-4I zgKhFg-r@W+!=`NovM6v{9|KVSHxs#<32wYm;(o3~pT=RGHGXqx(dIB|d1K2|L$8gX z#K9%eNSN{{uuEQi72ID5Hlv_8kS$a9EE;rUBL!M9g936VU zhN>KQ5iBTuw8XL6aXn_tG$Z0;FQQ~TO%y#mHxpDbC>jXp=U8&AGoC#(G~@K&U~!#Y z-GwJbUyjDub$n4a)twaoMCV33|gHqs-mp6ONj;K8!!2 zCjf{EDkAZufLAs1<00H)${seRvivIl+XI$ci)(|d))G*!$QOoHqo~_-$`-fv?HR(~ zR+0&o8iBz~3I>@KN>VzEXZ#(SP0tP=v<<~J{H zV4giU&l{fQX>AeXQP+RxIjgVx-H}fF#*FId{-dWKp!zlr1Bm4$cslycRsdKKr_c06Z7|#jvVFM@;v?E zT=Vi};Lqu-^twe_>6Z?la(C3YN-HHDW4#1Tgm06N;bkF2zJy+vMd53o9%%m^1mv^X z9#%OZzY`kkL}q&WAguzE&@Vi*%Agg0|D6BFKP!;{2I{Kk6;yBIpjA}+3Q#|ZYDONN z@}N)8bSXWasbjz{ZnnxQrTOSQSHE!9N;f<1O<0YDLgJ^93-^uEmUX^>05IaQY=H!D zIGDTQgG0a^OYzjIWzV8JdgF$a!|12CL>`BT>WPk_61Du+jfZe|tU>SOpEQf<7eeek zQ3#ZrVU|#gP)MiI1B}JQ>N`WhT^len%ixT3FoJQ`t;Py1i@iijiI*z)dN>4JQUfHE zP6j0S`4mR!j>9)$oy7WT~DpwXe9k zQ#VXmsFBL|Ux^9FGG}=3dM6LBwqc`sw#3xhi1JqA&FE>Ua~eVRVY-3;j*!*-m{)sM zT#He0~S$om}H9s z76C?LQFa<7uyR}gjjA3f1o%Ds|6af~xLleG6LU73$vEiRdP`IUOV?EStXgttIi5C? zneCbYOtzO8@s<)=HgrZ(>^meQwQyDqsJ8Xej!G|u>tI=Y#pSLNuMaQGvT)_jcoDo}TSmM@|)=$Tywn!qH&qnCk#4aAtV?s4I@Sq zY%lR*BwR6m)FiG`(JL(?j6}J4eV@W#w1`0!fA1n$wYm|5D&*W3%8f_KiS1po+A$r4 znFWSeg@N`FpB$4UXjaQ6+Fd~bIh`*eIyk>1W)mwz!zxsazr4LYRz~Q-E%*@>U@{3# z6mCy0&mx1h51p_$holg-EQfNuqzJE$2N*4-zkI{3->on>?YDG`bUF>~4#ysikjjsm z){b*vBFqMbiTAxi)Ld+?V1`vzK&dHJ;%`l3>=gD)bXBu@b(}mstsW4VJQTX^d1KP9 zkQYt5!j%ZbHR7aaR5mInukkMG7c*;Kzjm**9?D7w`x zk5q#_$Y4plglGQXFieezt8xwJxI#M{FYi3(AU;)=K++03C?sV$CvP@zntGyek-Ghm zkFL*Y>qVRF8(Fu>nfBT1LJr=2^;6SD?J+||^wR4GipCgjOZE=DvxxR5ephc*gYyYx z)ULiJNLt;qGHVo#S@vJ(a|$)^`^@8ca{R5_1~PiF71W@|_cPcm`!EMD?x5)VI8~^7 zsV1UPg_>#EBx>Z;I;(Zcq@l`^h}4ck~2QLOdsv7Q-x9yf?gkws>f_soZ}uWBDFrWze!~m z*mg|`_M5FgQwFLAF7OQ?`C7`R|P(u+3*&yd^uyUXSebL@`8$iZwmNN2D7`GVThY zf?q_l=~keStVlq$-|{5zDGUo6IRoCSi5>ab9*|FgeJdjd^!jC@6 zpt%wp4cJM&Z;&vKsl{b<8o{2A5=7|RfpCfN$;!ID%yE@XJT!mRnaU?Cau0tg;7V=_ z;E+Ju=hW}^xzVQ3E$<|whHhPJaAC5?XjBB3Ve)VF+Gn5QLtktjI2$#>vWUpbLa(Fm z%EY!A#ZolnlucR5FA}e_J0DxFmkd4-wtp^e1C!AXpFp{>q9`MtQyk@Cq@!C?0b5)R|Y~7tcqt?*C zc6BdA(;L*gZA(=12T8uCygGSgwXH3BCN8UO7Zf}-X?sx*HYCdSl10`X<-aeoE>I9P z>cD56=YAEbhS=Y;;np-pCq5;4B)`dukTl0R)NSEnlAzXi<*c~0vGzF_aNsIO@ZXCc zEcfa+uUSVg57Sm!Z*1@*Go`AQLcjN})*|K}4|qClj?Y_dj&^DrR#G*4crMWdc%z%w z@OfRN1kdGLnKQFto+!pC0`t~EL8rDp9>WeOT!MF`@{~A2DpC(t>5%t z!`AHfGV$>_)e-N1D0DbN=}VXFmSrB};JSCS12#I`qp81;Y-s4Q;J~La+oP?u+;70L zqu|TEUbY{^vst-DzI_q7vfxR-=Y|?n{^kBIT?-MPC&yxzoQ&M%Oh%pj1Z;20nHcVF zvV#RG*aGi<8QqEA`Fo3OirmBUTq)kOaa{rWbfVyi;H6v?uXNVkvKLT9--qY&fylSw zNcNXC&Nd?HUC%zcOzFh*78==27~smkPPDAexE#_P%hgJqebyLIE1B?E8f^-HGkM3v zQD&#V7An%7CANChy>~P=lR95f*dhY#!NXgeMCE96rGl;8B`zsD z2Mog~Pn-xeA(g@wSWj8#)Q6`qQ7uzl{6nhb=GL=L(df^bxY5tmpj0}yr@vtnv$gA% zt5gKz5lw~K(3w!EW{7wW3G=S#)?07rAjEs5a_CgCjMxEE}h9c;f z0awbLDE|E)5CNr<*1^>F)yiJ!rOQ?BSrrC!XB=xv7Og#~e~c`36?+wGDe$(t1rM+q zN{JW)VQUn7f9EYuR5RLxmMfQ2-YKFvwcf;nGf!95Erf4LOI>w0POr1*8*u)foy!Jj z|0L|~w-4%?KQ=ILZE_Ck)S|oX56@+kCQ)FQ1ey3k8b0tj&9=Kp)==VRPDG4NKt;2f z2?zT|zw50+_(^>!PUiNYqsWVwv0EBG#0Ep%gsa6M@wFz-3aX(Cm^e zQu)sJWgRr2PGdDOhwjfM5f1B$W(O5O&sdxJO>Yh+&pc+D_%IYxUl-sb!7UrDmhY%M zJxdw&Wz$3?c+0kK>qzG~jP%z5yL@{_96!f=P}9@oi^A;DG~~ub5uSO(`w)03+^#!G zY^w|zpg^d(4kvZ--JV$e6Eg_{*o_nVA7x(yhny9KokOY~1k?+t)X{-SC1$L1InO|Iyg-#h~9 zF&J7;nXz(Lwt({(I%x`g)fs{o();zBJ2gB(r!&q=QUb_iv1Z@Qj|5}QHZGzxV$xVI($N~(_hXf%B7F%@`6FacNwL^x z(UT{8inqV`ZmPvkSLI=J&J|?hZzE%zmSmt2?QXwL%}D_kT!SD_A|x-2jmjX1+inTfKZ=O{YsgQKBR?;J%$s&+n82_nWV9CQd?dFZIJQ4>j?nej06DxEojN?N@E(7GxzZAJdbvGmh&j)_CB-CKl2W z)hv5ar2WaUp#c%Btl4~L+0LyESStVgES4qwyU!b-b0{T`pO1opr0PU)rMtV`f?e$X7`*YyBl1fZH9e8BIkjz7TJg zoqBr9u{`koaEoBUEfm@sI{p1sK?^!|Pb8wqwuNyqEx%bdl{u;M(~_z&?`bQ&q^rQ* z1p;qvI-%UAoNC4v3bRye9mU&WINPzh>PpsjZ60~=x-r$N0E?8j$cnqa>?2Zjz~&jn9}!0J97j(g-DbtMZr zP>WvKJ!~h=cB(g-dU#oJVBSH;f+UMdoHt;ZnlVd2dU&+fjyWrU%?b&WN3aT)638Dq z#VlD&+)#1ua~0}SQ$QA2984;~Jq>}=cwW9rV@5NO+4>~kad#>~1ob4xk%9MZSk2(< zcNdR*|5^4-YG^S@E0*8O|5WLVckV5^H3}jTq0N0W2X_6+(ZvrfB!76x_zlhY&(o@t z0p4J?V)nC;W-G~Zz`PV8%@weTu03!+u1Zd0BHG1+;z1pUb=F!pXd)}~-)1qkjxlJG z)5omwl4-{i9e{*}H5kt@Z?_SwI%J?FQ{cfMy4#rkxxFc!UP#ZN-ws}*^Df)M{+W+W zg{ZgeBdU6h_+aB~**z1^GiOdesQV^;;Q7m0Cu?Kl!1G{gZ6ta_3=7?~h}yEbjFhq@ ziqwo=(i^p9U`e&~i(0}0m5x#%TYi2@wUt`P&}R7X_|i5KhM9LUpfz@WdE3wTYSaR~ z*P*>`Lz&?){Z5Mkx7^#*0fd-GP;_^m`;?f=a>OxOUVEq9(wuJ_ zkSiI;w7qlPc1^UZo7-bs&%yg!x8Sgz$=N&&>}sM^-O_95h+fY6p8FAH<|fI!G;*PC zOP-@I4fZ7{m+d5T0+-9oVIWrs3XW+5n=_a#LIcWILq#L`V6JO=?dxjVxDAs2LP^wj z4+5vg9jdr&%uV-d8~>%wTO9(zhL$aJEH@p^zC=|L_+r3JmehOO&iJU+H`#Lb?(CEo zM0T)9f|Rj_TJ!>PV&(~{a?h!89Q7m=Ljw{?4S4Bm8kzE{fKVW|X=fR9-ytn5r+7NG zfua+2Jeo(=a-w4zhQ(fCQl8afHTg!n6-Yk~cnV9C#vq+WD8gYVyOhD z7~KxUv&o*+?>1!-`*$)=Zi0_zL84IY0sT9=72J3FAQxwVWJYx;@b66 zJ;;wFbz1GYj*{lX1_%==fF#;*AJpAz`UD0&Yc8J_xSzxi2m%1Xw{$|kid&v{l<8Qy zw@gHL{#^S}+G0E%goEw4pz4+_jWTWXZi_?!X>VfVXaC7n<66$yE{l&x2OcIbb(_w; z>dzxd8>(6j&)^Hiir%>Ux@F7(2WY;T`1N_BU|m7`x8eYk>cqFw*nQ|zYaVS!!gc(u zAkV{QmlZu++Dj*PP9-Zf1Y7kcN;?ci5n~Sa&xo?eEcaP_8BIc@^G0P5=R4j6u(9No zXwl3S5oJZQITZdp~9tsxOoS#WAU06Jee))vz$bn!dSX@Pg$xk`rXtTAnA{vMji z^ZN@_zgJmIzHs!CFqtd1ioA_AAGosOM!jjOaGTm~xe}_u%^LaFmT7r%EXd<^dWRY= zsCcOxIxK-Bq-;$ADKQyJs?qtVjRuNpO}*+u)#7og{{79%6F$Z<3Qb8noJ>>~cy*Ou*3!lAg6&>wud+>+b|Px@XdN3oUfBgk zbleoWNF!a$p!fh(Kj*zR)fDrR$A=^>h$mf(g1vIFy@#pcjI|vssI(m|BR9<_I(BR> z-F7e?Ep|bBpEa4QG=kan*8-)$A-Q`di6j-+5q8y259h?e8viS)=bmQQ(H|L1E}uh% zoyWQ_nhVurxhc;B!kr^J^l@*fLT*mEdN zNlT+HZsk?)yGRsE*POZhh@I`_9VvDb_tzZqe%{v)z1{mutSJYbXrNF_G9#OEMesN@6_+nc^B8pvL7~Vf~pTyW0 zTx%2g8UH`GGuz*5@9}axgfC#|FfgYJhGn6_Zeio_?PC$S?zm!Bo@v-ZCI>P)Ln3;zS+KSOhsm*ay_IF32f=Fas%p{K!awyYauL?$7kpzB)E z?t(dEUD)vIxIL*7?1fqw)QColP^o6y{QT|Yf4qH@NAd6KOb&qrA{#MDxTCD%G1_lsQ^abax2FxUQ zwPk9?YlJZKEaXkNbUBL%Z*K8FsGIWI|C}j6j`gpi+3pu8FURNT^=&T49QWI1y@X$_ z+54N|capOqWzTWWj*R_D@M~?ATPVf<3EiI&dR^>yK#hdLp4AzdFU&! z##?GX4=6{8AD%;(r?a-rWY;;(q!eh;{lN-aYpvG3h4wQDd5I?Ychlb7eESSB7lGl* z|E@MIh6~NN4PI)E85x^%rn^8#e>nT*lY7Z^39X^}I~qXbe<$fb-k0ua_9wD}lmd-i z;%^+0lLacWLF(jrK598Z+C0;Cq@CjCC2Ppw&ro&w`YQH*dY!j6R4Axz?J=L* zoM5)ZTvhdcB)8@(rz9-8*J7fLnBaK|G31hAf~YG6YJ15y=!RSXm|9rIg)(tTU$XRP zh_^shiB~vyEOojxY~^myC&zxJ(*G^FzW66mK)F{8$g)Wrk?JfOMe%pe?L0OJZv*Pu z;@AyZvu7@mOQ9i?YKi8(nAa?*;8kSKzj|#&q!q>%_~IoewG6F;+hyb{9PZh>YLGS@Kk-oIFNTZM;v}4U zQTO7~2S`=s+x@;{6x5W(nYDx+8OisPNT#}*x-KK>7IB6<0nu${{DT2od47!Lbkc-c*< zU+pU#N(W$zkjz_97i!{pkF%WsK|ro89nLU{R)t$qZX+#cZuXE+?4EPj0RWUPGid~$jzAPYybqTBC>h46$%@9rq*KKiGE%Zq4mMN&?bBB0ps@yt- zxx5Zvl%CrJLb&0&!6U5Unm>0`x}(`=rz>O^THp{=WzV(K_l|vIkOGOD^vqq~2%N(FqYcwBY@2`EsRxP%2i#RqmU5A!*!5n_~%GmYg z$GtfB$(Vx5KopWsEl^0xztK-_Gj%;osB*|veNZ+tf6S7>i}>Z7M{11^j)JqVkR!gy zUaga??Gj~Pw0C3MX=%ar#M%dGsFYu`HsX5G{4fC zm(64GrPAqow9p5pFLR}Yp2)t}i_GFX%$J$E8n61Ox7H}p*P=h(>(6+?tJeX|Vyw~Y z=Rw~W4MV-BhuFDgnarNxE}lFTFYWVPy>u>-z1N)Y{N%$!aoXvAu%N$HVfd7aG>$xfB$!X9**l2=Y}}|IUk?DW?;< zOVp2&57CfZL=!7^UJWRunI!Tu_Iiz`RFvMQ`y+4JcQ`YT|E}$!JbwE|3Zr=I*?iT; zsw#27i@M`zVh^XgPiliN9y2LVLl zdGa?R&9#4cejjnW8Y7K=4#?MNofWzC zIlkP^LT+AqYsEv{Z^>G$l}fBCqQ5}QKHJ{gQ@rs(!zY`0N2{m{_n-$ur$v|Vb0KO; zN+0womR%R)3TM13my4WxyEa}!sc6H!rL;P4aoHh^ja)oVv zbb@VxrGn+Xn)N6x|H}7J1f%_fN1EJYDb{eeRoLx^Jt`iXUQdITjz@0uI3uf!1OM>p zi<~Ao<24OUy@m-(vbc=oCgeFBkc%4)+64r^G>_mOdmqs7-}zA^Jy%lA(;DMGa_G%G z4R^z=ZUPMdf0oDmUaZ@OkBC*tD8e`9__C-FWuU+IrZlm7=xJv871FnK~Lo8*n< z$KkD=<>j^a3x{6vvM=5E^a{*6Dq%VPcDldvsdu3YJG(C3I4)d{_`JKzW!&-F;bxFl zRF?3sfUM_4@R1*(*#A8b8Hm@qcHz&TG3;*43CMWgj~MNLXFwHBPaA=`wf~58eogh` z3?J*7Fv#WsNVb1l^{-08|2w+Wk2n-evHVvY`u{KcUs>lf$AsT9^Rzlx91I_n;%PZQ z27BBxyA@jVSovC86eSTjkO5TdzmG&^chp~j=PY+Z&*%3v686|RNKjzMDIi*R|MIVB zJtgSgQ{W)@#eIID-C?9G=k)$$=PsFNe-pU>W7HSF&W>Z{_jGj|O@!_^&vGk&^1Bss z_+O+r!?S;W!tZQ*$z`X0JeDG-TV=oh5L1E*zt<5|^gYWRZ12X3@c zP>pVK*Z-cuzN?hob6$VQU(b2MTW zzh|(Kr82!uU6uwS{^a`%_xAtD?{ggn({0a=s~`L-kUz*RT*lOWhO~^wJUI6ltukZ!i?`Ue{U%%% z;`;B|hAB@T?Rmt9O)9|meWGFy(yNHeqj zAUFl?7JIss&)v0or)=m=BM!E|I;GNvb;6ju-V1rfM}PQ)*$kU6aF9HI4>BisIR7MY zP%Q`B9(IL$=;i&JjN!+H#cF*wDiRVP5b`yQKLthTN};2>aHkL`wE!fc6p7iPEqj6~1LcJH zKOiTe*WlC>W=|;UO(kN!^8BZxpHezN>dZ~9byUn*yX3t`--L^xzsS?KrPSyEdWWp4 z%~m4z90wi&KiLPt#}4N!p6X7+$a>5@g!=cToZACgRr0|2J3hXSzrC{o-_d&w^jX*{ z!I~1U`mU;i%u>kN%YS__;XK;n&qH1X5Jh=43N}klPk!@j;B&wt()X92k}Rs{1F54B zz>;fBQ!f_n4XW4FF9e7LUs1;(U)a^)RLaJ(Q@~n0Ui%ZE52h-q^d-Y_1jr|a+_9N8 z5xsjt|B3Puz+NJdW96%jTu!m8tE<59im~Y!e+E9kqDO>`nDY<7r&D1!w?o0zLxO_G zh<^k6cd+GmnNX}Q=N4hNTLN@ zLu}9W-jCyUfhH`>c#>Q=;(U3nu8Y)4EsRbwXb4 z(^TInaPrSPecXyT*FPQ1<(M712LdSLFPVaKI`wN#9}D3$dtbp8RX%_1*MD>>G4N=o zHLWBaN*q0O^})3cov#VzBLV?~1wP4ly0{EdyI7fmCR7zDuW3!23ftci&)>G>8&!6^ z1Npzj55Y-lZYJ%J`P@cqv*K9!Z^Z6wB6hNG(vDuG*FezGK_1t${s0AVNX1jhKlRYu z7AM{=UHARv^P#NSq%fDrNL_{*aY$btmkkUy>c4@T4&{#kCH{?%Dc8pNdf)}- z_U{t*1Npnwdk|#-e)}X-*LmT)RVSY)r#(5wRJ6I9uY*85!gv`no1PNBoB3!A-4d;R z$PAeMb7NrVu^Bgk-&5pF)-g;; zK(L0S?=j5y^AP))SM;blik+NNnXEXKv}kYYPwaor4M^oXU}mkL6Wk(IVhV2V0g4D8 z{{2mP$}E3xwVU-?RH5HzZuc&wV>y6f&rF1iO0CUw7J`;(_*HhdE35}n(DO$S;~zH} zXWqF**!HA{kym3d#sEy-@siWl#BEEtteeT z1W}arUTqd!G`<6;V(|d90$Ab9v%8ILAM4tN)$c!;ey~Foa#Kh!!Trr}v4!e&V9AG= z7H@zHze!i7e(R{n&6AqW@_Q8x9|7pRV0>qXW0%04TE+$LiN6#r@sWH0Vml3{quWk}a6fRYS*!q8a`xzNADqdEF%_Gdnwkz| zkzs9pbX7YvjE><0|kp^w^FC(#iy2c8bqvH$MOd*Wb z)(lrVK>^7Cmh!t-5`*NHsv2H!h&OI6?`%8mKKSQv9}J5Vw?^8dsxv{1@;LZAk@-*j zgt*d2^97Z=;zuRpzzQzpKg6Hy^v<6&Y}NecPf%#tLcd)A*A5Z7V@V;-TknD^Ahc}8 z-#*9>L_{0yUq1%6!vPPYmkx-cF6DP`Ed)a{W9N_i!MA>q{_QHGz`-(socFXehO7yS z;~U$^OK=6R=xw5+&lXS)J))ZqQf@16RryiP2_h%J9`{R{FvA8b88i@)c)U32>|Gz z(_3z@w+1Y^_%zT{=(_)R7E)Yn-aub!*Pjc*;EYdRUfL&Jkh9fJPOK%Pf6uqccgd-Uso zdjDU~K(|+8cJivH_L*CiO>~tPJaq+{I)i3p36k0^vJA<$O${cZs{Zor$6;|(G4a3Ht>7jT5(H_t&%h+B7XyA4~$ zfZ9I!yV*l7g242W0K|u6+Agn$9Z0jqVNY&T9r8eEEp!~1>&bS`m-lXDp9O+qj z9qNFi0EnEAS^hDtw`-jWnA7|2cvmUXLpI+w@xe#e=Lb8t2H`Y45E(E3dT+K)UYSah z7&iI3hFv_f@ptirMi6hd=O3;=7p<#*<&@f%aNOox07>qDa6O=>j5B{59kUuo`F# zQIfi>0D^gPAVW=wX+`Qb-1?t>nv1J;13h2bj z8l$}jD^0yswr;)o9!9;^3leT~CY_(7JraQ)pUlov_bt}u@?^Cjr@(hi0B$u2bg$%< z1{P59>Ri9xaYCQ$4ISME`tT4;UcBCBQLZkO*5C{Ca$lki0_IuTfF47K27vh$Dm<@o z&&mHjmF~#XH9E+gxYPUZ!RJIp3qy5o0|A$ia`R={dT;wkxjh(lXZ#hU9M1Q9FX{-d zSf)lYN!yKFkOE0PMjLhZH-H8Y!wS(-=6L`2!z&Ggn&F;24srLFM&olDI4WP{^%mBg z0LYWizHUJtQ(9;ncqiZ(`fAg6p=vThoJQS{z7>5T=VneKi~QyX=F#`-J-0wh&0zdx z5|5pwPCNCkYB zbBBb}^aw4-0!R2BA9=?IxdV;6>Ra(>v3&3kIKsB>trTpe&wK1G*0HAj1&`do769mA z%%W)fmtUn5;#&?coYpr0d#F|?CjzQ0L$ukP=R|+Zjr9V0EBG zWU~JJ{R?(*h_Z74MA2W6urV|h19c1_l_tgEYP|H)&sYO7+w_QiVF3_~z7_Zd02hl^ zzt?>Mn^9iKkhFXAb+}3aCcaKHp12AXLmY|>NUU$imX(eR`{J7~aCs2@MX_RZUXE|qhNqF z*zG55gzw+}sa-7-f5hTYal>b&NZa(n>x6N1xl*?R!o z)Wifdg%=3Kx2-7a1dBw!)n)*dANM@{IhQAI`1C)bU;o_MeO?oFOihJhUfy4q%-JvP1lM|}GPq_9>W_&F!$k%vGP zr^jj^7`BxCK&O2_(8-PgR?_1J_6S-eDP`V`%-n4(1ga8k$mJa)hv74S!wpFs{Z#|Gp2y9RR{bU*cJygh_+rB^^bq)b=6GU zfqce}?3bV#fD>}WTQmSwSN#m*cjQr#e9YXt-NA0sfi1aTt{-_A2*6RYXP1DGa1G*w zJ$?=37Dz!KZOe=|#hC6qJ_qzYO4xR{NZ8{B)|1@51@P6xVXyY=Ai?-)*hri%k6b)> z4Z|>LiyAdIcgb~2&%@*f!bwc zceozdqL<%b#pfiowL)xj)tCKielmRe2d6BJ1m<#mNyDmtNH`AEgFXNB8uIm-)mtDv z;`}l|^(}IoI{nkD$k|ZeMst4X^5x~!zLN;o+WZa>{f1Z0c`ZraLeWr68z(NI?r|{X zp1rd3l&k&j4E%GjWYxrA#`^u`0C~0~-;S8}T&Fs-2F`Woc~Z>X&2%H)a`T-f&fgO$ zpYG1KmH`y!O*(M429cKQqqAT??#`(!c*nWXl)=Xu0k8o?P*nI&hqjoksck4_-``j_ z1F#-E9O_oL-0Be<@!qja_Nd71NUJ#T+6|x{T6&K|ZwD+%pU;=m+d_N3-?UyE)|015 z)GY_9yHq;DIkNtNk3&p{$Wl&N`BLua2%qI$6Yo*%V9+%l1E{&Bh(ldz^v6v|#34&% zVwuh0$89zdk=w18O^hSGJ>^!C)rse>05x;XnkD~*{oji9aIoG%`E^F>iT${PZr@O% z+J^LNSrOttH;a;7W|{jUZjN_<1yCP;$Xh$r!O9f+&bdKl8en)LrA*M+buAi8wss?q zHBIRlty*myxT-{`pT|V+>{6C3&LZM!KlA8?+9h8A+A9WGY8FRIHWJ!O^pSH{ewyYZ zE`5&69u;w}BU)6AZ(x_Gi9jy>1BsQCFqy0ZnI_PcN3GJ(M+dGYqPw1F-ZrRhb%2i} zrelSc$^wPd`G7uC@kjlhMgd!^Op+ra`L(hgKRme|!(wdD>TD;xzitNL;ud+uw_#QU zxR!yo(e^>ypU*{iMpzUa`S1@KmbeKrc2Al3fG)*2Rj+d4WlOLRyn!oeGl4_iD~xIW zt{KogJQm!PP}U(*SmchqXL1FZ`GgJk1SO|AaFrqy9}V*bVLi!V@W*8w8VDv`-stGR z^fN}UD{*N$+IpwM9T?N&9Q^YqIl**+)j9;w(Hg}m03`syhwfG01nqwGrQ_)vwK&Q@# zA<&R*Hbnh$VaOTdTx>sCN>MjMhLAo0j#w11c?$k-Wq0nYQ)@l~m!RBOQvS(_39fd#qG;nLeuGnmoztQV1JbMSj#^2CHA{`r; zJ2XgPcKg+pMTs7a@GDn@Ks_!`O{V$ zIAs7CJD|8Re@*1+NiHCSgOsOLz)I$#-NSP2LcwMjQ)WUN8c|&H5jXT zV|T)0T$#?sg1A3(oE@jMy}4nO$0a%eKJ(tUStWOIu7~)53u@x3ZmmtK<+qEKOHxdO zt~_Xp$~4)K&$MkFumYn=fQoj+NCYZ?)5`L7df3pYsbPTs2yW{a!ttNbez!@gl?jqp zO?woqI;vbxnUH7EX&z|TYS+#8f0E<`L^^YJd*31HPo5sm|AuDe?U!7)3=Dp`@Oko* z-qfJJxIx^XTeS3inv8^*ZUCzi;GLyGRq{ag?(HZe$UQ94PKQ zDZVjk-(E3;mo`fB9}F&M*>-iorj!A_7!m+Ss^%UNDiBsZ*5n{yFu29&C}9%vLLzS& z{@DS%g2}5#R@|ZEd$Hx3r6wW_8qie7vhcxRO=|IcR8E)&Iq_Tlh=UV~bV>VmT|`Qq z(Snyo^hfyuE>PhonSeHk=Zwn$CQNUNj@bjkEYKF{@N?2T*ZP;&zJ?X)uA*n?3q!4N z;AvsD!zqz4u4-4xWZ2SPo6)jrg8YMg1J70qDHC#?y4+7X9YDE`3m71iST5vF4&^OX z5I}4I8C13Wsk;=a^>UUvE1wtKPRMdcd(dxa)c&IA3FGIJo2T0t0-_B<3WfM)C{1t+ z!?JlB2IY11IKz&4?=LFe`6>U&Ih_&UmK5=j3o@nmkc>0-dk54mdX_1-7SD4+a&RV>Txj$nq&o*zs ziez0VRa#+cH#jqxBUUGZ>qKPAUP8VYe&L!5qRlkizJX@ABLvpdm&8 zodo*<(Yi6YBxz*9XP*@z{0lAYdN230`pu54Id}sUp0**j0p0Uio&#U@x3-~Tb{@#( z<8a&}_`<^ZJ!xWyZr4Zmb(g_jlQe&c$PpOe+AQjL9uJpn@QAxi9T)ee+?DeG#Q;nA z17v{p`2&zhx?~D+5asE!@tYiR9%nqJ8$08{8@oWXZ#Rz@EjDLzoMknuWl6Nl{N+)j zBe&&s)qSVT597b-BJ{;wA*w3A0;9B2U3 za+r91-W^LYwNH%>o158f^QSn*0oU|M`E8O~h5S2TKc;W(#MQpVgs*D zQ1&`^HXr^X$!E|lZq?`NgQjC7{I@jrLW#VkN8QW-CKRL~CN2Ky0TUA)ps*uRuZ_33_!- znF(YWs^D?y*{`cK*SWKU-0UK}hLzKpFYY!9M24Do^#Q6ivKuCkJiML!CcLDKx%$KM z(5YypGth#;Wxts7^G#{C0D3gE$t_dVZOLP*Ow(=h)bMHGa*8s9^V;BINI8>8;zMfi z-z+l}n{n{(t}^hG@p%zd5Ag7!XXY4TUb920mHf>C&j!XtjZ7{#`=nYKWul4X@jF*t z2(peo>WYBdiIa$0!w3E!+TJ^?scefI#&N{4fa6#gMZk)R0#X$sprRsGP?}T~=>$Xw zNDDd^tf11QiAe7tJv5aPr1t=!2uKSh(jlRIYX|V^+`0FA-uJoh`D-Q|lYREtW$m?o z#VEd|>1KM>J!Y4=X@M9DI^#jOPnSpn_*~KaNjCLR@dOZ-z_mWW|1nQf%UHnnE@Bc1 z!=>qpKaDxXZoJieCfe{s+ax&U3AbpzS=_5h&c8W&#r7T?9fz>y^(--K;W3_;p`Ugw zUqP6GMBA)D3#};h4j4YkE=LOWo6y`{G0On4&B6(&iJ>iEMm5E6&@lc(WK(H53!9o; zK;DS{aQCB@=^@`8CzRcW5m{-vfSIC9~}H!viQ6rWwaGfI2c5XRNq>lg^pI|vG_Tp`sJ8q z$IEk!jPUJs#d!87%CB~EOq?VhF8X0qR@rSl^N`WUnGr;JBXYV<93}x@L8^^0Fs9F( z_KmpZm{{pJv)J7IHu9H466C(q9RdrxZ!QT&KoMrj>FR-)U^c?H@v{49d7a;XKI0`4wSVJ&5y0e%uTiXA1O^Dvih}&Q%df zoF2lUrYJh(`?z>g^DTfa)5ab8^@kjH`PGW}W%VWuE(eD^-c-zpayBQ6V_Y7!MQw7SAO-Ml#|nSB7eS<>NWXf%VL*0*huDFt#~~l`}eu`)|2P!CSGex@%qyL%vjiG zuV+Y|sor(hY%Sfe)%nj-QsFosqjL|k={U!_c=<5@KGCF0*Nl3SQ+=xjJJB8DQg>t2 z%y_WUs=}|cH)izzoV+jc`y>bmHo4fD@-)dzWoS74U6z4+I%=fbVl!xLf?=jdL+8$YB2~F?wWT$lbfZieG++N!R+=OBC`qV4$ zuqD$A4aLfB4e>$)bn28wV}oSh*hyaz2M=jPX@28s?VyR7gk21-uvI^|AjaIYBLBK%-$^5=Jb{dloK44o5TlA1LL4r{6# zv8O$f9j;D}>l#`W^`sIGEm2~$g=co+w9&B;gL2v9Qm-CcIgLo5gu;!kke-H+IQJfu zTm&YFKO(4(2lAuIQ>A!x0PBa#{p zHmuA4fuwvNO zYZM9d{l>8&V_t3=hf4%@@|0#8g|`CFE=tN zW9F3fNdx^vkvlJjEuUA9Q8GERuKG1Y!nwSwEdSafj*D07HbC`Fk`1XCqpnh3qi++T zf`;{bda zq?%>;84MIi2zp(?Rplv&VY_Dx2Zu$gB&M}6nXj-?hy;+H|Bc8)=E`8vvK)&UPJc66 zc3hRGJD_ZSL+-_f9lFLkjyi4ly!H+OV&HYeRw(AvHDGXU-jvQZ+WzqLRB`5Er>PX; zSzr8%g^+&EI&$QNilb*QFE%pk zNF-+aI}gKBz}&}q9qCJ~dN42CklW*zfz=#*dBq=dC z4@e=+nB8+qx1>5c&P(ZX^k_8}1+_!~QKzJ^VHH)pSjcqU)V`e_Wafr$O5+sK`}5?& z=gjG7@p(c@xXj?xIF8fwjHy#aaePJ0?bNk$64yw&@QY?^Cb5YqRM0Qh>bLLR3}n8OjsMb@FHz-Shd zdJF~;lAo)H4fS*?|So`=9b^CJIFLud{a8UE$_z zIq#YUG0SU6=QU@qu5WHP_NDR;^mp{&^ba@0R7TGhGm2~Ggus* z8@|@rH5s%4b)-p9(3j5U7#t%+P_!Pf#?h(ymx+qxFoWURDf=HsVoPPFn7Zu(ZpX=Y za^j6j93Y7Ow-;AHonXlV)nBBbtdOFP9h%!ZC7`>_!BTHs+vm=U#V$zJxbn-x=R1wX zKYAw+#Ro>U)|>C8GQtJziJd~9L1|GWV8rV3p&@of5(oEAChO5^QCgX)Hq9MVZy6w- zrO`3{UXc{m3CX6sj$~z{`i1X+;{?P%yy@^`MAvaRyMdFR;E8SquXcM+HZg| z?->g{K901TS-Go2QO$y!bZ@xrm~~Imf>z!1?eDvCLCg326Ypg}cIxb34W}yx`t*CD z#OE;6)?IwBRxz2aWhij3Y~(*oA`tcdA?@_T@C4=XAGYvn!8LdODmW(y;E5&N?MhzH z#P63=A3OK%fGP%T^FM6fKRw&&A9il72qxv5nHQKL3&{ZE{;wY6Xc$VRfF7BM=q3TK ze|XeT9Nm2ivvqUw)*-Z0k{K`YZBli>I;J%;Y^Cr@m0e#i7fWYY7@NnPSND(XfzGu> zf33f`^68}?KHcihrg!@zLZB=m)jxM0--Sj8licQn`%G`YR38RVobsQ5t#o~@6eR9f z8H5Q?3=mZd<96$~$++>*xy(O)x$~EFBmm@2UDM2S&YI`2B&Wv@G$k86r)vG<_f4Oi ztiK##-CaCouc;JxBDvT;{u?_|0EQ~^ZyINyhx@%CBO0Z>KqKY1U{qV*K8YLFT^4%} zXU_G5I~ZELTWY0STroUW zL4{Nw+bV)3A))ls}JW9V#DL&%I5$OlBl`V z5lqfYKd!`RpcKJ8TrE~zjrp?kCW#5<6vs)*lLJew+`XRB?e&ilN)lIa#W={&|G1T& z;i)3A8zsMw*I&M<<^^aoxb7Tc^uEeL^hWDh;49BP-LA&08pf>VI6Z=8;K=@Vs|@2- z4##?p(DTn@HpT%YqhP2tE9D)4bI?_In$gk7(13}uy)CUa?n;W>J)WH=2>vzOT!mLx zS$>?Zx5u}@7Rzu3z*UX)lpSG-Lr{TdJ4IslvN67l=?vrDyCFk>I|Lc~cwuaduu#%F z;vHSPX2We+OE>-g7o!t5#=-w?ec0`esa#+Me0GcNP*Y9LXfgXX#-0cOogeRGhNfj& z8wla&ZaWc|Tco<_U*q-AE}Oo}w0lR6cR1T1=nA@S-235&+~F^Esph0gXF%yAR0XR3b@K&Q4P`V_%Hb^^Iy$!_dSgIUkV>Zz&mj~^Qn`^ucY*lE@ z8jBAfiQEKPR$_#~sN7mkrc;D3PCFP)*e#}Wy5~>Wr8dX=eW(pH1HSI@Oz`Z4gS(FNwUMK!D5PT`ziUcDwAJCuUA5Ap0eLrax zk{PNEx>KNOU0Oe!{hvSrqE+$sKv!0beV>5TU;Tt5^_4nPIDzK$2mx@kt)g^V1%4YQ zmtx)s=rVgG*|+v9`4#!P7Wj~Kk0Xx&x)bsUXyA$|^G2cp11{BG<%Hyg*lAb^{`N$f z?$_fw2&^s-E+Q1u6m{q5c&N41NynP??0QfY0eQyY)XqI_yvGm~y%Vrg<~}-fUG@=y z>wz4{R5b6cPq5TKJ|pl0THxKu2R%^JNEy7`6fZ*%pUZR-ZUTIRh|K&Ti}%>vP1N)N zW;G=ch_UIN#+%ipvhYA>y%c4X%xZ!cV_3>8a9M&xfb2AXyVqgV_lxU)OEH zoo&&gc$ZTQN42J7+jPuVbEX0bZh4x+`fQAH`jE@PlsMH~kez)YcH^@j(h^Hoh+J6v z8q~mU6okm!&7uiczXe(UI3oV=#cKnlNN}dJ{8Ys_b?sX9GsNAgR*r>!wQM5`S`n=I zIl3A*t{zbJ)n|Sx{|_DZQ0YroHMN@bLDo%gwPY51H$%whz`LJ>e1JWhjC>tlIs~c* zOGmV1o!=_C;7>1SpgzEj!I1N1gVo$dr?q8s8?4@sD$PG^q9LuY;>klvkcqG=09geM z!(9AHuY{z}Z9tBML#W8?A- zzsuYfU6X2Pj;VeRD}Wv7zB@PDatPKcZB4xU40O?f=#Fpi9dLJ7+2Bj=5za(X-FDUc z2zpYSavGW*yXwNe?c8Wh4RK02}9U@kYRV)WLzwsZZYa-Q0g` zd$MHo-2gv!DXX=jNMBmQ6?psZyiZ@t4}4h7bb-y8|N9mewF~xx#)FfG1lz!*!a?52 zO(9Xh6RPM_MLy@s!)?E;VhY>v)5tNjCVOoHw4ycilTaQ^rY&u^qEFYzH_4sKoh+NssxKiGX8u?TXdQRb!(% zk?N3D9zUl2>R|jV6@F)z3%_gF^`=Ys6@!!OEOcFniW$}#N`rwYO;@?5!D&d%-niDq z8)?Wi;nE>bH)uC7qE-1K)lx@PEqnEEUpDeXwe0bgLUy_M8Qfvl zWyyZO@3}$Jil_)eEr@SASse$$BISaPELfR-O=38m_~E$iko4fj;S7cM)Py+s>F&Jd zO13K??TjkIvz$@2G6=abA#qna{d@#^ zLe&zIgiRk`qa6(}!evK=Ry6l5pCB|<1V86xzi)sc`Lf8-0;$4~bI)b7ijLIhPkZA*rQV_)BpoYaob-f)U3&HfwC7-* zQbs>_82e1CIglPFXv}wa^h8_YROzE8O}P}yJG@mn*aFAEOTtpA`5`XNseLR?(|3BO zO*%rYA~>T3Ep?%!>i2m=LwFL|gS$ez(TfR{)pqyYaXJKTZnOR-x5gbU zR(>}+JpJKGEOU$OENt~MPnIWwE!02l!W@r6W)SEW<2J8kA*Ihz3Z?#7&nsnwOlobS zbB6+E+-A!14nv3Vdl}O16)huQkCF0QI|V7f)htjV#CllzQmi@1Ex{&viXP1!)p;n5 zv{KI$Mz|wy%II3@Iot!v6TWPNj`|-aL!Pa8gBPhsH)jg0SGJ0 z#gIgz6Vw7jE7)!Ob?7Ar!Y@JqQF*+p(GyD!C5IDv6(zB5x-y`q8S@nrN@s8JMFrH# zITk7BHi&-xT5M_q^-t32Ik>%eK}j*ngHebNZk$#hLevRC~d&MsbK9~f#QLBD=io*Uoyyb>^xLS0c?u8$`P zb4Xf6`vEl8VEov0?cKFZFZg~6R3p3qNWIzf|M?N?KkUV_-5;K$e?}e+bqP*fHs#*- zMLvq58~CysHU?)1Zy}{4&V&sJ6p#NiFzxuwmO!4K4e^U_M1Jz~QE40Xn{SRi@}Rr4 zQGKxqGj45}HMt*$U}nyb%Qo!LlEDos-D=M?*k^vrIY!$a$C=zMmz@9*{Qz{H2neQ0 zTV#cum$J!H#sU5H=Lqn%j@`*#IoE)nH*HV#hCP57W=a9iCPCAkCdx8Yzyq0e%ep%d zCGVBD+9Zk~(HvBNVpq>Cm8UzU!x~{!6P|&9*lIjmYNaG=47`uDP4&=QV+Nc;#|z)^ zi~M;ZYM35;BddioxW&@*zv+aHTXULn{oA2aLC4KRSaB3dWFh3R?XuyFkOP8pF)(7O zCWcz}JjbD&dpM;Wl?5_ZZ+g!WNa?ru0f3fXP9x&6$QW)hy!|_btg#`nvh6{(l@MUA zs=^-lY=}2*+m|owQmmBHLq`QbIS=y1X-c}wcViuQTQD|4w2*WK6L!`W1!5eHAneN( zBSWOsX9rh#*%UYQq^pf8yEL~)t`rcdfB2DL%`^OL#Y)WEcww^FK-{sRW)#}t+qN|_OS#7Lg7F1Uy-qAkr z&aq7_)>c0}9?Fe&K?J%yF55OY28Xty zje+7APtpoH>);SVx$}~J4Shhhlpz`1 zx?j+q2fw`1s4=MNIKSEt_Yn0xoJQ=Dq{;7w0Y4p8?x+)l*GaS_tJ%%HpA?PDJ};d4 zPA~-94_u(~ec6Hd+=bYU4WUYoxL>9vZ^!th_wGs=iuEw|8TFmcSnB+PxT8nn{*s$L|c_TC7G#Sf6_T%!5WxTY3*V zlvS~Vgk^@ivSYu^7yd|>KS5D60vrqNn95nYqMun!%*^0stfeGUsUgF)Z^K>Pe69LZ zj-*vCdk`g;im|ux@g7G=aF4t)9m140y(7<9%IT!7jt+-P8st{~cC@wTc$Uz8EJnTq zf(v%_wNVRcR38<>0yfcMbG_^!e`uxA15gcokh|JoqRZQ;L4w~Osg)~g_-lLEP7=iH z*NMYfKix*%+Snw^V$hl+!?B@l&HC&~fZ9V(PF|eNBnm%B50M@BNe?CQ50VD+OO+)q z1Iy`7gFGb_`Anwkf=HVPbeHnv5sq92qtot(hix-$+9vj@I zq&uM-HCTS-LHfv|RlJEL|2Ntp-E3MHYlnTs^>y%nw27Y?2&uZ8ANUCor?^D=z!NN_ zdy8(yQ$U=#Y{F`QTMtqg4!+W^(bFxNeF;Y=)^;$n*(K6;Wlzfn8|TN>TUU=hilFms zVtT83`+KUPEiL&*Ab3p}oqv*i4D9G;zSG4vt>8o?S?G@6Ebt zBK29i+vl%89!t`VH;VT zxuxp8eZ|?bLJlq|sR_Sz!`aWs2_bI$q6gb220}lCW!vnZX!c$Z4;}5di-vzW)p2k* zV@jWRns^Tb+3jap%c3EFxKcUnTdgBp7Z(`(_rtiKGj5rz#U^X73n#cH22XxlO!2_f zhT?VJFkh+Pk%jVXoiHlg#Oze-fRS&D0_QNR3eoH zFo6Me`SWH*4m(h{s(Oe!>K;rKZ}-)kme9`Vf+KCJz4D`P6J1n+fhlHf8q!60gzB)HY__}QLbV=1~{bFP#9Ik~o<&l3sV0s*q6Uk*}&=lp+_{4Dil zGwx05U^~%nj@V;h*^qP2e=yv+|8>)x1qv0z&$r}H|MVgr-t}(9fURCRXxTlgke>J< zxU_di77~VFA#u45+w{>7Q)RXnvhd1gOSJ+hX`DpK{Xd15v+!^0p$g2%!oR)!g^`6{ z4iQg9Z=TNWi`+(YdihhH#8zb|>G7jSkAnX{5P9O?=f=<5DpBH z+^h**jXOqux-9c%$r|5QvximMI|F=;=I8AQw)xLC5Tob7Ff?4wpNroM&f&)BxbYL! z?J&P1CvBu^Y7>HgZL)T-Hq-si5(LkI9l#RAkc?fYC~}>d@6#GMHn?H{N_s;oMawwV z8H*c~rGV2kLn|L;UEsL%8l=syS!5BE$xyHRTgJkyrN&P+A5Z|TCKwqFkXCOdOzR4b zgyD>u0=tiRCq`2O)!k57x-)!gHLlp_QwhB=_&yJ&|Dml!)?o@>a)zXdA1_9p;)q`kIsY*pfasi~tN3qxv}{ zp#AyFlJ(7Lcre_&kuNooYdvx9ctCnj?)9>U2Q7A}1oTthaSN$thoUyUnqL9@C5PLX zWJYLb!souHqaN3;q@%`zcrHuYJ`)@0l@Tnn0CbwR$I}(J$#i!}s)E^M+8px7SoGI$ z8v|-wkgP|-KC&q7T1Td!H9d3%UlxC97AZj@p9`EA?k{ulq=yq0Da@7;@)PhPGsw8OljHbavHNZ3;L4 zaDCZ%@^Qsu3?a3m7LX3@6KPHdPcE2u{cls7|f2nqxW;k_(qbd9ejp!v?eRqhM;Gg z>u;%ulzi=zDKp++>=gVe&=;|$FDsEvslWOqUB4WmmrJ9#=t5vE%CGGbGA)G;57d(Y zb3&;FzXK$E#wADH7%XUt{4HOT5L$=ZbzY6Ipf{BBw6~SOp*`e@8ufbuY zlzMx#%9{dqVF9YReq;%01mu+=gFc`rK3AM3h2CtP1s}`i$|FIWWHajYm55(v%tumI zud$x3xvg`8G3@GN$%$Y*o$6rekZX3;0EDB5-@0{GDu3^x`d#8X&pwMFg|~?Q+mwb1 zKql*1sf4WEI(<>h7v`K<)>{B)0Bqy=g_e8_C0(|@uFr4L)toeD-uZ@hZ@66j1c3w3 zkTS(SYuw^H@+oA#M%2`DBb!I#4lO0V`O@i%R3Ry2s=A7s>#KRSyr(jx@Vh)2iFvZ^ zPn8K94n}taq4hPKAoUL`-s4TFT6ibc`EYFoFs)>{`7JqWlFIGo%wpPI8zOI}<52O+ z2$Lv$^=o@JG8Zz;rT8{hS# zx7VcU{B{p6E{9OrISbDdhd3Y1W zjuC)51+w42>$VR>X5V}zjtY7nv!=-Y@tz@a>DeJUE##Mzi91^$nA*M{M4S1Alp{a7 zBhBVK{U`LkYqmkwvx zXt4l&^Ck!R)>}0_o5;?O#hl58ez&9Ys_9(lxO34H2F@Mfy5zWWyvClr=(=v&l3;M9 zXxnwQ{^n8v@h?zR&GR?W^5{a27eNQl+C*fYR2*pof#oTcE0l6Qu;>%j4Jwwi>Di7D z`V%k3uZr|NayjXx;YQKOrS)ZV=cqf{lIJovWRon&f3^@aX=mu6#9b)V^B$|LzQu?m zovlzDIes!V-x9y6?Pb?RFHT)w?re>|Y|h{?G0E1zgRu7VQPJ9O%HhAZ;3DQ$pPF5^ zNKF>=3mv^y@pspDXeCgx!0j z-uTo#(-9Znkm+__c9w%K!Do690~urLypn^w!biwqzAOBquj^mS`wqE8`7{=wv2n9> zctcZv(Sin-d(43L9TwP*_cm>64={>x5@%jmEK+i+75wCUR;Wsv+Zs z`Su6z)1t$G)Olft021grirt|ZE&WR8J~9PcV9#Af5DM8}2>VQED*g^Jyt+GYrh~-w}Ic0g~M@{&6|_$&=*u+MFAt@o2w(F&lU>DI`H&?C$d_v z^mF;Dsep)_0plR6z8oYIBeo)~SBr$sEueoZC{~;usB_MKvR_$i~FsN0qTV zM?0k6$6X0srzGZ!qQQNR7g`|5(4l zWOmi+?vP5*V0mWZ0!%o+B8^rRQ^wDj0Q0$x&%4UU{n4Y>=O?q-{(2L?YYWzS_ulJj zDf7pmbh@g=mhmUL@G^e+pq#x!jd0{x`pEpm$Yu>u!n5E!$n}&XmGX!7IWv7tV@g<$ z^dtUUjP`rdg4Uz~J0a4tO&e&2_vgTu zAmhYg)1!+VfE4o|yBXHbkA)=F)6zK9`ZOP3T9vwDH=pN?_h1Nbq)hWNh6Oa_r(v-fE_rQcb;|F2pEd*d2cTmYFlfB|zxcz8 z_n@o#0rX@o;J|()Ph=HS?c==IuAPmHvHeJMCxvkAR|de1iD~nXGv}^j(O_aeL#L@7 zPaj6gRse7VNm3YBu?S$Fe;Lt>ixI+Hlu<@=M!GbarL$$j9x)yr=V1bhOp@9Dy1VTH z2U3;g)k-~fL?Z#ooFO!3|H(6F3sc2=n=TJi>&u@9XN-b%Z0cx;%r1j&H!RFgaE`uW zDhBg7&JXlmb2|YuDGHFssW5C~+ztjf^VDf&?fnVYo=uWEZV)l+>SMPvs?mY=p zc2SEG27FWbcO9AEVIyd1g~E0O*r%vVuny_(6g~zqTYA1OB9ofJ)K?@!0&3`ODeyN< zD(BiSV)13HL4uQq_&l;{oGFM;RtO4_L5NK4U7CGTY8UTu#J$Y{40(mhqN8*~ppQ!4 zCp7PmW=&;_wi*;>mZ$IDyEj5*Uhe^%R4)~rW7EAso1}lelB~qgQoVELt=$Ekf}Jo% zSnRk28W+@AUOV6CxPbiJ6#-Q*bdMdlk}EJB|$aIkLy3jGHu2?meCJ;#l{*_Mr=IbMx`xmRcb$_UnfkGr>>$aAfKt1hH}*U#XAZdOKXD09#G*`O}hH=7tjkNDHN0~ z!EWCssIV`Mpx4Z8`!c`2%+9-77m-LODYU#Y&o`(O4u0I_-(A?V@@varo9ICH2{8cd z40V@owbX7ua(OMh*qsejwcnW4-+nRqL{kcqbZ9pVTtffip114<+i%z@gfqv(7ln~> zQ@(~{t8cA(TME_FyuVmc&|_L0ZM`=Q;e0DZYD#eTF9cDEyoF?avh26CJfxl>jD1>a z!Y=ZyHb)Ut_Z@%ZC%!cJ%Fy6jb8buH^e~y!?%lQ1z)jujYwkl~5yVBW<6ZsU7#}+Hl#O4M1+1(#zPVvD^R086yrJ>bP;+k@ zNiT%~a;$%7g87xPL~fR^`hDW0^yyi^ky`*1d$Ut5&FotbC@aTl^6bz;=-;=ZQ5>76 zJq|G9fU`$gQYQK9#y74p{#x?VZpWAbygVva#5Fm;bym!c& z)3HBfTexRv58TdVcCTeK<8?A(jFqqMLqdj!j~pr6`0gyWDJjIEKg5kk?x0K=z-*c7 znbY?Du;dyI>PPhLxb(44YH{|9?h#(?bm7IhKB*&e-2e4<=tZlw%B@$QP>~WO1Ec)b zQW=fkHgIi5oHe7a4fd`TJ`%l8FhZt-ob%qm0=&qrr!7rZ?q~&$lOp}id^tqaDYx2> zaQV6Cu5Ia7JKUC{>As5bG8(0D5`Uoenx)K_KT$BVHcDmRIEW=?zC&!|Q*FOfs=2J| z*D==5LbvjhEeJ*`H1Wtl(6p4xVM5mL#6krEY3_1rX!EW`*h54rlrvS4rmkLk+j$L> z7hss}7BOK8$CA_iPH$@leP*OO413GI`ViOpS$zTI7SkDDm1o`pMsDdR$J5cu+zjO_ zP>(=1kVxZhK;Tx+5Bd$<0f_`-Y%`X?aa);NlHt3<2mVQ)H=fGG{?=2LwrT#8Ei7$F zw+SgaG!47Ud#*@H{N(mD(17)*E1DDs+dPFj_4taRDm|-aW0MYkwcDu|@aSZBg*#RveN&LQ2%vf5{m3_Gd9;LFQb} z708|AAtK%)G@__;b#g3tHDxs41W?-09hpeHbUC=|9K*OygdoY82t&FcJRL&UrT_%T zZ)PQM<2oGiaii}WUVYX#crOMC+8k7dJ`TH&gZtA^Xn8tF^~L0mQtCqlwq^Vw!#oQl zRgizA5RQTa3ss@XqN315>)A{8W+pGrbXHuP54og=ONX+qk3Ge5bNG=!tW#I5NK%N{ zWd_mCpo|}#k-h5dT9QUAX3Kr^gFctp#m#9%K6RT~#aI)pT^mHsqmiWsQkRP*8{;NG zb$>4sGJk8SqFrdEfZ0Ymc|>L;hiCi{zcFd`dB1`T{WpI4=j;;@^2Lttr4T9zjp5sx zyJSm`Th;LahF`WT)G(zPWEoR{BBv(1DZc7r2STL{ge2KO)}R8(WFS~3YhmUD#ob%q zH-DsB682iSMiL1lzX5M3%w3)@t5Dda`hyOl128c7xThU2xzxtv4(Z!Go*8&@M0Y&) zP&+`5>6)BXgOFC zBT_%_Yj1|31MduFe!MnuVAT=#4En7i;GN6Z!P4n6w@*qE+;LEXF@k(6Pq>DC+?hev z;ic7~YawRP{db7&!Z)t2p#Y}~{&H#$LX)B^ zIo0rg`swGM4|1;vs=fVBEu&xc6308ik6Hwv#c7?L<33HdQx!xt+ES_}#BRixw#GdG zHdxU{2<#Luw?ow~#>>93C~qk@G2%h6Tz8r(686gCATMfh-e*IiD|~ar_=nXMpbM7Z z)$98?4X`Bl29|-D!G%)zv{8?`P(x|EA)%6TAo8XR&rVk~8h4~!YY544wvu9W=yk~k z5yjse6Zd%Ez?h=Ae@wCU?J2-1nIPAh2N&RqnC})$yeGZ z(C_dde+J-;BZwuVh;+@+EriMq4=*WI&0L6{q7iUKn?KFaJ(n_4{yqf9N$AxP8mPzm z-MR(&XDlJO*Gi@yjFDa=l#?962i!zklWPYdm3|z7W6>sIG2P;ZzeWJjLF!*(w@7S) zJ&f12c~DN*?f%>8otI?OJ-0*KcXt%xzRJ{Xm0AWi{5t+O9A&aL;3#KO%VBSYsK}6+ zXRb#a9QL-u(WDvG5LTcOrR7L+Mhi&P>E#T&=EJSfay!sV>`m6KRO@j57(q8})&UFa z;V&|XP+@9bDS)iPxvM(x54zpG^&*!E({f}9i2jWbVR~++hn=V40b_z_vQfnM|C&pB zA@@Eg6DlC^KJD$CT-<>adZY0tijz?8&fpxPeV1rvIJkXVqQ9hBX#G(g&!>7ZAS!xfpU5tIq9z2(;8w_$Pqun7BfIc#0R#!Dn-L*9K z@)~d#eja-|GI(L$og{-eBt)75IE9Fg!UDlr{C7SuM%vcctyl4}PtBdbxZh9L`GbQO z*L2!)5qyib{h?i2#w#$v!w0v)QS)qgss-|Zv-JJ6)9VVb4=i$@95-pIZZ`#6G6@9H zC;I-pibKu_uJQ~5>(8@Jw)Q0#Z+Gu+kY9HRUq_n1B0KYB9OT}c3FuhVcpQYaDGCR|0W{<(X6P+aU~iPazoEMtd7`a zN{YUj3iab~^H1}LLp;d`wt^AXDcikEmllIwZQbCX2mM*h+*6gdE1~PC8IttRyAC>H zL0W>N{*e!_mw$c_K+0c(uEKS&tU=fK0kFcME>%R=%O2hsSF)UD`-=q%hxO$QC)ur^ z5H$c}yHmu}3@zC0JGN-BGXA-M{*TMGS1Ay08CP-HhxPRs2G#4%88F0Jdcmgu+fQFT zyt@r6U7vYNcGn54QuxgBays91Ky;{2tq=GDe$ZtiNVfsd?XOpPdL)^aK20FnyQXt7 zTA!TIAfoW|i1TM&bt{<{&++5psm{J|qJlpd$WY8cp3pN#GP=#Qp={AOaWCJ9?uU#i zoGE2A7#i`@;=}U$**X+f1uhJ0U~hvyW76!wwy~(^!J}=#uVL`}aqcVj1Iu#1#-P#) zL>zJ8Ic{ENSvUOQp5Q1-F1FyP21qa$)=JBOk!NI|l}e;APy+rXRk7NLSMVtxoxsA6 zEGL_Wx`)SsU$78TnJfU{l^`%*{6@UT)4af z%Gi`|vv0Y}9FFNuKezf2I{sL6Odq5lO2l^?H%&ahI9JI!1G+hebwE^O_tm08|YX_ z&dom4351>30FUAyzi^aAHSl(~^pw&I1jih9ny5G$NdjZkv`*#L2i;wl?4KWMi_X3d zU>ixKB)#^ij=#R!nDE=quJ!b?gDUsV5#(i7%Ofp6q<9=5lc8pvVTFGaJkmG78n^^R zKY1lplCh!`5-ySU?di{bsQPL+3_%h~j$3oktWANvb`0f34s(gqJZIis>uut9uCwJl zR=c@-Qr6)e&B21mclpKRJgd&nV z*!KbSc9-oDWd@^7byx#MiIR8)`+$DSm&lUUmL=Wfri@K~Ki|&dGqrE86_gu%6#ngN z@57ht&F_>H#iqDa;qCIbHoh$fc`4r=nq#DW^H@e=$XawZmBXlFwG?9OJ!-RG?jz;D z_mM1SLc}zu^v!4xm+K#k?H3{E^?c~-7@QSKg{{c#sgOui`Z59`xo%x%{eCI~wLKVq znx!K8-2`BP~aYRq3!i2EMKM=W_`xMjNctpf{9!%mDvn_KK zyCW*3+T91H+;{cN4!*>5gn%Zg^`K3Os&)oE3LHCs&}A-1fmS*IURX^Ey_ACnY^Cg4iUb2YQ~(wq-Me?u`Sb_1+`nVh~~y zdk>0e&{gWD4I3or3c%R&M?&+UCR~2!vrH3${3fQ8HMHlub;t&K0XCw1Z5a``#zq}Z zDnpS}8<-50O`-!n*=`FcwNk#N@@W>ye;f5{Qo_t!HrYd8+D(#>P#WN!d` z%EnplNVBnUigaH~*}T~^ikkHMgr4;G6JJ24(j5~mM9u^lU= zCL0u<_z{yLda=jo=e!RSMO7TiXINT@d8LS_Qkzl1trgVv;t4ew1bGqoE;Q=&772}x zl)6#W2#K`}@}WziCjDaLK6{rvo(Zgcyku}@2*@AuAqq}nbemhkWCk_f)ajq+IE_FX z2($B?KKm~xa0M+}2dR-yE2$+3yIfX(i%RD&#sF@t(wau%Q_;p`8)~ybPgltFnL%>L z^y$IIg~1bt-V|=jpbAKwclVH3oV&Fknwe9bH&i9?=*>9(YYs@Zc%JSY^DL&4}>z^=gzKpF&9<7!%BHL?dm^h{6YFPIvl_<~07%!I)$(U6rk(F>n zE4Xx)=D;WA@59>`Jx6JzHV!=FqY@24>qc^6VLq9~3f!Wn#uIOeOCb?e6rja9eChKW zYp(P@F^_#ITS=lchB(gMP9H-ovMTKdxP@7ASLuoF+A#NmXnQHH0JivxiI!aRw5{%# zwbC7Gds18^yEeK2K~>0A%rz)s!9+_~x3gc8Ks!@LFXm00sFrlg3wRY6+MI4Y-<>}@ zBSAG;dzvBS`Vta7d?&go>i&}1EBdXsxDft5-AQNHW&vIb=IGxJIw!udH{hNBIj|7F zvJzMfzm`}@u0=Y6_rc$jYsbUfcZ4Ge`*319u;RVq^~(l2A}A)DNcYrPrKQkY+hT`? zq-tW>xPDseS2Z$L0#NZKVX>FZzy4)tL@IMl4sxn-%`~cOc*!dP&?|i+7TC0}rzB`C z?pTsQm|$KIjYU8{(RFu9puUXo$R|o>y}@Oav~&4YHh)ye!qFtSAuD)zB&6CYWOA5F ze>>l}kO2n`&P^O_8Zs9+JgaB>e4kq0+c;dduY(snp z?-pHAuLx>;v~eJ|z|AVZ6)AJ`Mpiu+_nGAE-*vMmCFsbWd92*6JoOCse*BFi`AA9X zBOTG1s_1~Qc_hKwtm>A|Ea}K~vOz)H#{J4v1Mx@?eR7umr=kaDEe=m~(Ri@YMe>HN z3!p|f8DogL9J0?6ge6FQD>e}*hSWO#KfS;@|_+=&F6)5XI8 z<5?1kmCMiPlk%l!BDpU+kEi?gMx6WmYC6NQcD)QBnt0<~YkA__YfyYvHyc`ccZ-#C z$pZb-xU$LB54@2YI5@;U8BAk93+{$Mp!}#SNO0}Dqly((M122!^XMW z{ke}YecsehAw6NAMl>y3#*M;zV(R#_0gg|uvIFTT&>Qw>p3+TBct%zqNIyFNnEh4PoDm@cD@Hx;q`(VyfyH+LHo{3LRYOV@jf%>Rz5EA7S@)iK=<(43C@{5 z>2#X|VcLm$o`H!C@zSRF9Ksl7BZ+!Pd~llwU+`I$#sPOZma_u_cnC+iJ^M(JNo1lj zAy}i$UQKn_ytPmIJPAN*(>MEH&*Jvi@Fzk9%?+GQXOrNx3hNHVhM-Li(W^3{%bOlv z%)((e$Y#pm{JY$X1F~zU-O;Ey;R9?!O z*NxOEJ&#TB)L+}QnTL~SyY&|p2{t4s5ylCy1uO|}j{`fOx7A2MMwkIxgAZ%B^7 zh!h9i_L-N7t(_O78E1%x&a=A0R3>_4CyC!l1qSYOa^DId-ykV0z3Kr>!u%12adp2- zHQsy?x6AhA+~h-rTMM(q;)|Pxw~?#s!yNS{_7Y=rS95g99_rM_B=Ar0#S z+}@Dk_+k5V^~&=Lw;)1Hw3JYZJ;J4BtaBnEAyYWII~_sh3JU9Bs_ec*urE{`!q)-~U zL{gQc-T>KeEX1WT7S0^R1j6{90L!ZaG0bPXN@c3nM33Q5`rCca%J`JI)PIR})!Y5g zGYk*!CPD_EMrmr!eMFNmvW@;~K+N%-#Q7FZ37X-j&+I~z58I#irx#8-8@!<9q1rcoaJ(ePjQU75CxZHv`Fh)jNbz`}L*$m*M@JFjCya=gsd#0`5?{vbpe5x<-xn=fKP~1fGPP#)eO-v@pfAzVJ%i#BiTQuW(8F>>$ zW+O7fimOd)hyuO0xNr%BRXlv|k6v$|tW$cjDh)DE%@rOD=H3VsEiJc~Z#S|Lw->S~h>8SdUQzW7Gb4Q>vc}p_! z5MM<&CM}q9)Rz9o$}4o%I6HnLYN7g82u(WpVP)8?^yz$^#0!- zy}ws%_?PF^vx*AB0-0yG_?|EjmFeDo;K}LMKSCcr=5`R~t0~03SI8^hf0@fl7DwTX zsT?Lx+f8F>(@t-u2Vz{~BP$0el8fRSHh>rmld=Nk)2AoG=7X$4Gxr~SOl_%uA@+1= z+xXh!CMM#nCtLK|c9y!0#p+BK^cM~OWoP^4iOa(1)1-?FbNR+yS(W5xhLhf+8XQsG zpRHd>vG`|wD9@ac|0sO_cb;@j&AYGeJz&&^xN(Np*6g|e`?5wh$esg_L>~)+*gHhsW z-90;fOYQJ8Sn6m%!uw%ii* zRD>u+I*8J%v`_+xq7do5gaA>hB!mbd2@nEz2buZKeCOQz*LUyp-1i?JlkmR#-Fxk| z*ZQsBS}WoX<*XA~*y*Ae!f6POecJARj$3hiWniWVm#Mok*(=I;FCm< zcvZBKry&2u@oL7ev=?8oG2*7YTtes*mF}%i^pMWM8=)3{B-40!e_LRhtVY)ufh>?W zFP$;i?p!llueEmoxy~hTcIwO}$ZbZ!Ecwbk3a*6cVCfdrk8~svfZ+Vi!6d|`%6V!8<})(Jw1|g5W9V2SZlrPAeF5R zixlrs`Z|M^=ZOhY?~e}!iF}7W_D!d<@v5Ym`O+evc9HMs7YB9w1-?c($h-V-$w~Y5e?L$7^FjS>xt(2cC0R(#$B^d?0*@HXTp-K}J>{+C6#W-Mq#cW%BTb6ieDnMfn(c>pg+B z&r#kX3SDcZ8YBW3pl)cUS&&m@Y2&ur0PM@{FeYX2{D|wKES}#p+qoNK2P(&Au=Mp? z9fFxx@d^_@i3?AAeD~6@{NNcBur1;LbicTzAKkFb~R9@t4;0{p?}pF z*%$CxtJ+k|)GsW40X1H0<&ieDw4ki{R0{h^2Vcv1(*osKnWkNwAlz~ymHXaiTIP96wk^{ndqOD|;`hg}v23I-KW6 zgw-%E{x%qr5Vh841fkCr!`+CCA#D6q#R+WL2pop1(Sq)y_+9)XuC>!Uw#6RRI1E`A zifhTXwExPkAPeCek5sBG1Z!~0COE`+=7Qq7-Gb&lJW>iTak7O~m{WN&5+{+pywb4- zZ)k{zNNO2=nh9!99iTkD;^u~i`y1jsEtscST4#bp*Z79cr_GHz=V#ir%{8x&{_a$< z{o4>XlY=zszG}ggg5;qxWS8_YqiZu862CFe;PZ){hHbtEEJaIMj07^$%LrvF)wK;{ zuB{(opSFW=joXc*xj3~J%5slbdh0nG9~$m_q1U$HgE1zYkAMrq*>6?jq?`^kj+jX5 zs@>cPI>&75khaZe1VR=^N)g7)hX)VZcp7vFICOqcv?SGSeNcuGTRTZ8tcI_hA3=7Y zIxy1EOheKU^~$D}nqjb+>}J>5nm<`(q{CfLa!_U6jZoC-*64T;)oJ5;rkv|_=DcOu zNF8i0*&jlZ5g3vi@j-6*G3%6&j$b}ggWldGYV;5fPh3^O*V}NNp&> z!MP}WSQGYKQo>kt5^+#1jTU+6`)Dp)qzj!15mjr>BV9=8|VHuiC$_x*^_pF z`rZWHJz6)948umSpT(Cr2vnYO&jLg#woX`ca-$2xuq`N_Wj%pX5fiyOFDG9rNOd<} z%6O$4624AKD|v_Juqa+t=^`-1uPve-6qY(9|9979HD-K-Gti?2wz(mSED&Y6MZ?e%%cmvr1>h3}&_AGrXp zvlCVYUS3Nk8!PlpSNdjPUJTW&TMeH%T5&Y3qX>)CEzHcR-ji2t?bJV~Y~@qpw6W9* zWRaYfY#+9<8lmF?x4LMF+RkusyTZ9-j+1V?oZo_ehZgq`n&_@u{{(t^IQfY-M6=B%WVI7(V8rb=3sYuaIuRd$vJ&In}&= zdaiyC8&ZVpjd(#p^|%?gcYT$@qIW&>Q#cKXX8qPalZiGu4B~HgQ5_YolPJdRFF>Y5RyyV-AJOYM0c=w>9#&GRjgtX@2P# z!a3tjI%|kd`qC`2RBKhnS-&zHa4{|HpRK7GKJJUCCf#+IUy#zx@xjddm(r$G<*!>? zHAZ?Tjtu2*(Y6{=O+Hy`abI)HC(7uzP9<1LJ_ucnG_sF}W!Lc+NARh^(p|Ms13_s) zGmR}_#(T0b0wPuz>2=!u#fk>5AD>`mU@B7dan*Xr6%sXHavIm9N*b?o^9)nIFO!zA z5(VU@!4oQ>e_~&ZPQSRR7w5=x|Er|jFM0V|oudv!>q{cJvsSi-B%`r_)422#e2C8x zuB~k^!n@yQBd0K05Lx+kT4d}&Ee$EH^Gb-i0PN?gTG=jVKRwQ@3*q^OSn7_TtiwIG zZ!USGZt94jnWx>^)W=MH&RG)qTcKDfOUQ7L1@v^9L{dM{S-u6~kT`!6MCh$P6SPDs zs;_2vX(a#l&2Sf1(cJSK7tUat9Dq;gI$n*NE5vrWSGT=58Rzq?iKCF>p7pgdUD!6% zh44o_Rp&zxD?_U?TBc1!noK&*5j3Bm*K-oCFrM~j#33KY6guTguxS;`ZA`5@6eI5} z2{c^)rfA|)bA?|8H@S3kGgoOle`tE1pIEgJVcJ&RO9$NrfucHMEZ!q;!HoFgB%t~M zwe2vzRr|CnmwrBpHK9M8Hmp1w^9TC`00Vy|G#JN&jFJdNVN zJw2B)5VXmV|2Zxq@rKv79xAOZxYDTtSwb6Td5S8DSI8VewqWE2QJ7C&3D5Bjv`7jc zQ7P-QS|L0BQrgNoC+B+AF-SUw3^s9+I^+u1cV*y;f0TY!b!agX#{CE^>q`9!sD4&< zeMti;$=ok&rm^o8=}7FZY{-m|?>=>LgYxu8He`S&77%2a-dOD|GiN-`bwXCWiZJ&) z`KvHQ#goya!4^ZPYZVY~FofCKeryA4IkbJ;Y0LLxP9sVkCK5#PQ)KkZSyj%4i9omi zDB@@sk8wlJmKwvLMaD_S(2sxWi!W4x7AWbb6FPXW5_>N#_hQ#A)0FM1!u$q~dTE!d z?PqqAv=m+qal1cKvJB~Tt4E5oeNXklc+2VjBKmf}>@W4fej@hj>kCJyX*rF5w^G}* zNK3|n{Qy+Cs5WE5DO|gsm^2nIqmg&n$SC7L`u4q%&KJU29};+SKwbYt$czZdt$sAm z^~@#gS%=A%*=;e8A8ER?`t)6x8&5FoQ;qm)F6AHypJhc6aRrg5*qSLgLgr10+Ly&| z0`AV1B`j(K4=fom3*`>>XjWI}nQ!7R~y_i#abs znbH{t^iC)N{=;!mgF2I z5yrfk6{>XWM{uVCZZ$|2%xRNLGly4;6*D`>SdpTIMzQgEa-AoPYZ>* z@pB#YITG^EeF^!Ca;k+yJ`GzX?6FbJjeBXPJ4~ zU77yO-Z6NpEXL{x82L0fnpumP+qTxYNSuekH(-ArHBx2K{o!_|#4U+I`5bP3xUK8J ztKVyW``jN-0x5O!asB`1=Co-|%P-6(v=<}xkFK#%4Q%wrTh{64X39?R^1S}&yxBXx zfoFgKGlLIgT%i2=!8rc#Wve!64NJ7+{vsg{%)!+$7bI0oqFFOiEa5y|~pN@boz8-VQdBIz@{Ws10X!p|6=(sj9elmQ#5{ zTu4U1m6CYr$k~#iw%k@VR(}|?ob>PqR^Rukuz{+VZdwcsVeyf9(>n%ntH8Sqjhx( zvZ9rdu`wpXl1azX)HW-}=%FY5`o(vQ=`q&&W2Rk@q!`stp&kN7J|c1rvO~@e<`=b^BaYtR z5?Uh|c!lTKZ+%w>Nsn6|#oTX|C-&T^|A1OOTB#xhnq=~%sABsragkx728`)N_Jour zI%4T#PMzaVbdq1<7GpIN)W&psxqUg*AazToe$^FkKaM;z-(QU@H29kxZMf1CT(57{ zyr4ZkRG;?ZOdR94o=SRe#LWw0iMS#xn)&U(()K*}wlxA7s-r9~U9e?v;#BD%&JPBBpT!(kuNwLvuZ zo`{l?*go-$`k#)7U1|@MKsz?d-jowssQ)Axu|<|4`laNzBSrPK7xO7HaBr2Tgm~!b z2p6yG@&P}KA-3w@y-49-#16-WQ0^8{MEGS){9g0QpYLh8A9?gEwDY61PsQtRPw(4B zmaMHW1H1>|IN2-5{^x&QWNV!gF_bX}+Im#l-U0R9>^i{!@51VfRB7`?))N*rM9+Y#tPeFG|Ijso-bO@QNesXrK z{QrQaU;t=Q5L+pnW)EsDtP>ht-(9qSpSd)-a74Ky^u-^ z9LF}Th=yYw_fK~y_zZxaC$y(!O1a%Ttp0nO8Aa!wz1cX0AD$ zPRo+~KfYW1+@@)&9C5=i#d8j|M1`o++kKM%7|;wFHMaJBB<#=nhlnQqt(75K-fS_k z->>&Pj{68r6Kr&85RmYpzx)hwc^T7D)*xBd&^hURa^IG&6*;j&l9XgQxm~%S6kOND z7e;yRsQ~laEDq;Bh=FeiD{CZ3omvkxAIU7SgBhWORW9U5P@?m$Zf6UpW$Ey+!iig; z0qPM0(CO3vPc(y2+HoD)RnNvHw~36o*C01y1YK9W*81qQOZCAx_Pw!@<2TRQPby^u zl8pRE51lf+cc#mLff4I0DFy96eLB!sR9)~GtF96k9<~@9^mynbm~kcdwop$7<9K3w@^VhDrxj7Z#%vL+wd;f{IXZZOC94>MI=|y4aO>to#;t+S%)Hd zFb^CHFrI4Y>_#dJlnL~)GcA_bs)6-We#HLxvWFqD9#f+X8){8Qjx2FkIDA7gq)z>E z<#$FJwQ*gMp8g_L-0q*j?mWBQ?}PZ8f{LME;BU*t_V9}%C#q?EJ?F6VD%hKq?(-tU zlQ{CzPN@5af%@Ag>d+eC*o%cFR_#7ni~;0Kj=`h}zWPOC(7Xv)28;u= zx4|EwcIJeL)tO&o2#vH_J3_+>1n%HWL)okIKJaBX9@V;mFed3@&?0lhBgtAhS9Ws7Oe!B2He0P)aVQ;PQ~QRYMg*n`tV3OEwU7*X~~wLuejw>u3?%RrSr!zV4fk zqq~Hawd@PiXV5mMNsI`H!-LqiPACdDHs0#>b^o{H|NH)*R_g>Zfzni2JfY-$OYCuX zUS0YVK5F|+@3cd}-WvZks+ko;rleu^iU0a0<_fJC#ZEp^Gu9LKVZzvM z{)G&1K9;^ZA74J&h>p13EKDC8gVM)q^<*lIfCBHZa ze#Y2zeNxcq1b>Wfe`bQ{a>FeL`rW=}^MX>>alw2zx?@@fCMecJ{Gc^=I+BYtOYCo* z2hLeR|8ntOK~MjMu#WqzL?^3)$sGI4QG}$WPJbr#_y*G}bmOXjdks5zE-I*cc@)*< z9wCFCGV;dfhMgzcENX z_Vn1q%EOfEjRDs3odLN9Nc@?4)O{}x^7<)kU0gc#mcK|bCxdqenQY~d7mf#)XB))@ z^?pr?;R-Ep8>p>Aw$0)WTNp8SojkRljy#8wRYt=XCVmi(8W$v2x*cIrjaskLip8z) z$9~+b-s`k(thhC9Oq5xQH+r8OMH_KMha>< zOixcOnZ@cS`iI2nv5L2S8J#e-t@X1P+VC>JkU5u0W%9k&Hw~^JT-V|lL1WrZ7x;bZ zdd_Vx@xR4v`s(JnVKt0&!#)Rh|Gp(Jx9~W81S3~7pA!t3eno#8F;C$6KB@%{>h%X@ zmq_s;S5_GJacUK+aV&en1sn9Sv=gAu!vQGgVj5SB)9vbC5;>*YvO?I%Gj2J`!pm47 zYm4Jz#fB0Sj%mk*43*h^?`!G^V%Z1P0_#&k7LsI$U=pEZVcscz)Nz9r`!@%BSgFNxx5QFpNo2^gG@t-<;y=iyn0zVkZ8*t-qv`@*b43YEa*7{*1l zl#|-fKvA#WOsSanQjl3fwgreQNXO?94lT@%@Q_+Xw+YBA_Q5&@V1Ply_3n*EtF!u|y@p@w!W=W|=i=2F$j+5)RjwV}Z-kgD;IGOq%s z`AtGq9^=_7+FF5DsB2$=qTGjsHa4mkQ=CP_L34q#JbXEPOVCmAo_qK&X2xFjzeX%p zO^8d9#nm#ao<(%(^VL!n>p+~MQdj~R6|1rBm!@MxIqBbB&F?O&Q4g0D7kQOzzXgPw z7#qK%wwX7d>5--wgSiHr16#F?Oy)3u7i#?sHVhLtrm_0b)6tkU_elTqyU8DoyO z$El`$+R!=DnM`)M`&x5i(5LS6W3X?aZ<*v`B{|(}+A7@k(vX-ut`t%$Pof{-#n4vd zxIu(BVFv@_s_%VGs#z1LdTQUmN$?+ODzJ&QRv9@uJRNwZbn@1_(3A?%PoeLlGqdQK z1La8>2r)=E?z?|l#Zuh}m6Du~gm{{MsBk3(Hne=3qq%=bl9N)a;hqt|6e!E`ZO1B@ zR*I1`6RNQ4*$uOMhNsc?naP`>zp6Z)y#yl3vHzIJ{fPg55_xO9Atw0-DGq8*ue|LB zC8={1Ye#jY+n$vmJU&!V+GoPFa-@h4lmZz;K_upt2o-m-e&7q!Q|iMN^)iaR1gyW0MkNrAzUvo=y!~l2c>Lc zEmRgJwt$ZIVj&jQ4yz4sR<;EzoOktKwkCIneX-z7u^<^?$%;P6YE!UGB&!0!(9Wb| z7pmWUBGnv!iH!v@kNXaby=*gjZ(sCWU{Y+BavEH1c|~ML_&phF_jo33sxE%w@1TON zq-|Nh!``D4p&rPjEAVTv+D#%afhqAe!2Gy{ht_8|+Q~4fcCEQw%Z!GxmOPWmh8N$# z7Wt)G&ud9X?yRG^k&E+QJ%M98d3DKy!~Y(LwV2-x#H#x19FQ*YBMJgQfqzVY$2j%> zgBMV|8$x+3P+cLHaJ+W2;0TUqN1CWSiadH&#o8L+_IF%N)A(h0#wAa1^o;-KgRK7} zA>5hxZ4Q20(Ff1Pgsiklb}8eQ4^{j|7RRIq-f~m zQBL>I?0vl0TENGh9P#^tTARJ<+nbEJ%&;YSQmv0NsFtBLkZ-Ka-C!z$ly!L;sa6@Z zv{K+dcJ2b`w3bb*fJTMPJk#m)K`}%acrXmkEnd6>Tm=Vs$n#e($6|Q|zcmDa&yKk* z4qbV~0hEuxqnc8beKLhXo<3}%p$0~*%oSE}eVC%?5o=TB9#hX6^XzJ3PkN$QRaq(c z%~`qsy0bX%FlycjcJq3FDVH~~QbQIi2%TNZWG`9Vnv4I&LVlV&v^6YB%q?1?#|N7h9;etQeY3oP=kTSB~$4OkM8sTss;e@6S|{xKQK zPjqmahENLgJ2McIFil2EPqKfv3c(;#ow)kB4|FXW{1C9v-||F4Ycf4#E@iH=LsqeN zGPF2kbD7YU=7WQg<9X8yE|Z(Q_%CqzfM|_<0(X;F^ypT47bU1%YFnd#-{#WUxknyo zf%bHBkwT6a$m`vR&Wj`VP|I>hxY4Cg-z(n!_}f5LPEkYLPWxpSNdF?7tqE`l{uChJrr2x4&a_-+|*d977j!k%cw)j%hiD z%pS!af2n=wd~AC7N}K!WYv;U|FPmjE4}fY2ov9Z)WQ%V4YI8Rhj3aIdIqcqA@8DmP z^)q5?HD4rW?!(--6!yU^OpoY6|ue6cO~kWW%Zk>S8BSee(YcA zP3%lC+a6x2j(f_5-bzGlF6SZWldQeV8LEK_P(o~tTcZmxJ*@cH&5Gx z>rVJR9@4!-qA(ZKpy)@7hpr}x*BrgVf6CULCw#mXU@B7&PZ@x!F}w;4o!525+~#_+ ze1^-Ne9Ev(s{Y$tcAC@Xl+avx1ec2I|9ay+KmR$~XOD$S-k0Bg$Rb4^ zYRYkxD@JGQ1TSbW^Y#{d+#kJDKtgll^DAWG?k%1k1j4PiYR?o9Q(cU!{wyNBA?aG&Tg33*yOavxs9? zC`6KT!i}#k40vSm$RNDS?;H9>SM9(I<%_w!v~TuY+SL9IL3n)3DicMIC>6E!l1hwB zWE$f<^LE{Ci!m!vL^uXfHni7v2qS)Lylb?Ub_gD)!>RG>Ec8>59wWE3P~l z!gK;Or7Bvck}zh1AKKZaZu5@<#Zl>h8W~aAO`BR1JKK& z@W~O+ce>zZW{$RB{<6ECyiCZpm_!o zAAV`GG#zP9H`^YQ5mY8p|MACdXQYQoB)3>t)rcnYr*@R{1}??EDOQt7^OZ+{+I^ z>pujV(hwzL~HatacJmcdGa_c*vaooHHIH-l&)>jt$!d1D#6Bg z$MNFJ?cW6ge@fThV{&eD?>Fq`(CtGv8vI)pfRcg8q1M=X-AkrPcrI!b%~p3Wg7wxF z9xUf^W#I3>7a6(RS{asEoG_A9WjGOD)1;QGjWuHJWp|?{zbj7$@g9;6vWBkwD`P3i zT4Yu>2Goq)xsHf;f5-S^T5UU>_E5eO`4c{I z_b*!hQ1mN^|9I}(J1#&qN*`Ue+ZA;a(MzX#o;`ldNA3M>zt~zoTdaK_jq&ubNOr>+ zvEtpoHVFnlIwb0|KM{S~@wreZy}_I%a!%J!L<;47 zhflSepjK$Q4QGwy4g>+-h{T_KyGblSy*h)59TMcW13ua13xY)={iwSuCr3hFj_3IL zK`lP4ALp8#xcYU5_V705{CX)2U#RvoC53w)Y^x758@Yqk-By_(&R;!)Iu<@ zvAoiQa`n(86=nl{?2Gd~ozcCABca`zwQKU!0jG1ap|`m7L$!GQx*VFii?B(Jl>mECXdGl&ZNpu zqp#lBBW=I$gT6g748iQeRdA0fwDJO<}k?r3! z-Ta?R(sq3$Pv1Dqjlnz2;0^l!T!_Z8zVinxPK$PW)V?nDp$BMkwFYBYS!-X3Fm1zmR-X<2&gDA)ay4C<+zmx(U-W5 z+%e{~Jhpl3)f;zvzX?>xb?4lQsFgqHwm~cNx#67zy{ilTJok}z{r2h86Lp>RtNV{y zo1CB*=Fg#}EOg1A4G&@{pmD^Vox0D>JJ%jM+(kstFtCRHP!OrYq^Cautf=bP zR#z-a-JRF%Pz|(%TI64y2>QJ2D$85ZEfdW(vi^}IEf3uq4GC=M^?w)elL>;bPjjRE zTV1N6Nt4`ajY-v2Ern#ES*J^K9sF|$b7>dv-Sh6O0B`%oDkm5(Tz73y%ey?C#}Y`9 zy-G;VMbLkk(_RIqmC|?0Nj1$BfF|>4VuThyL?-zj*xPNT`fWI|C!?b-zxfNrhbj* zo5Iiws$S`Zv0ojKmo=AV-wdNnG51_#2I!BcAP+<5xf zZ9o}TT)0&KqaJU*Uqa^Jdr?d?|0SH1kEvlr`4Gu})S&TP-*9?<482B@(Fz_DN8bV} z-*?v_|EltHRALe!zC&(#7Wrj);Eb8eDW+Gls=P@s0X3H#SZ_`Ws1b56r6y44%r-AF zaxm?p@__7SV+x>@rV$e3Ewxr<29SdldCmiKd4Gn0bp0PQ3jv38ZV?-_Z@|2@ttG#N z3XI2yDd}sT7 z-n=&J>6&47%ud%rn;+|UCRZuDuINIam(&K4=h&wTM}4B(L44_E$!Z&2uPdiwcQ5|= zYM}{1ot5X^r_JaoApH1R;1rP*ME9J7<0rgObEm3iVjc2e)W-%}3t1a4Yb*k&aK_qf z4$5pURfU9ehb)DVHOq22A9Pcyi@PPGC>JZ@lXdEIEbFXLTeKNo&|4XmpYHK?g7 zOQYHlc_#BltL(&02AapFRM;xt}K)K zK(I&;eLsPxn>^WYm>vhd%qhS9zkekRY6P1&wPUS@UAzrKvxdOrc3a#?7|@Ts7^$qm zbhhSW8jDi$yw+p+Q=PR+q7akJG0<0s9kO|0Da3_>%HaxJ935qie)u)~*8 zA2?Dr(~tw~LS^qh>FGUYO>N=(#rE$`Ty)nT*>#gvQ#|?;niKLMqhY+WakV4SYkiH@ zJJph>Az@w#d-i(}*-~sBf4*XCZ83Y zK<(F=we@ZsK=?*Jz(-)3*TO*R4N&a}&4P zxuqHJo?IGDZY~$| z9%Z!|+}TanWA!Kh?fi=q$o5Ohtcuvoqw$Qlw*mL{Y`{qp?Uzgr^oVf`#&5Nu$-Xap zcxbP7jO(P!BXVL<%ioRYW0so3y!@KP!N2lr-io$InMm=^yM@i%R{j2|8}1rr6oQ!o|8)^UoU)Ezia8gxWu!iE=~Tw{Vwu$W6RiwTpA_e zx?_GQQzZGZH?nh+hWH*>>h>FUq#ckV)&$Zoe_-~iLvq!R?Z0;#?rtgIw5*6q-Jd=` z{JSjND*s6qVjxqnb6mz-F~+XCC?OvtS6Ex*HuUZ@Z|GnCZuUFIUg@Lq%Dc;QWFI(4 ze(hSu{0Fc=+tVuLQL(IIscK*i;%<5X(_t6|2>UyU5eHM16nQR16=vzfj22+$=Ydp# zJV2mwA7V#5tDu@auBlnUWsy`xF8aWD#Duq7t(doeqn(VnFyt$8D-@bK?n9C^0L*;J5Xs>4$$TD*n6KaV>S4 zmzx26kMfk5uu0`At+40?O~1IB;?BRq=%u+W-~OfFWnInzgB&;%zr;t(ykvAWIM9>j zJ$x@vm`V@-~;Xy4@0RBJDH>U<-eJ|>Sy#@LD}QKOBpP-_6%qR z8X!6G=p?q;_Vev^doj--ob#EIy7#=)sA|m;yVkNEa8SeFZ3LjF{T571&LhjhrVx-u z_xZHMcJtF(bU<`g%C`?g!+7Lmt1whnfQ~_`gCDbHBNfBVUT)V$5sN5+%hnOs*L67( z?faDX{cJw+-?jpIIq!A+l&yK)$G#E81r*Mtv7=Ys{a?eV!dT4_t@Y(^-vG$NEfoW^ zD@;G)sgEu<6^@h(UaaNRWaPO{@=}6$871#*$J@@mSS)p-<@~v(^m%^xJultUjJojb z+X`jy+g}Gn_#S8%m2a~fr033j73VusZ-A$nqcRy9ljSXe&(*Ou#DBKsZ2d@#ce8H< zkw(@b>-p`E7Zq=|N-6{)DNUz4aLh*JucLPgB`u^STgLid)tmqBCaBUlf`aF_^%N(K zNQ7<8=FQ8S!9g@knDxx5VH(tO2PIaLr3!=EhJ>h|V=S!dm1W9jKnGpSkmr-6_MK|w z;ekz6`M`0qVC_ntNqqf3vakI68YZ&wUQQ753tzK=?CodnK;znkRX&L}23Q4bys`$? zR~C;fY^b98FDPdzfJhgK9ae%`8%b=}t-$OA*P5BT!L*|7(MWzB7&;~cxes6xB4!b*I>W)Ae*_Ndg( zi~En7{#`%3U4CG)z8@&ax0|tg+W&uk2Xq{K%L0*lPXr=;&914{xaU4&!V#-^uL~%7 z^R~RYMO-PSdUZ)7Z+Fu2Y_!z|{1=yp^OPd6@kKP>Yt$(754ge8x$xSAo@qChhmZU{ zciGWmGx((ho&L3}>n7K)JF``;1UeYnidE7J{&F(z@|=v`{VF#+%>ZHTb+n9KCdceT z6U5>n&DYe9#yKcYMKpO%Z`{@FOGI+%g1(IDXrwOs+Cw-aKr{sYa*OI#AL#(QLq3&=Qojgy?Uqe3L@Q!Vp8_;*i$v#Ym$l^ft@ zj9>m{Ajd*v!1rUHdrOm?Tl+QJ*U4Lja;XB`D0i>+FqQ*WW;WhDDL$G5oh4l*ogfLx zw``m&EQyaE`Q1rL;DlgI^wh;ZN)*bi0j)YLF1U+KgTuy7QLbrH$!mDr0HkiZ168|- zVxWJR{eh-T%mDUK*o$>Qk8pV>q|UM%c-9(y#GrC?i7R~>eR3!ja>2S~}JBUA@ zD1r@mRq6qbhI=8Kb(+SFo4=!*Q@*WT`HcaJ6>yTCgZgWi7n@`_-yj|*5$>ADP( z`KW|UQeIClrj?~_0HkH5PblVI>#M-|rJ!&nqe{u#O`tnw*%YkYBBhiAq($a0#@eGT$eEsIW-W&vmk*)PV|trXpOV82?AG1%rke0;?}ThH5a{hv)m`Pk^xHKR1z>i9X3p=rwStGFv6 zgGL`@1BF#v4X~g2-9H^C6y& z^e_#Y65#QN==+9d9Pgm3u&s>YF@^4Fhr~12e1%x>P|<-=e+Utwy-O65T@X70){y6m zh@O*at2j5m!Gl&0q%J=)*tpE3xRos;FxFXI^@`wB`^iK$cU-el7ag-gGcC#5SOlI! z6@`OWdev>~HfS}m_a#;=-7uQZ`|{;(#@>+#xp?t0Z%KUDQw@xqiyk#90gWp_ z#pu8rp}Bx#6x12$5k(nPGjwHh5{|{vlq0aGoAPssC!@v?ZFQ9!CA4X6D5K41`_f^n zI)|*cDRpu~y!z!|ub;mhn|nwfG}My|8JT(@E|Y_ReAW;OeSdw#d3uqb78ux+{=Pk} zgjXNMXMq2`g>i9gR9c7?>fNv;HY`7K>A7xLA$PFLiliPYRp%JH)IS?O{OZ}JLWL$L z#AP}8Jj{wKf?DNpLGA#>j9%Io-Or3g11wI}?97=M{G`w;`C{_x3gjXD(k&C)o7jv^ z$W_)T?h9j$_nzgr!RG<$0V48mS=Xa~>R1Zt9^-1}MnXAVWN+Q*Q`>&MmoXZkN#)So z@=Kkl1=-_5R@QQ_5x!1EygmS5uirkN?>s~YhtrF|IlIeK~c1MP`SI^YxoIJe>j<>;>Wf{m1(O zohA{zL$2Aq$v&X10p$WCO4iU$>D!}dlvDp z;{wGifBKhK0>7%Bum?D4u|7V#e>)R5)9RKEb0_G6!Q(bMNf0kXjarb`# DtI_|= literal 0 HcmV?d00001 diff --git a/wp/pictures/data_flow.PNG b/wp/pictures/data_flow.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9a2677797a816f7ead925aa7e0c44b993347ccba GIT binary patch literal 30951 zcmeFZc{tSl`!_tLw9ujwC0x}NAzSuctL$aXuEmyYVHAe;g>Y3Qdtyuyim@+~B4uaN zWP~C#29wKjUgvq|xPjKXwcFRC zP^fj<#||5zP%9i!sO9fgv%@E95tHWdzh&M=S_e^C4Ld0Cj}=Y_^bep=FC#c+PpyQ1 zukk!);f+FV@W$CJVJ)UU$o9K;675>}BJ^WI9OO~N7-`n|!(!Hs!Y5fWr6iQQJbDfXZ z8b{|HM{KLdX>Q!vR>Vl_^}2R+#cPB-&~FSXNC%hcZ3rk$#?h= zWrJp?Wxf0A#w#})o~3pxg)4U79#m533LG1+_Xx^$Ea7wGfkACt98>Foqsji4uJ7k+ zD4qCdMH$G*HfVP*aVr3juEm+-L zhgJh+N~%wrLp0Vj!wefLS+2<8wALx{lA|*9d2UNhlvu81q30>(*&lCmeVvjpXyx8W zbBUIXR0TanS0Yhs}rS89rW)BQ^=wGm>p|$YBc}uS^@{*s(o6huGArw+tlL9boHq2 z$YfYJ$)o_S9rV!zTA=Vy39X#+Jtd+cIt@R^pek_Mb<1=nUCb(csHgToNPmF4t`t6Y z$;|s$la_HUMiHAnN@tKIP^im)ES?&47iMalm^3GwMCIb?>NpdjHd+m(7%+NaX_O8VkhyD%4 zb(F%Ma9uw4!c%C=mgg3^EyjjwQJH+lq&8R7RICz=x0&A){-p?>J|?FYq2|$9I_NLW zcmBnJShWDO|LjQhSe^*Sxo%uxPf?>%f4prYiG*GT7L;{Y5&L@dA!`rW;K$v%QpBn~ zkKosr@9kl&KYaM=-6q!cfFJG8u3?=a_)#in;h4gYycRZGb(QG#j+Y7|?U$a|FzPO{ zU={XN=&56R9kkMXKU%1S@s$`b5-9DdR&&0#+3kr^9%kep9aOA-r;G@*etzzl|42H= z$LFEdciIU~J=zq%skU+wlU#(@4%R#AKCJ6GPUUFfj-E61?~q$yw)>`WyDe82JHl0LgKouBS{L~m;hddO6ZGSXo>`G38^Gn%*_g{l#L z^d6U8-tFBsYasYpm4Ch}!a=KH3suN4@vqE`=Zd}QCk4j{7U}b_cG+WLQyEZj`<21ujJ0M$w0f} znFiyW-FCypL#cQYtyIJ2$NXGXY`Q96HZ8a-phYP-_+<7(C?UX_ z?0^$*c_i(cNxzYHx}(<6?$O-jn4mVT)BZG?K@{Yd6<1@7-EjTL;fPYc1}EyW88z1a zXzN3J_QwjynaUJeIGf`_a8w_+E*!+movc^KE*$2jIM(+ToDIr}BK^*J7+ZRE63r%u}+vJ)Bmp_`=Vc ziQBu1y4*(fxQ~{undea%M?I>#q~;!?RwGkE{P@=xr^NT{*$F4jZG271r$fbCz)V{B zVQ?n1>y~}z>I!8&#pPs$p76}4YW0Wf8m8Kuk^JxC!eTdb;8~ z%9ZTcG=-NIRT=TBvTkJjc~A9#$w7RzKtO71hIzm1vhOB~OKsds!6g~=R7V*0Cj|$| zD-x(#B{_63n7sw4$Hq&G9@P_}p+5BD&H6TqUe%Yt&eBURh+Xt39XT2#hEl$_xTPi+ z>Alg!NDiL-%c-^st^+t26X zFZ9-&7r5N8d-3EHtv<8sq6K(mc{?@52u(+#zC!(Sc+i8z2Yr($pY9&?!73(*eBg_( z2B#9In78Kd?-M2_+dZ6kX4Sr4-#of!VpYG_NLS%%Pis!zU;jpOepIr}@U)~|xvE{u zMNa{ZIQ{~g{PPy&D=r)CTpS5O4lKS8!~)>tx36n3&$dlSGfYbCYeDa)!DBGnJSAAD6pIOGbC~WNgsE%qs4c4u6dG%$-V+(?X~)$Vb};q{6bOIx;MQm(H=z6*e^~j9AlwT4B-s^L70S~OS=eKx z6*BAK?kJN!7kLxm&YbYJG)ke(?G9JmRsTqOcM==wavxWn>>rPoeYdWCD6WgxsL-wA zHPPcwr#j}xiH1%EWqq-(qkG`Js*CS+`_(d)r_TA!k5mgLp+B9vH4fH*_1a5|uWk2k zREtj2i@D#RF+UrqJ>)kJnM8a?-+f()_kG~J+7|z=3qs2l$2hl#W?9xT_2$7-!4nyF z`e11<`7yKR-Qev}FG*uUCwJ6u_xTZ&O4 z2Ie8gaiWA7NibjMFU2>k&>RCRsYc&|Iv_>7431Q#vMRV|*DEjSFWn{ckYm+KOb5JP zBmAvk`Cbx-Wa(ew$^%dFy$`J%2`dU_hD&f6+2zW1DuE>105?mg1mBj#fZ>?>YQK(S z+f>l!)mu}ZyHTie5#Ok*p7!yjF9R<-bvvm(oIkl_xL9mvSc6tNmP7BYjqWc%?QImx zF1?TL@Fn>)DD|h>%`-?yR;6Fi>F>JaonpGqUEKA!5MpR@H&34>cgXle>`EC;m65Oy z$lwEz6QrM{u2!F@rZO{}B=2UaJo;|;SRSeuGO!0N(S+IFz+wKbna-(}Tk7Mbz`zw!kXZX&56#WI+I7bI8x2_jccsB~ zViYyQgYOxs3vyGJrS#Ju$=Rwk=0*m)Dd5W78DCauJaRAmdQ+|-^Z99uwa%|liK7o;~%>eTDY60!oz3|6eVq1bUDBQw9I-~aVZyV-6Yx^={^ z%TGCX9qgU84H$|Nz>GHsN?_rZPFdPvx{jC4AvtaTIY0qiDK<2Jb9K^G{`kc99(0(k zof<~Xu-B(_j>j%gI*Ixca{Yc|RIlr-{>R=GGOv!^K2+EfpcST9mA9iic0Cg0bWA+M zk-u)|ZqgPqh=0U9930&Jq$mpasdBu~t7o(*@kKJjD+Kjq|LxULV_cj^E*jlVu@)tn zcPW@;+vS}zRVVeV(wIJg)@j*!YsdZ0sq(1_`o*X*OxfE)n-r3{pzVwESy4ovQu7|Hf7>qslz9waxMw+_le0rVWOPszv5fo3&^rS=r?t z=MULYC5yh@#}DPVr-e!s?`3}1(x}PeB9ey0`e*#ZTBkTB9&$7M6Kg0FBs+r8$>q-2 zw_Rs#AcTt)VJEk{wU24s4q!H*DIYe^Cyg8OV3a#Xv&UN4{E|97Ixh4`w_!Y(lP`Tq zVy2E&{j%!WBSjrlKlD5G&~~?}Ia`&#UcIWix2J3Rt-@%L-{BWtU#_g`^A;&Nc4@*z zJxW8(W}Ez$5QkQ(Xn)W0HxU|h7uBP4IOt}`$gO*@*%Mw|U+kCz=5*^C1C0UdBx^AT zW^VY`u4V1*iZiSdsB;cg{TDl%1?$yGs|E>nlS-@Dhi~xM9&7g?U>Ial`NPB@24&FX ziC9+Qu9>J*#XV0wD^~J+u;x_=ax6K{fB5=oi;3bI`o%2Cz^@!_T*_Qcb&sS|NQ}Z) z!+9jzI2%uegE1R|r{9!)G}l1y5Z}!6AuQ$iaQVz|Z0MTm6Ve-0f4quCrCqSG%YMnY zbg<0f#(;0sSQODKxoaiI*0b3f`63c#*4u6`TR0{VGuKZtX~7!$mcgeP?`X8xpz?3{ zVwFNR@rt|WszVwlw1R@k9)86iYt?D6w&|2ADU)oB+j=)mq&Iohy8GF`E5crfg@FIjn+INb{ZkZ zA-gn-0dNWEATVBb-;OK1?O|OabTDRhkf?|E+igy%2znEcko=G7v^HM)>{ab>;N*F8 zYt&piF0qERJTe zY$=92@{H-OhNwuu7{G=*KNOsw&cY8DpJ}1sW-U7S+ma*gkLCqb^<%&@(1-fxt1s!I z$gPM^gF#Ze9rLW0YV{YvjoGNg4}ILG)P99%HmKs~N2m2&h@ zRew;%lzUiA0Aen)(<5!^CR+TyaK~MiKelH1a!r{!`k01+3a#*nKblhH z>t}t-^6ECZ?V{>a@R>ubOn+195fth;B#SU+-mF^!fq!I{OhRO`1uJCE$(rJ?Id904V?)7YSg#E~Vs0CWAPcsE16PYxo-IA^ z5AqvRbMYFh)hN`;h1V*o`+ll!5o{!<3>%!2(SfzFIubA)YRG@TU?U2(mNn;Bo`Wx# zTPdz~^s!=4?foZ@;6V- zGA*Mp!JpB@5nsRKaVKEq$fD#!fsQE{6za$Ng&{qCC}9(e37!pn@ACifMMK){Xd9klZ59Un_yPN0lBGWmLO_+$^3cjX=4}K7i-0!zOteCh-2Ud%%QIu?$#)2* zV)%heyd8s=4~h>hM?F>#h*{@TgBpcoT^ph(SLObA@0RRfCUqQe=`<^kGC8-GRa+@F zV%c>ba-%o-F5*R{Tw2GZirbdmuq3ZUX<{b#^L>w8_Dx$jA+mh7SF+!mHVw`!8UmsX zz1|{a%*l2N;Np65?!_)>llr95}lC&kMH9xv-V z@WBUwfHVlVW$Yfb{5t^tY;Dg=QL*s}MQKbWm)2P-D+LFr-MgOoOwPZdsmoR1-Pm|u zf{&!iOrJwW?6K>!Hk1PLhqJf z(8cn6VO5CV3@l%sK4Mwy<5C}3I6GQPgnN|4AH||GAefPjlRSLvcN|`4?a0L&a{~Pk zuo&RtFX&8uj!+#v77Kx6_Kf)eRcR)mo~dRS+^#*9!?rdr1P#!--&vxL2=>EqQ3)a{ z;V6t!mSOPeWh{b&1O6~p^mf;)JD$le0_mgpRx_(!Z?%f|%&D%V#@2>g2$xYd-5m-+ zPt6}^;Ze^m>~2?!jZY~;yye7b%?^rcoY=uMzBI3cQF98;E+X_(JX9#Q38Tzq^(J@@ zY^2dS#RZ`7yCNJ!Wru1}gRhq$6M)xtcbBx{IKuc8B$B9W47#5_I5-jbXum^EtYqC2 zQNi75UH;U}z^sBow`4-~ofniah0b^VM*eg3elYq*c8zUauEVrTjCO-!087qs554A+ zfoa~fY`(>Nruhz}BZtq>t{)`;@6n1`=XK%>0@8Sk=*P;Yn;+vk)c_b1jD5WUy!2!+ zbHep?tPX_FwrHDZe^J3~LUsCjy>62f|Mtxc|AskR@8Cva7rLcPxp#&wR=WgcUpT(U z=NTg)*GDfUBD6iIhp+2hf2UXT5gsht234sfT?mmVa<~bTx$C|Te{enPKXhG_6&aX| z8Hj~wK25Btrqa9|>mJ=hygCGsf^$D^`4$L|PsB6J`sNaGgrU@cZL*qhfi8b-iEc_C zpw+iXhp9IZK129UB$ZF*?K^vsf{kwBHn+Yf|Jn1+_%*@VKM1BeJl38E>Th`VIOTIh zT{-+Taf8>_cEo`nIJQCnmw>j+{?Y%z*ihfx4&#fC)ZUSqTPsoW&DB5L_7X%wIWrSe zieAkfG7XBm+Z(252Fs>8|2Zn(nyFQ?575hAc~W!%7lTbrZ>~1f6!ia|>;k+>LzYr5 zpfG8$?&5asyA7sd6g+Iv9g*wgKmCDZIcjs$J#_Mwl`CTt7cIFw;UjoHUuufqg=>g*|E>U`LHExxSmuR`c}I^wiREzv@zeQm)LU>e1xyD zBZf-Knz2ba3P74L0bbEALF8t#wn`Li_;xiZlX8v64}TfDtiS!!z~H}E8BUG!_^ade zxyy**d4rWq225{z)AuXMaG>+>9H^VRb_a3b^d2{}9fG59&V>iX8C`ze0I`}Wt8T*5 zd}xtC@k(IUBR3R|rKAVJ!;)(!Rc44MvE%$ zRHw*%H($p3Ud`8tSR`unW`qt&k%4Lvu`x&`u!_U$ac+57UTwtVZQAa)b{)Ar6mvM z(F3>HP*vKkn39IX6)t^sF%(EI-+jDsvCAC>jejo`mkP!}BQD#gR=gnffuXt*$)yMkFp6fRz3}>du+W1 zY%`*vOMV4Oe z{C=OM)qcEUYh&*AQ397XnwJ;R(Ul+X)1plQHc>ajEVE{L(rTx*(RM+Xl;>y1a!KX!}5gFa?He=nnZam}XS$OO`K0+v!&Wto-OXsn@xO1j}kA0n&toDwb znM^7kA}Tu3Oz!_n!s=G#73-jn4TFmc6~ z^7+8|f2_K2C9ZrbQvPz8E$3!7j4G2p*6&yUcxU*uBH#_Vltb&4{sfv?zs={mTrdL{ z9IG$rRmnw(bgwf4JFH1vMQ^kYWjbj>&=7^lX{tS1f=zf+jFX6~0%=ufw z(H<*e9fH%vCpOAD)I<)Bh-QqIa~-S7u|c=(Q5j9U7=N_30t78&E zhjo!uQQjj!Wrgw|LV)Wvx8Cje`uZBW_0XBA0&y_uow3F*o*nsOylwQuX086few8k5 zfFeAU8Q)XJuwl`D#qMy4Behuf8YOF?ux|T$f* z5vHQU#;-%puH6rk=e|l`>kzw(&$bpM02Mr0uJ!l=P@+z_libe)O9*aC6CaFoiFw`c z9;$ zg(|BsIQ=-YDe^wAc2;^yA9z~G>($F8*&5N>m4Eo8H(;Pnr0_P(R0k1UN?bOjhz5~K zG~&1hBXJhFjuXoD16d`*HTm9cxAtR!TABIr0>`ywg1^Q9&+fC`9At` z;lmX&L;6crKIP7u3z&jE&%zjsd%Qw(9W#5mPI6A_{Vno8Y~cIp>^i z@}w3-=mQ$zgZg9#qJYFc5Vt9o<&#~^z@i4;hD>f8+C^T4 z;=Hjm3DB!JT)Wxsu3SF6{+Ut7$!38kHiu_Wmyf9O@}sM0sj$0K(c}1=c29axRd7L0~OZ|sHUWA z8`Kr+D@| zNQyQtNx5XWA;T!)Brb{ZzAI>$RIfgdlH&ab*}+q%${Z)Qxn&pi#Rjgqs}9F;>+Pg9 z0xIYZpYeY6SX+yrQyu&kSmosIG3JfN`S%_gv@=XqdDJ0!2&V90u(9BjciS0hJW_&C zl?ckLzd9l2UV0XNZi3*z2%_wzOr9My&pnbj;;HS#ku8MAc>^B9e-Wm@6-x!CfuIge>B8|zwtif`Ets( z1SgraU%@7bQtDG%U}&)6Gp5*R$Z9HZ&rE#KSj(B9nIdKesCkIFjb4F?8QCD&dc*aT zliC01j+D^BxEmZij#PVDfXD7WPMRCXwG}lJafXHv%+<5=>4e=3X><*A?-}DuI)mDK zD86P3POK_jKdPdxipMTqJc1ZwkB6_~?-hF5aLOxV*8|9S9>uNlW8z~*vxUTq)tVUy zee<-gkk@nkfOHl@&*yN=C3?PIL(jDLfv+A9;gtoTirj`0ohw65nH&nm#)GSQYk0cL zmt@mc*j+l-P}>(RX_wfw{3xQTzygWC8_}WVEc-CnmWyy@_soEVhWYV6;?<~ZE|5;V z?RDg?WH`9DK=HdlV5ee-f!1%B6BcXWh*)MayTQD!L~$QVYKzx5x88e&US}nI*20tZ zd3dgec@xewZDD(=D6HDBF2cLA$Jz^Zxq=LoLXKAzi#}jUM`TTBl@HG9yar-Fr*9W# z#A7!0;J%0yXVl{kAM(m+m6utiqc!-dtyZaE5uOeEn;H~-52nNZ$+m*vtrZV5#gXiS z)u?r$_Cou>lS91g=+avg)uxKN8#$dmulU*F(kYnFc!cG&8U{N|*2=R;ZipHz|6>H`U112A*Mn1jAj zo4ZZY3iad3D^QuEwRUs+`CrTjxd2$&QktR>`wP^avK`YdNmD8t_QxuhJsCR87m~ll zO_aR#4M-Rsi*(QS6n>$t5sv>$GF`B4iPN)3Ip3fK&(A>UH_-xsvL%ClpExjlemx%C z$Ak$U>ulVd|0GP>2N_kj-CVC-PEw_9dElk;)F2pG6RB`0$DtM|o#Ww!#|J;Xq^70J zGairEjB-$i?boNlL&}Vs)1J5;;S0@w+XVK~Ib@rRW9se*)%{7-=Dm+B%KUv}L4+5p z;^$tz0u@UGs?5t0gl!90Z$b@%^`?_UT~s`u&L@wb%i3e4ev+^Ca&MIB0JsR|WrAMFG=Y-{08`+=*jD ze9U_F=`VrnQL#=9@rQ^FD(*3fHvaA%3Gb30!VnnTFaT@38avp`S1&4Ev=QbHA)|p! zX0DXceYj7Xg%U*i}#Q1WEx2;bQ+a7rVO!XeA3nh*zVp9+)OszvH>`Q>kXGGneF zcXvBHCllRj8rt&h{pOZXL!0A%IIqGz;OJ=~`8;k?aCwR}a^ z*iwwyA4iR=Gzs9V7Dk`2AC;Nfb!GCg`1){UodJh+-ospvU*+ZnumcSqE}nn-ywagp>1#c$_q5W@z`Dd_ePY%Pf72c$3fl zQU2!(wLYDf`sD)GWdmQNJow^GOXZ!-V1Q`x5BFKz&(^kh#1Yka zLN#c_<7xbQ490+b17Gd&lysGnW3oPvqOWk^W8jz zcj5Ty*?x^ANqrYsa~+VroK`2L?En6Bt5!xS)rMA+)>Hf^Lk34W1$E+ccasJtMBL1)j!v`(#SrP2uV zbAefAy))aN31(j?M-(4aDkkeAqM6t*>Am0R?y^<55n(peO>N|^=ba&JZa21hIW~ut z#E7VzOWTN(GwpK!Trd0H>@Dq6AFVuqE~z@=EoS)g4yA@fB%w_-y6oZLlqdURc6>EX zZ~nKV`{v!U{@;!6B0wX#M6005+@xOJ&X>RwlUA@tA;{Z5R0j@5s%VH{aJ?q#9x0TA zy*ZGhO|4b81*u5!)Ea~jc*8MnfKUd`Tsx0gyJDMQWZ8Umlcrq?`N`Q6n6m6-#=nRF zf|DMgPjM8~4tKT$97>GOz2W4<2}tXZCV(0Xq{C+1*$&4=&81e{@#t#_

s-1`D69OLRatt5cyLsoyUdBN6FctV3lRDs0UI<-Iw!t zstv>r20e!CD9#59zERdLewQiTRc9!zqafSLPX?G=q`2t-<_~bU?`8I0m9U6*9ZzkEGO}V7MvCHzm8g#cnGTfU+3eEnrUDW`P=YD z=gx|fA4`8(2zPznB6k$~-7gZtwzrr6j!DzM`?-Ubh!Q;&pB1@1IYi!>Q_=Hh2p1C6 z5A|e*Vu?k^MC1-~yEXB8{)~;qAb>p=v8j%e6*M8i>HoH%5aL`i=o%KHFC>!wEr2!A z$xB1x=)1p0ubGy4qEtWk63TS3N~liyM-wEDZWf{>{fwkV9*_(}CFaj}Bpkt-xQdeP z@kiK^n4cA@l2@t!nDgnodWbr_PZVou+{~ULzF%r-U0T)0VhDPI%y;e(hDRA?rvDy; zpHukkc_>!#lr^J12S6n$QEW}7bHG0yVfOswJs~~bq`8r+pk>(}Wz=hiFZ=B|Qk%k+ z`X1psW)yTGaX8SyuZ1;H;&ziD+u{$fFNL;uuR}m5p`Tb9q#W=yzE~wfC;fsbq)G57 zPS9qtvIbreBsKsx+?w&Vdoy)dQxqF()%+33ht4cSxR6mDiv_CQpZ1WZ9iidr*uk%j zHSrcDpF~nN01g))qz&1H4p0k* z+z70T!b)FWj*6`+yo#W{k7e19qcn4Q%~_yuxvbJ!j3;sC4`{*|X#} zr8Cvx^*DddK3`emm_{q+_FJx%ohh?+C}(BZn5KOxJ0IOS-uFvBa@XYD@yxm*=gBh> z9|ArK1dE`Q??VlS^F#C+$J2A*ko4dX_=2_~@8fmpm!&xeSIC0A=rkFqMIxL|B4~4n zz&PK_+QE)6O&EHH@VYLCJ4=O!WEH>e44BL9eKh&V~)di4u7D0+`3iXX@P#vrD`Do#UjS@3= z%=7&E8Unam{jLG-8i3c}PgjNDeJCyG=N=@@o<(Z!axM#@85$Gmh9GbDPbZCSVdWnGsl@wTk%Z@HM z`##Vm9)V<|P=CS=Q8Jy!mH#3PDu7gBW~?ZYUM(1`8W%K_N6MiiMD$oPVaml z=Rj;N$cz_!hnDjXW2S$;xpB&>^rDw~bPH4wKSJt&8YjxcClYfnSOm9kp=NY-oeL9? z48MBEEfXny?l%@uxi9oO*YW%BqXH!`XeYK5q?fx{$LEiZM?VTrv0v*oqZpF9aV)AJ zg06TPdz?A_m8EtTZCX-0XBNRU#QIm|*o3jp#?H!d1cGnG4z7Lx6}nBN{0{*Q5OC@~ zM~;J>f2oLZ+F3*lEzkkMQd^GV_V1|=3~Fpi$0tBaXU_|!Pcs*9EM=T|(yUBk`R$sf zov*_S9d$vVH0+^nz!75XEK&UZ$#{w&b8m^fu2)?7u*#>QwG$6-l;`iP^of6B0*_T_ z0bNrxvJ9<_+X^y*2Fp#d1?ze;33H6Hg9-lK&ha!Lsa-%qrmrQ4@hSKD!3g3;NpXUm ze{6B|3}h>VCP`_=^v#V-HFWTp*NOI@f@D#5A_=pq#3s2jr->wg{n6Ze!=U>x+k6B= z9fPzF%TVfxRcMXqh#r`@cBvFeK-54_LAUNg!uy~CtmGkrFlzh|V!3ObIUQLgz#Q#J z2{4^J2ShVn(P)o^5uXMScIx8rw^;tm?0dnMU4yFVE-DRUSVr@OuN5T+6a-f@z%5dSC z;(=!wIVtYlJQ>?FO!%a7yKk5`nA%Pyta7l?hZ+R7l`+(;bB&}UUi#C6Rx zyJ)@#ddl;$({fK0^IV_?-w<$k%wpD@~S)aj)aDa~WzsQ)B z6>LJq0?YazfAkNx{z?;3ME+VZ)f0kLg_L-xvfq_oj2}_#JzuVEFpzO-0M!!J)mr*z z!IV_-VyuGVy^9nHhHaA(>VSp{^!t9Y+szT;(FH7$0pvUl(XYPmTEuhhUm%bj{4yez zcz=7C7^}t;YOMm-9~E2-%23zx&bQ?tEf(<3%}*dP_wNb8B?T#$49StKA4pz7h)7oC z`|5ZdoHVF-K(o|{(H{iOcLdLlxPap67z?ml@%J+ijX<3e77R-2Sfhr9osmiwQtg@B zZhhhXo;KCHW=qq(d9LE>N*BmCbrDFZFQb|pxy~%i&K3kU=LL?6Ee-P!_fK~2f;HDE zz3cLzCawEe;SA-&EbQ7|5x5vd8xaGyXFJTT5xM-RV#1oENLLF4Kn8k`XFe=k%_azkl@izBzdLL52=F}51tzh7E6{!zp4f3(YBvj>| zx0ZJO_&jip5Bt*K>PL}s0=Ke>j}*|QHAb__HUU1@!yuyL$;7=#9LnXKH$9Q$pQME? zF8ejoBw7u6HUM@KU>9jLL9Lu0Grtsi<-&98qWvA7OkbB_yP#cegpL3r@DS7>$V$|> z-Tox&;A5Osnr}J_EDYg;Xy*e&_@XyI=`sHiJ+Wc_$A-2^46Q4CK@@Q`HIX?%=oe>? zr9yzETU|GE5c2BtkjtEBq+CnG5;T~bU7CB7^tOf&k)x+7L0@>M?&z9q~)&1@v`@ahXd7~^d)Gwods`I6{xq$!EfZX=K9r79lM zO{#YDWDA#3;KA}~NwGhZf*CqB`?OZ;RO&!qNd9KGy<{VNy0aB$#KLx-WAP{}WMqlY zOfy>I4TE;+$4f~hlzAE;?Fj~ghS?GB$zOEgTg`8uWJ)9X1W**|2um{$+KhcKQwnM7 z0a)>ZPf|?JkvqN`9@n4@z^F`YA-Q=Slf1w@=w&)W5&rxDq6c8ifcCJsKu~XuqlrqS z{Y}Rhnf}d`GAs8;f#5H7#YuVp!kfX-h_S3+>-a1>0U%ZvfD_-WcUmFyj9*9UkW4&g zl0>{dawZO)s)z78zma8dvOkE5J-QR(DHI-`Uf}*1Mm}`>>b9F=K_ZT@Ub$4e4>Nby z;ec_G%R(r&Sxe#~$&zLkQ=w;KM`bs?DVZ|lpS%%7MdbQ#|B+MX zA;hB{JpD{n2)E1Y>s}N=$wyXGV{)Gny2cM7(RkhNEs`;M$U-qe&+-*2>i8fvJ45t% znD9UU>*+e&21Cf8@x@7*jtIjzC-!Rwo>#-^*v%7TJ!|fJaE&8x_=dnQ7)ssBTOXO{ zY?2?{u3t_1^$iJ!<5GhcPfP1v`?NZwR%UMn=b=0Zbq7@j z`*jU6X4=mET)J9MUrN5