diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml index 5f3983c7f5..48ff9cf0dc 100644 --- a/.github/workflows/build_and_test.yml +++ b/.github/workflows/build_and_test.yml @@ -118,3 +118,10 @@ jobs: run: | mvn -B clean install shell: cmd + + - name: Upload gradle reports + if: ${{ failure() }} + uses: actions/upload-artifact@v2 + with: + name: gradle-test-report-${{ matrix.os }} + path: 'diktat-gradle-plugin/build/reports/' diff --git a/README.md b/README.md index 0cc29e5a45..6cdd05fe59 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ Main features of diktat are the following: # another option is "brew install ktlint" ``` -2. Load diKTat manually: [here](https://github.com/cqfn/diKTat/releases/download/v0.4.0/diktat.jar) +2. Load diKTat manually: [here](https://github.com/cqfn/diKTat/releases/download/v0.4.2/diktat.jar) **OR** use curl: ```bash - $ curl -sSLO https://github.com/cqfn/diKTat/releases/download/v0.4.0/diktat-0.4.0.jar + $ curl -sSLO https://github.com/cqfn/diKTat/releases/download/v0.4.2/diktat-0.4.2.jar ``` 3. Finally, run KTlint (with diKTat injected) to check your `*.kt` files in `dir/your/dir`: @@ -110,7 +110,7 @@ This plugin is available since version 0.1.5. You can see how the plugin is conf Add this plugin to your `build.gradle.kts`: ```kotlin plugins { - id("org.cqfn.diktat.diktat-gradle-plugin") version "0.4.0" + id("org.cqfn.diktat.diktat-gradle-plugin") version "0.4.2" } ``` @@ -121,7 +121,7 @@ buildscript { mavenCentral() } dependencies { - classpath("org.cqfn.diktat:diktat-gradle-plugin:0.4.0") + classpath("org.cqfn.diktat:diktat-gradle-plugin:0.4.2") } } @@ -194,7 +194,7 @@ spotless { ```kotlin spotless { kotlin { - diktat("0.4.0").configFile("full/path/to/diktat-analysis.yml") + diktat("0.4.2").configFile("full/path/to/diktat-analysis.yml") } } ``` @@ -225,7 +225,7 @@ Diktat can be run via spotless-maven-plugin since version 2.8.0 ```xml - 0.4.0 + 0.4.2 full/path/to/diktat-analysis.yml ``` diff --git a/detekt-config.yml b/detekt-config.yml index 4268f5235e..f3e24ba4a3 100644 --- a/detekt-config.yml +++ b/detekt-config.yml @@ -9,6 +9,7 @@ build: config: validation: true + warningsAsErrors: false # when writing own rules with new properties, exclude the property path e.g.: 'my_rule_set,.*>.*>[my_property]' excludes: '' @@ -16,11 +17,18 @@ processors: active: true exclude: - 'DetektProgressListener' + # - 'KtFileCountProcessor' + # - 'PackageCountProcessor' + # - 'ClassCountProcessor' # - 'FunctionCountProcessor' # - 'PropertyCountProcessor' - # - 'ClassCountProcessor' - # - 'PackageCountProcessor' - # - 'KtFileCountProcessor' + # - 'ProjectComplexityProcessor' + # - 'ProjectCognitiveComplexityProcessor' + # - 'ProjectLLOCProcessor' + # - 'ProjectCLOCProcessor' + # - 'ProjectLOCProcessor' + # - 'ProjectSLOCProcessor' + # - 'LicenseHeaderLoaderExtension' console-reports: active: true @@ -31,52 +39,49 @@ console-reports: # - 'FindingsReport' - 'FileBasedFindingsReport' +output-reports: + active: true + exclude: + # - 'TxtOutputReport' + # - 'XmlOutputReport' + # - 'HtmlOutputReport' + comments: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] AbsentOrWrongFileLicense: - excludes: ['**/resources/**'] active: false - licenseTemplateFile: 'license.template' + licenseTemplateIsRegex: false CommentOverPrivateFunction: - excludes: ['**/resources/**'] active: false CommentOverPrivateProperty: - excludes: ['**/resources/**'] active: false EndOfSentenceFormat: - excludes: ['**/resources/**'] active: false endOfSentenceFormat: '([.?!][ \t\n\r\f<])|([.?!:]$)' UndocumentedPublicClass: - excludes: ['**/resources/**'] active: false searchInNestedClass: true searchInInnerClass: true searchInInnerObject: true searchInInnerInterface: true UndocumentedPublicFunction: - excludes: ['**/resources/**'] active: false UndocumentedPublicProperty: - excludes: ['**/resources/**'] active: false complexity: active: true ComplexCondition: - excludes: ['**/resources/**'] active: false threshold: 4 ComplexInterface: - excludes: ['**/resources/**'] active: false threshold: 10 includeStaticDeclarations: false includePrivateDeclarations: false ComplexMethod: - excludes: ['**/resources/**'] active: true threshold: 15 ignoreSingleWhenExpression: false @@ -84,19 +89,15 @@ complexity: ignoreNestingFunctions: false nestingFunctions: [run, let, apply, with, also, use, forEach, isNotNull, ifNull] LabeledExpression: - excludes: ['**/resources/**'] active: false ignoredLabels: [] LargeClass: - excludes: ['**/resources/**'] active: true threshold: 600 LongMethod: - excludes: ['**/resources/**'] active: true threshold: 60 LongParameterList: - excludes: ['**/resources/**'] active: true functionThreshold: 6 constructorThreshold: 7 @@ -104,13 +105,14 @@ complexity: ignoreDataClasses: true ignoreAnnotated: [] MethodOverloading: - excludes: ['**/resources/**'] - active: false + active: true threshold: 6 + NamedArguments: + active: false + threshold: 3 NestedBlockDepth: - excludes: ['**/resources/**'] - active: true - threshold: 5 + active: false + threshold: 4 ReplaceSafeCallChainWithRun: active: false StringLiteralDuplication: @@ -135,88 +137,69 @@ complexity: coroutines: active: true GlobalCoroutineUsage: - excludes: ['**/resources/**'] - active: false + active: true RedundantSuspendModifier: - excludes: ['**/resources/**'] - active: false + active: true + SleepInsteadOfDelay: + active: true SuspendFunWithFlowReturnType: - active: false + active: true empty-blocks: active: true EmptyCatchBlock: - excludes: ['**/resources/**'] active: true allowedExceptionNameRegex: '_|(ignore|expected).*' EmptyClassBlock: - excludes: ['**/resources/**'] active: true EmptyDefaultConstructor: - excludes: ['**/resources/**'] active: true EmptyDoWhileBlock: - excludes: ['**/resources/**'] active: true EmptyElseBlock: - excludes: ['**/resources/**'] active: true EmptyFinallyBlock: - excludes: ['**/resources/**'] active: true EmptyForBlock: - excludes: ['**/resources/**'] active: true EmptyFunctionBlock: - excludes: ['**/resources/**'] active: true ignoreOverridden: false EmptyIfBlock: - excludes: ['**/resources/**'] active: true EmptyInitBlock: - excludes: ['**/resources/**'] active: true EmptyKtFile: - excludes: ['**/resources/**'] active: true EmptySecondaryConstructor: - excludes: ['**/resources/**'] active: true EmptyTryBlock: - excludes: ['**/resources/**'] active: true EmptyWhenBlock: - excludes: ['**/resources/**'] active: true EmptyWhileBlock: - excludes: ['**/resources/**'] active: true exceptions: active: true ExceptionRaisedInUnexpectedLocation: - excludes: ['**/resources/**'] - active: false + active: true methodNames: [toString, hashCode, equals, finalize] InstanceOfCheckForException: active: false excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] NotImplementedDeclaration: - excludes: ['**/resources/**'] - active: false + active: true + ObjectExtendsThrowable: + active: true PrintStackTrace: - excludes: ['**/resources/**'] - active: false + active: true RethrowCaughtException: - excludes: ['**/resources/**'] - active: false + active: true ReturnFromFinally: - excludes: ['**/resources/**'] - active: false + active: true ignoreLabeled: false SwallowedException: - excludes: ['**/resources/**'] active: false ignoredExceptionTypes: - InterruptedException @@ -225,21 +208,18 @@ exceptions: - MalformedURLException allowedExceptionNameRegex: '_|(ignore|expected).*' ThrowingExceptionFromFinally: - excludes: ['**/resources/**'] - active: false + active: true ThrowingExceptionInMain: - excludes: ['**/resources/**'] - active: false + active: true ThrowingExceptionsWithoutMessageOrCause: - active: false + active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] exceptions: - IllegalArgumentException - IllegalStateException - IOException ThrowingNewInstanceOfSameException: - excludes: ['**/resources/**'] - active: false + active: true TooGenericExceptionCaught: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] @@ -254,7 +234,6 @@ exceptions: - Throwable allowedExceptionNameRegex: '_|(ignore|expected).*' TooGenericExceptionThrown: - excludes: ['**/resources/**'] active: true exceptionNames: - Error @@ -267,158 +246,129 @@ formatting: android: false autoCorrect: true AnnotationOnSeparateLine: - excludes: ['**/resources/**'] active: false autoCorrect: true AnnotationSpacing: active: true + autoCorrect: true ArgumentListWrapping: active: true + autoCorrect: true ChainWrapping: - excludes: ['**/resources/**'] active: true autoCorrect: true CommentSpacing: - excludes: ['**/resources/**'] active: true autoCorrect: true EnumEntryNameCase: - excludes: ['**/resources/**'] active: false autoCorrect: true Filename: - excludes: ['**/resources/**'] active: true FinalNewline: - excludes: ['**/resources/**'] active: true autoCorrect: true insertFinalNewLine: true ImportOrdering: - excludes: ['**/resources/**'] active: false autoCorrect: true layout: 'idea' Indentation: - excludes: ['**/resources/**'] active: false autoCorrect: true indentSize: 4 continuationIndentSize: 4 MaximumLineLength: - excludes: ['**/resources/**'] active: true maxLineLength: 180 ModifierOrdering: - excludes: ['**/resources/**'] active: true autoCorrect: true MultiLineIfElse: - excludes: ['**/resources/**'] active: true autoCorrect: true NoBlankLineBeforeRbrace: - excludes: ['**/resources/**'] active: true autoCorrect: true NoConsecutiveBlankLines: - excludes: ['**/resources/**'] active: true autoCorrect: true NoEmptyClassBody: - excludes: ['**/resources/**'] active: true autoCorrect: true NoEmptyFirstLineInMethodBlock: - excludes: ['**/resources/**'] active: false autoCorrect: true NoLineBreakAfterElse: - excludes: ['**/resources/**'] active: true autoCorrect: true NoLineBreakBeforeAssignment: - excludes: ['**/resources/**'] active: true autoCorrect: true NoMultipleSpaces: - excludes: ['**/resources/**'] active: true autoCorrect: true NoSemicolons: - excludes: ['**/resources/**'] active: true autoCorrect: true NoTrailingSpaces: - excludes: ['**/resources/**'] active: true autoCorrect: true NoUnitReturn: - excludes: ['**/resources/**'] active: true autoCorrect: true NoUnusedImports: - excludes: ['**/resources/**'] active: true autoCorrect: true NoWildcardImports: - excludes: ['**/resources/**'] active: true PackageName: - excludes: ['**/resources/**'] active: true autoCorrect: true ParameterListWrapping: - excludes: ['**/resources/**'] active: true autoCorrect: true indentSize: 4 + SpacingAroundAngleBrackets: + active: true + autoCorrect: true SpacingAroundColon: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundComma: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundCurly: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundDot: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundDoubleColon: - excludes: ['**/resources/**'] active: false autoCorrect: true SpacingAroundKeyword: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundOperators: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundParens: - excludes: ['**/resources/**'] active: true autoCorrect: true SpacingAroundRangeOperator: - excludes: ['**/resources/**'] + active: true + autoCorrect: true + SpacingAroundUnaryOperator: active: true autoCorrect: true SpacingBetweenDeclarationsWithAnnotations: - excludes: ['**/resources/**'] active: false autoCorrect: true SpacingBetweenDeclarationsWithComments: - excludes: ['**/resources/**'] active: false autoCorrect: true StringTemplate: - excludes: ['**/resources/**'] active: true autoCorrect: true @@ -465,19 +415,20 @@ naming: excludeClassPattern: '$^' ignoreOverridden: true InvalidPackageDeclaration: - excludes: ['**/resources/**'] active: false + excludes: ['*.kts'] rootPackage: '' MatchingDeclarationName: - excludes: ['**/resources/**'] active: true mustBeFirst: true MemberNameEqualsClassName: - excludes: ['**/resources/**'] active: true ignoreOverridden: true + NoNameShadowing: + active: false NonBooleanPropertyPrefixedWithIs: - active: true + active: false + excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] ObjectPropertyNaming: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] @@ -513,58 +464,50 @@ naming: performance: active: true ArrayPrimitive: - excludes: ['**/resources/**'] active: true ForEachOnRange: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] SpreadOperator: - active: false + active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] UnnecessaryTemporaryInstantiation: - excludes: ['**/resources/**'] active: true potential-bugs: active: true - Deprecation: - excludes: ['**/resources/**'] + CastToNullableType: active: false + Deprecation: + active: true + DontDowncastCollectionTypes: + active: true DuplicateCaseInWhenExpression: - excludes: ['**/resources/**'] active: true EqualsAlwaysReturnsTrueOrFalse: - excludes: ['**/resources/**'] active: true EqualsWithHashCodeExist: - excludes: ['**/resources/**'] active: true + ExitOutsideMain: + active: false ExplicitGarbageCollectionCall: - excludes: ['**/resources/**'] active: true HasPlatformType: - excludes: ['**/resources/**'] active: false IgnoredReturnValue: - excludes: ['**/resources/**'] - active: false + active: true restrictToAnnotatedMethods: true returnValueAnnotations: ['*.CheckReturnValue', '*.CheckResult'] ImplicitDefaultLocale: - excludes: ['**/resources/**'] active: false ImplicitUnitReturnType: - excludes: ['**/resources/**'] active: false allowExplicitReturnType: true InvalidRange: - excludes: ['**/resources/**'] active: true IteratorHasNextCallsNextMethod: - excludes: ['**/resources/**'] active: true IteratorNotThrowingNoSuchElementException: - excludes: ['**/resources/**'] active: true LateinitUsage: active: false @@ -572,39 +515,33 @@ potential-bugs: excludeAnnotatedProperties: [] ignoreOnClassesPattern: '' MapGetWithNotNullAssertionOperator: - excludes: ['**/resources/**'] active: false MissingWhenCase: - excludes: ['**/resources/**'] active: true + allowElseExpression: true NullableToStringCall: active: false RedundantElseInWhen: - excludes: ['**/resources/**'] active: true UnconditionalJumpStatementInLoop: - excludes: ['**/resources/**'] active: false UnnecessaryNotNullOperator: - excludes: ['**/resources/**'] - active: false + active: true UnnecessarySafeCall: - excludes: ['**/resources/**'] + active: true + UnreachableCatchBlock: active: false UnreachableCode: - excludes: ['**/resources/**'] active: true UnsafeCallOnNullableType: - excludes: ['**/resources/**'] active: true UnsafeCast: - excludes: ['**/resources/**'] + active: true + UnusedUnaryOperator: active: false UselessPostfixExpression: - excludes: ['**/resources/**'] active: false WrongEqualsTypeParameter: - excludes: ['**/resources/**'] active: true style: @@ -612,67 +549,58 @@ style: ClassOrdering: active: true CollapsibleIfStatements: - excludes: ['**/resources/**'] active: false DataClassContainsFunctions: - excludes: ['**/resources/**'] active: false conversionFunctionPrefix: 'to' DataClassShouldBeImmutable: - excludes: ['**/resources/**'] active: false + DestructuringDeclarationWithTooManyEntries: + active: true + maxDestructuringEntries: 3 EqualsNullCall: - excludes: ['**/resources/**'] active: true EqualsOnSignatureLine: - excludes: ['**/resources/**'] active: false ExplicitCollectionElementAccessMethod: - excludes: ['**/resources/**'] active: false ExplicitItLambdaParameter: - excludes: ['**/resources/**'] active: false ExpressionBodySyntax: - excludes: ['**/resources/**'] active: false includeLineWrapping: false ForbiddenComment: - excludes: ['**/resources/**'] active: true values: ['TODO:', 'STOPSHIP:'] allowedPatterns: '' ForbiddenImport: - excludes: ['**/resources/**'] active: false imports: [] forbiddenPatterns: '' ForbiddenMethodCall: - excludes: ['**/resources/**'] active: false methods: ['kotlin.io.println', 'kotlin.io.print'] ForbiddenPublicDataClass: - excludes: ['**/resources/**'] - active: false + active: true + excludes: ['**'] ignorePackages: ['*.internal', '*.internal.*'] ForbiddenVoid: - excludes: ['**/resources/**'] active: false ignoreOverridden: false ignoreUsageInGenerics: false FunctionOnlyReturningConstant: - excludes: ['**/resources/**'] active: true ignoreOverridableFunction: true + ignoreActualFunction: true excludedFunctions: 'describeContents' excludeAnnotatedFunction: ['dagger.Provides'] LibraryCodeMustSpecifyReturnType: - excludes: ['**/resources/**'] - active: false + active: true + excludes: ['**'] LibraryEntitiesShouldNotBePublic: - active: false + active: true + excludes: ['**'] LoopWithTooManyJumpStatements: - excludes: ['**/resources/**'] active: true maxJumpCount: 1 MagicNumber: @@ -688,59 +616,46 @@ style: ignoreNamedArgument: true ignoreEnums: false ignoreRanges: false + ignoreExtensionFunctions: true MandatoryBracesIfStatements: - excludes: ['**/resources/**'] active: false MandatoryBracesLoops: - excludes: ['**/resources/**'] active: false MaxLineLength: - excludes: ['**/resources/**'] active: true maxLineLength: 180 excludePackageStatements: true excludeImportStatements: true excludeCommentStatements: false MayBeConst: - excludes: ['**/resources/**'] active: true ModifierOrder: - excludes: ['**/resources/**'] active: true - NestedClassesVisibility: - excludes: ['**/resources/**'] + MultilineLambdaItParameter: active: false + NestedClassesVisibility: + active: true NewLineAtEndOfFile: - excludes: ['**/resources/**'] active: true NoTabs: - excludes: ['**/resources/**'] active: false OptionalAbstractKeyword: - excludes: ['**/resources/**'] active: true OptionalUnit: - excludes: ['**/resources/**'] active: false OptionalWhenBraces: - excludes: ['**/resources/**'] active: false PreferToOverPairSyntax: - excludes: ['**/resources/**'] active: false ProtectedMemberInFinalClass: - excludes: ['**/resources/**'] active: true RedundantExplicitType: - excludes: ['**/resources/**'] active: false RedundantHigherOrderMapUsage: active: true RedundantVisibilityModifierRule: - excludes: ['**/resources/**'] active: false ReturnCount: - excludes: ['**/resources/**'] active: false max: 4 excludedFunctions: 'equals' @@ -748,67 +663,50 @@ style: excludeReturnFromLambda: true excludeGuardClauses: false SafeCast: - excludes: ['**/resources/**'] active: true SerialVersionUIDInSerializableClass: - excludes: ['**/resources/**'] - active: false + active: true SpacingBetweenPackageAndImports: - excludes: ['**/resources/**'] active: false ThrowsCount: - excludes: ['**/resources/**'] active: true max: 2 TrailingWhitespace: - excludes: ['**/resources/**'] active: false UnderscoresInNumericLiterals: - excludes: ['**/resources/**'] active: false acceptableDecimalLength: 5 UnnecessaryAbstractClass: - excludes: ['**/resources/**'] active: true excludeAnnotatedClasses: ['dagger.Module'] UnnecessaryAnnotationUseSiteTarget: - excludes: ['**/resources/**'] active: false UnnecessaryApply: - excludes: ['**/resources/**'] + active: true + UnnecessaryFilter: active: false UnnecessaryInheritance: - excludes: ['**/resources/**'] active: true UnnecessaryLet: - excludes: ['**/resources/**'] active: false UnnecessaryParentheses: - excludes: ['**/resources/**'] active: false UntilInsteadOfRangeTo: - excludes: ['**/resources/**'] active: false UnusedImports: - excludes: ['**/resources/**'] active: false UnusedPrivateClass: - excludes: ['**/resources/**'] active: true UnusedPrivateMember: - excludes: ['**/resources/**'] active: false allowedNames: '(_|ignored|expected|serialVersionUID)' UseArrayLiteralsInAnnotations: - excludes: ['**/resources/**'] active: false UseCheckNotNull: - active: true + active: false UseCheckOrError: - excludes: ['**/resources/**'] active: false UseDataClass: - excludes: ['**/resources/**'] active: false excludeAnnotatedClasses: [] allowVars: false @@ -817,22 +715,21 @@ style: UseIfEmptyOrIfBlank: active: true UseIfInsteadOfWhen: - excludes: ['**/resources/**'] + active: false + UseIsNullOrEmpty: + active: true + UseOrEmpty: active: false UseRequire: - excludes: ['**/resources/**'] active: false UseRequireNotNull: active: true UselessCallOnNotNull: - excludes: ['**/resources/**'] active: true UtilityClassWithPublicConstructor: - excludes: ['**/resources/**'] active: true VarCouldBeVal: - excludes: ['**/resources/**'] - active: false + active: true WildcardImport: active: true excludes: ['**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**'] diff --git a/diktat-analysis.yml b/diktat-analysis.yml index 3c93bfdfff..6ec429c6d7 100644 --- a/diktat-analysis.yml +++ b/diktat-analysis.yml @@ -8,6 +8,7 @@ disabledChapters: "" testDirs: test kotlinVersion: 1.4.30 + srcDirectories: "main" # Checks that the Class/Enum/Interface name does not match Pascal case - name: CLASS_NAME_INCORRECT enabled: true @@ -489,4 +490,7 @@ enabled: true # If file contains class, then it can't contain extension functions for the same class - name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT enabled: true \ No newline at end of file diff --git a/diktat-common/pom.xml b/diktat-common/pom.xml index 9414be5f08..faa40164b7 100644 --- a/diktat-common/pom.xml +++ b/diktat-common/pom.xml @@ -9,7 +9,7 @@ org.cqfn.diktat diktat-parent - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT @@ -25,7 +25,7 @@ com.charleskorn.kaml kaml - 0.27.0 + 0.28.3 commons-cli diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt index 6a9d5ec3a2..b0219179f6 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/reader/ApplicationProperties.kt @@ -10,6 +10,7 @@ import kotlin.system.exitProcess /** * Base class for working with properties files. */ +@Suppress("SwallowedException") open class ApplicationProperties(propertiesFileName: String) { /** * The [Properties] loaded from a file. diff --git a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt index 771e063597..9cc8ac6f61 100644 --- a/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt +++ b/diktat-common/src/main/kotlin/org/cqfn/diktat/common/config/rules/RulesConfigReader.kt @@ -101,7 +101,11 @@ data class CommonConfiguration(private val configuration: Map?) * List of directory names which will be used to detect test sources */ val testAnchors: List by lazy { - (configuration ?: emptyMap()).getOrDefault("testDirs", "test").split(',') + val testDirs = (configuration ?: emptyMap()).getOrDefault("testDirs", "test").split(',') + if (testDirs.any { !it.toLowerCase().endsWith("test") }) { + log.error("test directory names should end with `test`") + } + testDirs } /** @@ -130,6 +134,17 @@ data class CommonConfiguration(private val configuration: Map?) } } + /** + * Get source directories from configuration + */ + val srcDirectories: List by lazy { + val srcDirs = configuration?.get("srcDirectories")?.split(",")?.map { it.trim() } ?: listOf("main") + if (srcDirs.any { !it.toLowerCase().endsWith("main") }) { + log.error("source directory names should end with `main`") + } + srcDirs + } + /** * False if configuration has been read from config file, true if defaults are used */ diff --git a/diktat-gradle-plugin/build.gradle.kts b/diktat-gradle-plugin/build.gradle.kts index daa17dcf97..a19e566b81 100644 --- a/diktat-gradle-plugin/build.gradle.kts +++ b/diktat-gradle-plugin/build.gradle.kts @@ -3,7 +3,7 @@ import org.gradle.nativeplatform.platform.internal.DefaultNativePlatform.getCurr plugins { `java-gradle-plugin` - kotlin("jvm") version "1.4.30" + kotlin("jvm") version "1.4.31" jacoco id("pl.droidsonroids.jacoco.testkit") version "1.0.7" } @@ -61,6 +61,7 @@ tasks.withType { languageVersion = "1.3" apiVersion = "1.3" jvmTarget = "1.8" + useIR = true } dependsOn.add(generateVersionsFile) @@ -98,6 +99,7 @@ tasks.getByName("functionalTest") { testClassesDirs = functionalTest.output.classesDirs classpath = functionalTest.runtimeClasspath maxParallelForks = Runtime.getRuntime().availableProcessors() + maxHeapSize = "1024m" doLast { if (getCurrentOperatingSystem().isWindows) { // workaround for https://github.com/koral--/jacoco-gradle-testkit-plugin/issues/9 diff --git a/diktat-gradle-plugin/gradle-plugin-marker/pom.xml b/diktat-gradle-plugin/gradle-plugin-marker/pom.xml index 15d3e32309..b0d110d91d 100644 --- a/diktat-gradle-plugin/gradle-plugin-marker/pom.xml +++ b/diktat-gradle-plugin/gradle-plugin-marker/pom.xml @@ -4,7 +4,7 @@ diktat-gradle-plugin org.cqfn.diktat - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT 4.0.0 diff --git a/diktat-gradle-plugin/pom.xml b/diktat-gradle-plugin/pom.xml index d49c4baff5..1ea559437c 100644 --- a/diktat-gradle-plugin/pom.xml +++ b/diktat-gradle-plugin/pom.xml @@ -5,7 +5,7 @@ diktat-parent org.cqfn.diktat - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT 4.0.0 diff --git a/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt b/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt index f244e2e035..2c23d98bc9 100644 --- a/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt +++ b/diktat-gradle-plugin/src/functionalTest/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt @@ -41,7 +41,7 @@ internal fun runDiktat(testProjectDir: TemporaryFolder, ) = GradleRunner.create() .run(configureRunner) .withProjectDir(testProjectDir.root) - .withArguments(*arguments.toTypedArray(), DiktatGradlePlugin.DIKTAT_CHECK_TASK) + .withArguments(arguments + DiktatGradlePlugin.DIKTAT_CHECK_TASK) .withPluginClasspath() .withJaCoCo(testsCounter.incrementAndGet()) .forwardOutput() diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt index b705d03d0b..ac809fff15 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/DiktatJavaExecTaskBase.kt @@ -144,7 +144,7 @@ open class DiktatJavaExecTaskBase @Inject constructor( } } else { flag.append("--reporter=plain") - log.warn("Unknown reporter was specified. Falling back to plain reporter.") + log.debug("Unknown reporter was specified. Falling back to plain reporter.") } } diff --git a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt index 58ee144069..20bda86b63 100644 --- a/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt +++ b/diktat-gradle-plugin/src/main/kotlin/org/cqfn/diktat/plugin/gradle/Utils.kt @@ -8,8 +8,14 @@ package org.cqfn.diktat.plugin.gradle import groovy.lang.Closure -@Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_CLASS_ELEMENTS", "KDOC_NO_CONSTRUCTOR_PROPERTY", - "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") +@Suppress( + "MISSING_KDOC_TOP_LEVEL", + "MISSING_KDOC_CLASS_ELEMENTS", + "KDOC_NO_CONSTRUCTOR_PROPERTY", + "MISSING_KDOC_ON_FUNCTION", + "KDOC_WITHOUT_PARAM_TAG", + "KDOC_WITHOUT_RETURN_TAG" +) class KotlinClosure1( val function: T.() -> V?, owner: Any? = null, @@ -21,6 +27,11 @@ class KotlinClosure1( // These two are copy-pasted from `kotlin-dsl` plugin's groovy interop. // Because `kotlin-dsl` depends on kotlin 1.3.x. -@Suppress("MISSING_KDOC_TOP_LEVEL", "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") +@Suppress( + "MISSING_KDOC_TOP_LEVEL", + "MISSING_KDOC_ON_FUNCTION", + "KDOC_WITHOUT_PARAM_TAG", + "KDOC_WITHOUT_RETURN_TAG" +) fun Any.closureOf(action: T.() -> Unit): Closure = KotlinClosure1(action, this, this) diff --git a/diktat-maven-plugin/pom.xml b/diktat-maven-plugin/pom.xml index e97937871a..b6082a3ae7 100644 --- a/diktat-maven-plugin/pom.xml +++ b/diktat-maven-plugin/pom.xml @@ -5,7 +5,7 @@ diktat-parent org.cqfn.diktat - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT 4.0.0 diff --git a/diktat-rules/pom.xml b/diktat-rules/pom.xml index 92df63b1a1..bb753aa0e7 100644 --- a/diktat-rules/pom.xml +++ b/diktat-rules/pom.xml @@ -9,7 +9,7 @@ org.cqfn.diktat diktat-parent - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT diff --git a/diktat-rules/src/main/kotlin/generated/WarningNames.kt b/diktat-rules/src/main/kotlin/generated/WarningNames.kt index fdfc9db868..5e7861546d 100644 --- a/diktat-rules/src/main/kotlin/generated/WarningNames.kt +++ b/diktat-rules/src/main/kotlin/generated/WarningNames.kt @@ -5,6 +5,8 @@ package generated import kotlin.String public object WarningNames { + public const val DUMMY_TEST_WARNING: String = "DUMMY_TEST_WARNING" + public const val PACKAGE_NAME_MISSING: String = "PACKAGE_NAME_MISSING" public const val PACKAGE_NAME_INCORRECT_CASE: String = "PACKAGE_NAME_INCORRECT_CASE" @@ -251,4 +253,6 @@ public object WarningNames { public const val INLINE_CLASS_CAN_BE_USED: String = "INLINE_CLASS_CAN_BE_USED" public const val EXTENSION_FUNCTION_WITH_CLASS: String = "EXTENSION_FUNCTION_WITH_CLASS" + + public const val RUN_IN_SCRIPT: String = "RUN_IN_SCRIPT" } 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 dd3fb7a7ac..52031ff781 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 @@ -6,7 +6,9 @@ import org.cqfn.diktat.common.config.rules.isRuleEnabled import org.cqfn.diktat.ruleset.utils.hasSuppress import org.jetbrains.kotlin.com.intellij.lang.ASTNode -typealias EmitType = ((offset: Int, errorMessage: String, canBeAutoCorrected: Boolean) -> Unit) +typealias EmitType = ((offset: Int, + errorMessage: String, + canBeAutoCorrected: Boolean) -> Unit) typealias ListOfList = MutableList> @@ -18,11 +20,20 @@ typealias ListOfPairs = MutableList> * @property canBeAutoCorrected whether this inspection can automatically fix the code * @property ruleId number of the inspection according to []diktat code style](https://www.cqfn.org/diKTat/info/guide/diktat-coding-convention.html) */ -@Suppress("ForbiddenComment", "MagicNumber", "WRONG_DECLARATIONS_ORDER", "MaxLineLength") +@Suppress( + "ForbiddenComment", + "MagicNumber", + "WRONG_DECLARATIONS_ORDER", + "MaxLineLength", + "WRONG_NEWLINES" +) enum class Warnings( val canBeAutoCorrected: Boolean, val ruleId: String, private val warn: String) : Rule { + // ======== dummy test warning ====== + DUMMY_TEST_WARNING(true, "0.0.0", "this is a dummy warning that can be used for manual testing of fixer/checker"), + // ======== chapter 1 ======== 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"), @@ -104,7 +115,7 @@ enum class Warnings( COMPLEX_EXPRESSION(false, "3.6.3", "complex dot qualified expression should be replaced with variable"), // FixMe: autofixing will be added for this rule - STRING_CONCATENATION(false, "3.15.1", "strings should not be concatenated using plus operator - use string templates instead if the statement fits one line"), + STRING_CONCATENATION(true, "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"), @@ -160,6 +171,7 @@ enum class Warnings( OBJECT_IS_PREFERRED(true, "6.4.2", "it is better to use object for stateless classes"), INLINE_CLASS_CAN_BE_USED(true, "6.1.12", "inline class can be used"), EXTENSION_FUNCTION_WITH_CLASS(false, "6.2.3", "do not use extension functions for the class defined in the same file"), + RUN_IN_SCRIPT(true, "6.5.1", "wrap blocks of code in top-level scope functions like `run`"), ; /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/dummy/DummyWarning.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/dummy/DummyWarning.kt new file mode 100644 index 0000000000..d7bb77507b --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/dummy/DummyWarning.kt @@ -0,0 +1,24 @@ +package org.cqfn.diktat.ruleset.dummy + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.DiktatRule +import org.jetbrains.kotlin.com.intellij.lang.ASTNode + +/** + * Dummy warning used for testing and debug purposes. + * Can be used in manual testing. + */ +class DummyWarning(configRules: List) : DiktatRule( + "dummy-rule", + configRules, + listOf( + Warnings.FILE_NAME_INCORRECT, + Warnings.FILE_NAME_MATCH_CLASS + ) +) { + private lateinit var filePath: String + + @Suppress("EmptyFunctionBlock") + override fun logic(node: ASTNode) {} +} 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 56bd730e75..f97829a82d 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 @@ -4,6 +4,7 @@ import org.cqfn.diktat.common.config.rules.DIKTAT_COMMON import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.RulesConfigReader import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.dummy.DummyWarning import org.cqfn.diktat.ruleset.rules.chapter1.FileNaming import org.cqfn.diktat.ruleset.rules.chapter1.IdentifierNaming import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming @@ -62,6 +63,7 @@ import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsInFileRule import org.cqfn.diktat.ruleset.rules.chapter6.ExtensionFunctionsSameNameRule import org.cqfn.diktat.ruleset.rules.chapter6.ImplicitBackingPropertyRule import org.cqfn.diktat.ruleset.rules.chapter6.PropertyAccessorFields +import org.cqfn.diktat.ruleset.rules.chapter6.RunInScript import org.cqfn.diktat.ruleset.rules.chapter6.TrivialPropertyAccessors import org.cqfn.diktat.ruleset.rules.chapter6.UselessSupertype import org.cqfn.diktat.ruleset.rules.chapter6.classes.AbstractClassesRule @@ -103,7 +105,10 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS it?.takeIf { File(it).exists() } } - @Suppress("LongMethod", "TOO_LONG_FUNCTION") + @Suppress( + "LongMethod", + "TOO_LONG_FUNCTION", + "SpreadOperator") override fun get(): RuleSet { log.debug("Will run $DIKTAT_RULE_SET_ID with $diktatConfigFile" + " (it can be placed to the run directory or the default file from resources will be used)") @@ -133,6 +138,9 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS // We don't have a way to enforce a specific order, so we should just be careful when adding new rules to this list and, when possible, // cover new rules in smoke test as well. If a rule needs to be at a specific position in a list, please add comment explaining it (like for NewlinesRule). val rules = listOf( + // test warning that can be used for manual testing of diktat + ::DummyWarning, + // comments & documentation ::CommentsRule, ::SingleConstructorRule, // this rule can add properties to a primary constructor, so should be before KdocComments @@ -164,7 +172,6 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::CheckInverseMethodRule, ::StatelessClassesRule, ::ImplicitBackingPropertyRule, - ::StringTemplateFormatRule, ::DataClassesRule, ::LocalVariablesRule, ::SmartCastRule, @@ -180,8 +187,10 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::AnnotationNewLineRule, ::SortRule, ::StringConcatenationRule, + ::StringTemplateFormatRule, ::AccurateCalculationsRule, ::LineLength, + ::RunInScript, ::TypeAliasRule, ::OverloadingArgumentsFunction, ::FunctionLength, @@ -204,7 +213,8 @@ class DiktatRuleSetProvider(private var diktatConfigFile: String = DIKTAT_ANALYS ::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 + ::IndentationRule, // indentation rule should be the last because it fixes formatting after all the changes done by previous rules + ) .map { it.invoke(configRules) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/FileNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/FileNaming.kt index 26fc069de3..8c89d666d8 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/FileNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/FileNaming.kt @@ -26,7 +26,10 @@ import java.io.File * Aggressive: In case file contains only one class on upper level - it should be named with the same name */ @Suppress("ForbiddenComment") -class FileNaming(configRules: List) : DiktatRule("file-naming", configRules, listOf(FILE_NAME_INCORRECT, FILE_NAME_MATCH_CLASS)) { +class FileNaming(configRules: List) : DiktatRule( + "file-naming", + configRules, + listOf(FILE_NAME_INCORRECT, FILE_NAME_MATCH_CLASS)) { private lateinit var filePath: String override fun logic(node: ASTNode) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt index f80c6a6561..36831d91b4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/IdentifierNaming.kt @@ -79,13 +79,15 @@ import org.jetbrains.kotlin.psi.psiUtil.parents * // FixMe: because it fixes only declaration without the usages */ @Suppress("ForbiddenComment", "MISSING_KDOC_CLASS_ELEMENTS") -class IdentifierNaming(configRules: List) : DiktatRule("identifier-naming", configRules, +class IdentifierNaming(configRules: List) : DiktatRule( + "identifier-naming", + configRules, listOf(BACKTICKS_PROHIBITED, VARIABLE_NAME_INCORRECT, VARIABLE_NAME_INCORRECT_FORMAT, CONSTANT_UPPERCASE, VARIABLE_HAS_PREFIX, CONFUSING_IDENTIFIER_NAMING, GENERIC_NAME, CLASS_NAME_INCORRECT, ENUM_VALUE, EXCEPTION_SUFFIX, FUNCTION_BOOLEAN_PREFIX, FUNCTION_NAME_INCORRECT_CASE, IDENTIFIER_LENGTH, OBJECT_NAME_INCORRECT)) { private val allMethodPrefixes by lazy { - if (configuration.allowedBooleanPrefixes.isNullOrEmpty()) { + if (configuration.allowedBooleanPrefixes.isEmpty()) { booleanMethodPrefixes } else { booleanMethodPrefixes + configuration.allowedBooleanPrefixes.filter { it.isNotEmpty() } @@ -148,7 +150,11 @@ class IdentifierNaming(configRules: List) : DiktatRule("identifier- /** * all checks for case and naming for vals/vars/constants */ - @Suppress("SAY_NO_TO_VAR", "TOO_LONG_FUNCTION", "ComplexMethod") + @Suppress( + "SAY_NO_TO_VAR", + "TOO_LONG_FUNCTION", + "ComplexMethod" + ) private fun checkVariableName(node: ASTNode): List { // special case for Destructuring declarations that can be treated as parameters in lambda: var namesOfVariables = extractVariableIdentifiers(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt index 78b0c08dc8..6e22883fbd 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter1/PackageNaming.kt @@ -1,5 +1,6 @@ package org.cqfn.diktat.ruleset.rules.chapter1 +import org.cqfn.diktat.common.config.rules.CommonConfiguration import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getCommonConfiguration import org.cqfn.diktat.ruleset.constants.Warnings.INCORRECT_PACKAGE_SEPARATOR @@ -22,6 +23,7 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.lexer.KtTokens.PACKAGE_KEYWORD import org.slf4j.LoggerFactory + import java.util.concurrent.atomic.AtomicInteger /** @@ -33,7 +35,9 @@ import java.util.concurrent.atomic.AtomicInteger * package a.b.c.D -> then class D should be placed in a/b/c/ directories */ @Suppress("ForbiddenComment", "TOO_MANY_LINES_IN_LAMBDA") -class PackageNaming(configRules: List) : DiktatRule("package-naming", configRules, +class PackageNaming(configRules: List) : DiktatRule( + "package-naming", + configRules, listOf(INCORRECT_PACKAGE_SEPARATOR, PACKAGE_NAME_INCORRECT_CASE, PACKAGE_NAME_MISSING, PACKAGE_NAME_INCORRECT_PATH, PACKAGE_NAME_INCORRECT_PREFIX, PACKAGE_NAME_INCORRECT_SYMBOLS)) { private lateinit var domainName: String @@ -45,7 +49,7 @@ class PackageNaming(configRules: List) : DiktatRule("package-naming if (node.elementType == PACKAGE_DIRECTIVE) { val filePath = node.getRootNode().getFilePath() // calculating package name based on the directory where the file is placed - val realPackageName = calculateRealPackageName(filePath) + val realPackageName = calculateRealPackageName(filePath, configuration) // if node isLeaf - this means that there is no package name declared if (node.isLeaf() && !filePath.isKotlinScript()) { @@ -54,7 +58,7 @@ class PackageNaming(configRules: List) : DiktatRule("package-naming } // getting all identifiers from existing package name into the list like [org, diktat, project] - val wordsInPackageName = node.findAllNodesWithSpecificType(IDENTIFIER) + val wordsInPackageName = node.findAllDescendantsWithSpecificType(IDENTIFIER) // no need to check that packageIdentifiers is empty, because in this case parsing will fail checkPackageName(wordsInPackageName, node) @@ -90,7 +94,7 @@ class PackageNaming(configRules: List) : DiktatRule("package-naming * * @return list with words that are parts of package name like [org, diktat, name] */ - private fun calculateRealPackageName(fileName: String): List { + private fun calculateRealPackageName(fileName: String, configuration: CommonConfiguration): List { val filePathParts = fileName.splitPathToDirs() return if (!filePathParts.contains(PACKAGE_PATH_ANCHOR)) { @@ -102,8 +106,9 @@ class PackageNaming(configRules: List) : DiktatRule("package-naming // 1) getting a path after the base project directory (after "src" directory) // 2) removing src/main/kotlin/java/e.t.c dirs and removing file name // 3) adding company's domain name at the beginning + val allDirs = languageDirNames + configuration.srcDirectories + configuration.testAnchors val fileSubDir = filePathParts.subList(filePathParts.lastIndexOf(PACKAGE_PATH_ANCHOR), filePathParts.size - 1) - .dropWhile { languageDirNames.contains(it) } + .dropWhile { allDirs.contains(it) } // no need to add DOMAIN_NAME to the package name if it is already in path val domainPrefix = if (!fileSubDir.joinToString(PACKAGE_SEPARATOR).startsWith(domainName)) domainName.split(PACKAGE_SEPARATOR) else emptyList() domainPrefix + fileSubDir @@ -257,6 +262,6 @@ class PackageNaming(configRules: List) : DiktatRule("package-naming * Directories that are supposed to be first in sources file paths, relative to [PACKAGE_PATH_ANCHOR]. * For kotlin multiplatform projects directories for targets from [kmmTargets] are supported. */ - val languageDirNames = listOf("src", "main", "test", "java", "kotlin") + kmmTargets.flatMap { listOf("${it}Main", "${it}Test") } + val languageDirNames = listOf("src", "java", "kotlin") + kmmTargets.flatMap { listOf("${it}Main", "${it}Test") } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt index 38d860bcbd..1029eb5330 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/CommentsRule.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.ListOfPairs import org.cqfn.diktat.ruleset.constants.Warnings.COMMENTED_OUT_CODE import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import com.pinterest.ktlint.core.ast.ElementType.BLOCK_COMMENT import com.pinterest.ktlint.core.ast.ElementType.EOL_COMMENT @@ -23,7 +23,10 @@ import org.jetbrains.kotlin.resolve.ImportPath * No commented out code is allowed, including imports. */ @Suppress("ForbiddenComment") -class CommentsRule(configRules: List) : DiktatRule("comments", configRules, listOf(COMMENTED_OUT_CODE)) { +class CommentsRule(configRules: List) : DiktatRule( + "comments", + configRules, + listOf(COMMENTED_OUT_CODE)) { private lateinit var ktPsiFactory: KtPsiFactory override fun logic(node: ASTNode) { @@ -49,7 +52,7 @@ class CommentsRule(configRules: List) : DiktatRule("comments", conf val errorNodesWithText: ListOfPairs = mutableListOf() val eolCommentsOffsetToText = getOffsetsToTextBlocksFromEolComments(node, errorNodesWithText) val blockCommentsOffsetToText = node - .findAllNodesWithSpecificType(BLOCK_COMMENT) + .findAllDescendantsWithSpecificType(BLOCK_COMMENT) .map { errorNodesWithText.add(it to it.text.trim().removeSurrounding("/*", "*/")) it.startOffset to it.text.trim().removeSurrounding("/*", "*/") @@ -77,7 +80,7 @@ class CommentsRule(configRules: List) : DiktatRule("comments", conf } .filter { (_, parsedNode) -> parsedNode - .findAllNodesWithSpecificType(TokenType.ERROR_ELEMENT) + .findAllDescendantsWithSpecificType(TokenType.ERROR_ELEMENT) .isEmpty() } .forEach { (offset, parsedNode) -> @@ -94,7 +97,7 @@ class CommentsRule(configRules: List) : DiktatRule("comments", conf */ private fun getOffsetsToTextBlocksFromEolComments(node: ASTNode, errorNodesWithText: ListOfPairs): List> { val comments = node - .findAllNodesWithSpecificType(EOL_COMMENT) + .findAllDescendantsWithSpecificType(EOL_COMMENT) .filter { !it.text.contains(eolCommentStart) || isCodeAfterCommentStart(it.text) } return if (comments.isNotEmpty()) { val result = mutableListOf(mutableListOf(comments.first())) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt index 05c5a95469..0b7541f012 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/comments/HeaderCommentRule.kt @@ -39,7 +39,9 @@ import java.time.LocalDate * 4) Ensure files with many or zero classes have proper description */ @Suppress("ForbiddenComment") -class HeaderCommentRule(configRules: List) : DiktatRule("header-comment", configRules, +class HeaderCommentRule(configRules: List) : DiktatRule( + "header-comment", + configRules, listOf(HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE, HEADER_MISSING_OR_WRONG_COPYRIGHT, HEADER_NOT_BEFORE_PACKAGE, HEADER_NOT_BEFORE_PACKAGE, HEADER_WRONG_FORMAT, WRONG_COPYRIGHT_YEAR)) { override fun logic(node: ASTNode) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt index 97599a127e..0f3192a298 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/CommentsFormatting.kt @@ -49,7 +49,9 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl * * Leave one single space between the comment on the right side of the code and the code. * * Comments in if else should be inside code blocks. Exception: General if comment */ -class CommentsFormatting(configRules: List) : DiktatRule("kdoc-comments-codeblocks-formatting", configRules, +class CommentsFormatting(configRules: List) : DiktatRule( + "kdoc-comments-codeblocks-formatting", + configRules, listOf(COMMENT_WHITE_SPACE, FIRST_COMMENT_NO_BLANK_LINE, IF_ELSE_COMMENTS, WRONG_NEWLINES_AROUND_KDOC)) { /** @@ -268,10 +270,10 @@ class CommentsFormatting(configRules: List) : DiktatRule("kdoc-comm EOL_COMMENT -> (node as LeafPsiElement).replaceWithText("// $commentText") BLOCK_COMMENT -> (node as LeafPsiElement).replaceWithText("/* $commentText") KDOC -> { - node.findAllNodesWithSpecificType(KDOC_TEXT).forEach { + node.findAllDescendantsWithSpecificType(KDOC_TEXT).forEach { modifyKdocText(it, configuration) } - node.findAllNodesWithSpecificType(KDOC_CODE_BLOCK_TEXT).forEach { + node.findAllDescendantsWithSpecificType(KDOC_CODE_BLOCK_TEXT).forEach { modifyKdocText(it, configuration) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt index d6cd89709c..96842d2c7f 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocComments.kt @@ -44,7 +44,9 @@ import org.jetbrains.kotlin.psi.psiUtil.parents * 2) All internal elements in class like class, property or function should be documented with KDoc * 3) All properties declared in the primary constructor are documented using `@property` tag in class KDoc */ -class KdocComments(configRules: List) : DiktatRule("kdoc-comments", configRules, +class KdocComments(configRules: List) : DiktatRule( + "kdoc-comments", + configRules, listOf(KDOC_EXTRA_PROPERTY, KDOC_NO_CONSTRUCTOR_PROPERTY, KDOC_NO_CONSTRUCTOR_PROPERTY_WITH_COMMENT, MISSING_KDOC_CLASS_ELEMENTS, MISSING_KDOC_TOP_LEVEL)) { /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocFormatting.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocFormatting.kt index 5fd4df686b..5d85013673 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocFormatting.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocFormatting.kt @@ -59,7 +59,9 @@ import java.time.temporal.ChronoField * 7) ensuring @since tag contains only versions and not dates */ @Suppress("ForbiddenComment") -class KdocFormatting(configRules: List) : DiktatRule("kdoc-formatting", configRules, +class KdocFormatting(configRules: List) : DiktatRule( + "kdoc-formatting", + configRules, listOf(KDOC_CONTAINS_DATE_OR_AUTHOR, KDOC_EMPTY_KDOC, KDOC_NEWLINES_BEFORE_BASIC_TAGS, KDOC_NO_DEPRECATED_TAG, KDOC_NO_EMPTY_TAGS, KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS, KDOC_NO_NEWLINE_AFTER_SPECIAL_TAGS, KDOC_WRONG_SPACES_AFTER_TAG, KDOC_WRONG_TAGS_ORDER)) { @@ -168,7 +170,7 @@ class KdocFormatting(configRules: List) : DiktatRule("kdoc-formatti } ) - @Suppress("UnsafeCallOnNullableType") + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkBasicTagsOrder(node: ASTNode) { val kdocTags = node.kDocTags() // distinct basic tags which are present in current KDoc, in proper order @@ -200,10 +202,13 @@ class KdocFormatting(configRules: List) : DiktatRule("kdoc-formatti .filter { basicTagsOrdered.contains(it.knownTag) } .map { it.node } - basicTagsOrdered.forEachIndexed { index, tag -> - val tagNode = kdocTags.find { it.knownTag == tag }!!.node - kdocSection.addChild(tagNode.clone() as CompositeElement, basicTagChildren[index]) - kdocSection.removeChild(basicTagChildren[index]) + val correctKdocOrder = basicTags + .sortedBy { basicTagsOrdered.indexOf(it.knownTag) } + .map { it.node } + + basicTagChildren.mapIndexed { index, astNode -> + kdocSection.addChild(correctKdocOrder[index].clone() as CompositeElement, astNode) + kdocSection.removeChild(astNode) } } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt index a0fd9878d3..fda5c905e0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter2/kdoc/KdocMethods.kt @@ -10,7 +10,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_ON_FUNCTION import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.KotlinParser import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.findChildAfter import org.cqfn.diktat.ruleset.utils.getBodyLines import org.cqfn.diktat.ruleset.utils.getFilePath @@ -65,7 +65,9 @@ import org.jetbrains.kotlin.psi.psiUtil.referenceExpression * Currently only `throw` keyword from this methods body is supported for `@throws` check. */ @Suppress("ForbiddenComment") -class KdocMethods(configRules: List) : DiktatRule("kdoc-methods", configRules, +class KdocMethods(configRules: List) : DiktatRule( + "kdoc-methods", + configRules, listOf(KDOC_TRIVIAL_KDOC_ON_FUNCTION, KDOC_WITHOUT_PARAM_TAG, KDOC_WITHOUT_RETURN_TAG, KDOC_WITHOUT_THROWS_TAG, MISSING_KDOC_ON_FUNCTION)) { /** @@ -153,7 +155,7 @@ class KdocMethods(configRules: List) : DiktatRule("kdoc-methods", c private fun getExplicitlyThrownExceptions(node: ASTNode): Set { val codeBlock = node.getFirstChildWithType(BLOCK) - val throwKeywords = codeBlock?.findAllNodesWithSpecificType(THROW) + val throwKeywords = codeBlock?.findAllDescendantsWithSpecificType(THROW) return throwKeywords ?.map { it.psi as KtThrowExpression } ?.mapNotNull { it.thrownExpression?.referenceExpression()?.text } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/AnnotationNewLineRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/AnnotationNewLineRule.kt index dbd1f16b1a..ec025d6f15 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/AnnotationNewLineRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/AnnotationNewLineRule.kt @@ -19,7 +19,10 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl /** * This rule makes each annotation applied to a class, method or constructor is on its own line. Except: if first annotation of constructor, class or method */ -class AnnotationNewLineRule(configRules: List) : DiktatRule("annotation-new-line", configRules, listOf(ANNOTATION_NEW_LINE)) { +class AnnotationNewLineRule(configRules: List) : DiktatRule( + "annotation-new-line", + configRules, + listOf(ANNOTATION_NEW_LINE)) { override fun logic(node: ASTNode) { when (node.elementType) { CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt index fd1b254786..a2b5b43144 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BlockStructureBraces.kt @@ -7,6 +7,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.BRACES_BLOCK_STRUCTURE_ERROR import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.* +import com.pinterest.ktlint.core.ast.ElementType import com.pinterest.ktlint.core.ast.ElementType.BLOCK import com.pinterest.ktlint.core.ast.ElementType.BODY import com.pinterest.ktlint.core.ast.ElementType.CATCH @@ -49,7 +50,10 @@ import org.jetbrains.kotlin.psi.KtTryExpression * - opening brace of lambda * - braces around `else`/`catch`/`finally`/`while` (in `do-while` loop) */ -class BlockStructureBraces(configRules: List) : DiktatRule("block-structure", configRules, listOf(BRACES_BLOCK_STRUCTURE_ERROR)) { +class BlockStructureBraces(configRules: List) : DiktatRule( + "block-structure", + configRules, + listOf(BRACES_BLOCK_STRUCTURE_ERROR)) { override fun logic(node: ASTNode) { val configuration = BlockStructureBracesConfiguration( configRules.getRuleConfig(BRACES_BLOCK_STRUCTURE_ERROR)?.configuration ?: emptyMap() @@ -88,7 +92,7 @@ class BlockStructureBraces(configRules: List) : DiktatRule("block-s val catchBlocks = tryBlock.catchClauses.map { it.node } val finallyBlock = tryBlock.finallyBlock?.node checkOpenBraceOnSameLine(tryBlock.node, BLOCK, configuration) - val allMiddleSpaceNodes = node.findAllNodesWithSpecificType(CATCH).map { it.treePrev } + val allMiddleSpaceNodes = node.findAllDescendantsWithSpecificType(CATCH).map { it.treePrev } checkMidBrace(allMiddleSpaceNodes, node, CATCH_KEYWORD) catchBlocks.forEach { checkOpenBraceOnSameLine(it, BLOCK, configuration) @@ -97,7 +101,7 @@ class BlockStructureBraces(configRules: List) : DiktatRule("block-s finallyBlock?.let { block -> checkOpenBraceOnSameLine(block, BLOCK, configuration) checkCloseBrace(block.findChildByType(BLOCK)!!, configuration) - val newAllMiddleSpaceNodes = node.findAllNodesWithSpecificType(FINALLY).map { it.treePrev } + val newAllMiddleSpaceNodes = node.findAllDescendantsWithSpecificType(FINALLY).map { it.treePrev } checkMidBrace(newAllMiddleSpaceNodes, node, FINALLY_KEYWORD) } } @@ -230,6 +234,11 @@ class BlockStructureBraces(configRules: List) : DiktatRule("block-s return } val space = node.findChildByType(RBRACE)!!.treePrev + node.findParentNodeWithSpecificType(ElementType.LAMBDA_ARGUMENT)?.let { + if (space.text.isEmpty()) { + return + } + } if (checkBraceNode(space)) { BRACES_BLOCK_STRUCTURE_ERROR.warnAndFix(configRules, emitWarn, isFixMode, "no newline before closing brace", (space.treeNext ?: node.findChildByType(RBRACE))!!.startOffset, node) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BracesInConditionalsAndLoopsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BracesInConditionalsAndLoopsRule.kt index d03b9a7dab..40ce08799a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BracesInConditionalsAndLoopsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/BracesInConditionalsAndLoopsRule.kt @@ -34,7 +34,10 @@ import org.jetbrains.kotlin.psi.psiUtil.astReplace /** * Rule that checks that all conditionals and loops have braces. */ -class BracesInConditionalsAndLoopsRule(configRules: List) : DiktatRule("braces-rule", configRules, listOf(NO_BRACES_IN_CONDITIONALS_AND_LOOPS)) { +class BracesInConditionalsAndLoopsRule(configRules: List) : DiktatRule( + "braces-rule", + configRules, + listOf(NO_BRACES_IN_CONDITIONALS_AND_LOOPS)) { override fun logic(node: ASTNode) { when (node.elementType) { IF -> checkIfNode(node) @@ -47,7 +50,12 @@ class BracesInConditionalsAndLoopsRule(configRules: List) : DiktatR /** * Check braces in if-else statements. Check for both IF and ELSE needs to be done in one method to discover single-line if-else statements correctly. */ - @Suppress("ForbiddenComment", "UnsafeCallOnNullableType", "ComplexMethod", "TOO_LONG_FUNCTION") + @Suppress( + "ForbiddenComment", + "UnsafeCallOnNullableType", + "ComplexMethod", + "TOO_LONG_FUNCTION" + ) private fun checkIfNode(node: ASTNode) { val ifPsi = node.psi as KtIfExpression val thenNode = ifPsi.then?.node diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt index d3ef6941cc..12a6d7ba4f 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ClassLikeStructuresOrderRule.kt @@ -41,7 +41,9 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings /** * Rule that checks order of declarations inside classes, interfaces and objects. */ -class ClassLikeStructuresOrderRule(configRules: List) : DiktatRule("class-like-structures", configRules, +class ClassLikeStructuresOrderRule(configRules: List) : DiktatRule( + "class-like-structures", + configRules, listOf(BLANK_LINE_BETWEEN_PROPERTIES, WRONG_ORDER_IN_CLASS_LIKE_STRUCTURES)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS_BODY) { @@ -51,7 +53,12 @@ class ClassLikeStructuresOrderRule(configRules: List) : DiktatRule( } } - @Suppress("UnsafeCallOnNullableType", "LongMethod", "ComplexMethod", "TOO_LONG_FUNCTION") + @Suppress( + "UnsafeCallOnNullableType", + "LongMethod", + "ComplexMethod", + "TOO_LONG_FUNCTION" + ) private fun checkDeclarationsOrderInClass(node: ASTNode) { val allProperties = node.getChildren(TokenSet.create(PROPERTY)) val constProperties = allProperties.filter { it.findLeafWithSpecificType(CONST_KEYWORD) != null }.toMutableList() @@ -73,7 +80,7 @@ class ClassLikeStructuresOrderRule(configRules: List) : DiktatRule( node .parents() .last() - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .any { ref -> ref.parent({ it == classNode }) == null && ref.text.contains(identifierNode.text) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ConsecutiveSpacesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ConsecutiveSpacesRule.kt index caac371b96..73411dc604 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ConsecutiveSpacesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/ConsecutiveSpacesRule.kt @@ -20,7 +20,10 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafElement * 2) If saveInitialFormattingForEnums is true then white spaces in enums will not be affected * */ -class ConsecutiveSpacesRule(configRules: List) : DiktatRule("too-many-spaces", configRules, listOf(TOO_MANY_CONSECUTIVE_SPACES)) { +class ConsecutiveSpacesRule(configRules: List) : DiktatRule( + "too-many-spaces", + configRules, + listOf(TOO_MANY_CONSECUTIVE_SPACES)) { override fun logic(node: ASTNode) { val configuration = TooManySpacesRuleConfiguration( configRules.getRuleConfig(TOO_MANY_CONSECUTIVE_SPACES)?.configuration ?: emptyMap()) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EmptyBlock.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EmptyBlock.kt index 4525e11ea7..8a1dd69960 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EmptyBlock.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EmptyBlock.kt @@ -5,27 +5,33 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_BLOCK_STRUCTURE_ERROR import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findLBrace -import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType -import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType -import org.cqfn.diktat.ruleset.utils.hasParent -import org.cqfn.diktat.ruleset.utils.isBlockEmpty -import org.cqfn.diktat.ruleset.utils.isOverridden -import org.cqfn.diktat.ruleset.utils.isPascalCase +import org.cqfn.diktat.ruleset.utils.* +import com.pinterest.ktlint.core.ast.ElementType import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION +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.FUNCTION_LITERAL import com.pinterest.ktlint.core.ast.ElementType.IDENTIFIER +import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.LPAR import com.pinterest.ktlint.core.ast.ElementType.RBRACE +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST +import com.pinterest.ktlint.core.ast.ElementType.VALUE_PARAMETER import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE 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 +import org.jetbrains.kotlin.psi.psiUtil.parents /** * Rule that checks if empty code blocks (`{ }`) are used and checks their formatting. */ -class EmptyBlock(configRules: List) : DiktatRule("empty-block-structure", configRules, listOf(EMPTY_BLOCK_STRUCTURE_ERROR)) { +class EmptyBlock(configRules: List) : DiktatRule( + "empty-block-structure", + configRules, + listOf(EMPTY_BLOCK_STRUCTURE_ERROR)) { override fun logic(node: ASTNode) { val configuration = EmptyBlockStyleConfiguration( configRules.getRuleConfig(EMPTY_BLOCK_STRUCTURE_ERROR)?.configuration ?: emptyMap() @@ -38,9 +44,12 @@ class EmptyBlock(configRules: List) : DiktatRule("empty-block-struc checkEmptyBlock(newNode, configuration) } - @Suppress("UnsafeCallOnNullableType") + private fun isNewLine(node: ASTNode) = + node.findChildByType(WHITE_SPACE)?.text?.contains("\n") ?: false + + @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION") private fun checkEmptyBlock(node: ASTNode, configuration: EmptyBlockStyleConfiguration) { - if (node.treeParent.isOverridden() || isAnonymousSamClass(node)) { + if (node.treeParent.isOverridden() || isAnonymousSamClass(node) || isLambdaUsedAsFunction(node)) { return } if (node.isBlockEmpty()) { @@ -48,6 +57,19 @@ class EmptyBlock(configRules: List) : DiktatRule("empty-block-struc EMPTY_BLOCK_STRUCTURE_ERROR.warn(configRules, emitWarn, isFixMode, "empty blocks are forbidden unless it is function with override keyword", node.startOffset, node) } else { + node.findParentNodeWithSpecificType(ElementType.LAMBDA_ARGUMENT)?.let { + // Lambda body is always has a BLOCK -> run { } - (LBRACE, WHITE_SPACE, BLOCK "", RBRACE) + if (isNewLine(node)) { + val freeText = "do not put newlines in empty lambda" + EMPTY_BLOCK_STRUCTURE_ERROR.warnAndFix(configRules, emitWarn, isFixMode, freeText, node.startOffset, node) { + val whiteSpaceNode = node.findChildByType(WHITE_SPACE) + whiteSpaceNode?.let { + node.replaceChild(whiteSpaceNode, PsiWhiteSpaceImpl(" ")) + } + } + } + return + } val space = node.findChildByType(RBRACE)!!.treePrev if (configuration.emptyBlockNewline && !space.text.contains("\n")) { EMPTY_BLOCK_STRUCTURE_ERROR.warnAndFix(configRules, emitWarn, isFixMode, "different style for empty block", @@ -68,11 +90,11 @@ class EmptyBlock(configRules: List) : DiktatRule("empty-block-struc } } - @Suppress("UnsafeCallOnNullableType", "WRONG_WHITESPACE") - private fun isAnonymousSamClass(node: ASTNode) : Boolean = + @Suppress("UnsafeCallOnNullableType") + private fun isAnonymousSamClass(node: ASTNode): Boolean = if (node.elementType == FUNCTION_LITERAL && node.hasParent(CALL_EXPRESSION)) { - // We are checking identifier because it is not class in AST - // , SAM conversions are indistinguishable from lambdas. + // We are checking identifier because it is not class in AST, + // SAM conversions are indistinguishable from lambdas. // So we just verify that identifier is in PascalCase val valueArgument = node.findParentNodeWithSpecificType(CALL_EXPRESSION)!! valueArgument.findLeafWithSpecificType(IDENTIFIER)?.text?.isPascalCase() ?: false @@ -80,6 +102,25 @@ class EmptyBlock(configRules: List) : DiktatRule("empty-block-struc false } + @Suppress("UnsafeCallOnNullableType") + private fun isLambdaUsedAsFunction(node: ASTNode): Boolean { + val parents = node.parents() + return when { + parents.any { it.elementType == CALL_EXPRESSION } -> { + val callExpression = parents.find { it.elementType == CALL_EXPRESSION }!! + // excepting cases like list.map { }. In this case call expression will not have value argument list + // And in this case: Parser.parse({}, some, thing) it will have value argument list + callExpression.hasChildOfType(VALUE_ARGUMENT_LIST) + } + parents.any { it.elementType == LAMBDA_EXPRESSION } -> { + val lambdaExpression = parents.find { it.elementType == LAMBDA_EXPRESSION }!! + // cases like A({}). Here Lambda expression is used as a value parameter. + lambdaExpression.treeParent.elementType == VALUE_PARAMETER + } + else -> false + } + } + /** * [RuleConfiguration] for empty blocks formatting */ diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt index cbe6e1c8e9..fe607214c4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/EnumsSeparated.kt @@ -26,7 +26,10 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.PsiWhiteSpaceImpl /** * Rule that checks enum classes formatting */ -class EnumsSeparated(configRules: List) : DiktatRule("enum-separated", configRules, listOf(ENUMS_SEPARATED)) { +class EnumsSeparated(configRules: List) : DiktatRule( + "enum-separated", + configRules, + listOf(ENUMS_SEPARATED)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS && node.hasChildOfType(CLASS_BODY)) { if (node.isClassEnum()) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt index 777ab27ba4..f35eff56be 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LineLength.kt @@ -57,7 +57,10 @@ import java.net.URL * Rule can fix long binary expressions in condition inside `if` and in property declarations and one line functions */ @Suppress("ForbiddenComment") -class LineLength(configRules: List) : DiktatRule("line-length", configRules, listOf(LONG_LINE)) { +class LineLength(configRules: List) : DiktatRule( + "line-length", + configRules, + listOf(LONG_LINE)) { private lateinit var positionByOffset: (Int) -> Pair override fun logic(node: ASTNode) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LongNumericalValuesSeparatedRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LongNumericalValuesSeparatedRule.kt index 93da60e617..556b79cff2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LongNumericalValuesSeparatedRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/LongNumericalValuesSeparatedRule.kt @@ -16,7 +16,10 @@ import java.lang.StringBuilder /** * Rule that checks if numerical separators (`_`) are used for long numerical literals */ -class LongNumericalValuesSeparatedRule(configRules: List) : DiktatRule("long-numerical-values", configRules, listOf(LONG_NUMERICAL_VALUES_SEPARATED)) { +class LongNumericalValuesSeparatedRule(configRules: List) : DiktatRule( + "long-numerical-values", + configRules, + listOf(LONG_NUMERICAL_VALUES_SEPARATED)) { override fun logic(node: ASTNode) { val configuration = LongNumericalValuesConfiguration( configRules.getRuleConfig(LONG_NUMERICAL_VALUES_SEPARATED)?.configuration ?: emptyMap()) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/MultipleModifiersSequence.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/MultipleModifiersSequence.kt index 113c2b9077..a326996341 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/MultipleModifiersSequence.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/MultipleModifiersSequence.kt @@ -15,7 +15,10 @@ import org.jetbrains.kotlin.psi.psiUtil.children /** * @property configRules */ -class MultipleModifiersSequence(configRules: List) : DiktatRule("multiple-modifiers", configRules, listOf(WRONG_MULTIPLE_MODIFIERS_ORDER)) { +class MultipleModifiersSequence(configRules: List) : DiktatRule( + "multiple-modifiers", + configRules, + listOf(WRONG_MULTIPLE_MODIFIERS_ORDER)) { override fun logic(node: ASTNode) { if (node.elementType == MODIFIER_LIST) { checkModifierList(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt index 8215f3b5a5..7d0bb0e8fb 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/NullableTypeRule.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.NULLABLE_PROPERTY_TYPE import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.KotlinParser -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import com.pinterest.ktlint.core.ast.ElementType.BOOLEAN_CONSTANT @@ -37,7 +37,10 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.IElementType /** * Rule that checks if nullable types are used and suggest to substitute them with non-nullable */ -class NullableTypeRule(configRules: List) : DiktatRule("nullable-type", configRules, listOf(NULLABLE_PROPERTY_TYPE)) { +class NullableTypeRule(configRules: List) : DiktatRule( + "nullable-type", + configRules, + listOf(NULLABLE_PROPERTY_TYPE)) { override fun logic(node: ASTNode) { if (node.elementType == PROPERTY) { checkProperty(node) @@ -50,7 +53,7 @@ class NullableTypeRule(configRules: List) : DiktatRule("nullable-ty val typeReferenceNode = node.findChildByType(TYPE_REFERENCE)!! // check that property has nullable type, right value one of allow expression if (!node.hasChildOfType(NULL) && - node.findAllNodesWithSpecificType(DOT_QUALIFIED_EXPRESSION).isEmpty() && + node.findAllDescendantsWithSpecificType(DOT_QUALIFIED_EXPRESSION).isEmpty() && typeReferenceNode.hasChildOfType(NULLABLE_TYPE) && typeReferenceNode.findChildByType(NULLABLE_TYPE)!!.hasChildOfType(QUEST) && (node.findChildByType(CALL_EXPRESSION)?.findChildByType(REFERENCE_EXPRESSION) == null || diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SingleLineStatementsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SingleLineStatementsRule.kt index 806dafa989..a4fc8ec5e4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SingleLineStatementsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SingleLineStatementsRule.kt @@ -18,7 +18,10 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet /** * Rule that looks for multiple statements on a single line separated with a `;` and splits them in multiple lines. */ -class SingleLineStatementsRule(configRules: List) : DiktatRule("statement", configRules, listOf(MORE_THAN_ONE_STATEMENT_PER_LINE)) { +class SingleLineStatementsRule(configRules: List) : DiktatRule( + "statement", + configRules, + listOf(MORE_THAN_ONE_STATEMENT_PER_LINE)) { override fun logic(node: ASTNode) { checkSemicolon(node) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt index 935e7805f7..fb1d2a3a90 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/SortRule.kt @@ -5,7 +5,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_DECLARATIONS_ORDER import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import org.cqfn.diktat.ruleset.utils.isClassEnum @@ -31,7 +31,10 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings /** * Rule that sorts class properties and enum members alphabetically */ -class SortRule(configRules: List) : DiktatRule("sort-rule", configRules, listOf(WRONG_DECLARATIONS_ORDER)) { +class SortRule(configRules: List) : DiktatRule( + "sort-rule", + configRules, + listOf(WRONG_DECLARATIONS_ORDER)) { override fun logic(node: ASTNode) { val configuration = SortRuleConfiguration( configRules.getRuleConfig(WRONG_DECLARATIONS_ORDER)?.configuration ?: emptyMap() @@ -78,7 +81,7 @@ class SortRule(configRules: List) : DiktatRule("sort-rule", configR nonSortList: List, node: ASTNode) { val isEnum = nonSortList.first().elementType == ENUM_ENTRY - val spaceBefore = if (node.findAllNodesWithSpecificType(EOL_COMMENT).isNotEmpty() && isEnum) { + val spaceBefore = if (node.findAllDescendantsWithSpecificType(EOL_COMMENT).isNotEmpty() && isEnum) { nonSortList.last().run { if (this.hasChildOfType(EOL_COMMENT) && !this.hasChildOfType(COMMA)) { this.addChild(LeafPsiElement(COMMA, ","), this.findChildByType(EOL_COMMENT)) @@ -139,7 +142,7 @@ class SortRule(configRules: List) : DiktatRule("sort-rule", configR val hasTrailingComma = (sortList.last() != enumEntryList.last() && enumEntryList.last().hasChildOfType(COMMA)) swapSortNodes(sortList, enumEntryList, node) if (!hasTrailingComma) { - val lastEntry = node.findAllNodesWithSpecificType(ENUM_ENTRY).last() + val lastEntry = node.findAllDescendantsWithSpecificType(ENUM_ENTRY).last() lastEntry.removeChild(lastEntry.findChildByType(COMMA)!!) } if (isEndSpace) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt index cdd52d046d..0c3fee3624 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringConcatenationRule.kt @@ -3,23 +3,39 @@ package org.cqfn.diktat.ruleset.rules.chapter3 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.STRING_CONCATENATION import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType -import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType -import org.cqfn.diktat.ruleset.utils.getFirstChildWithType +import org.cqfn.diktat.ruleset.utils.* import com.pinterest.ktlint.core.ast.ElementType.BINARY_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE import com.pinterest.ktlint.core.ast.ElementType.PLUS import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.psi.KtBinaryExpression +import org.jetbrains.kotlin.psi.KtCallExpression +import org.jetbrains.kotlin.psi.KtConstantExpression +import org.jetbrains.kotlin.psi.KtDotQualifiedExpression +import org.jetbrains.kotlin.psi.KtOperationExpression +import org.jetbrains.kotlin.psi.KtParenthesizedExpression +import org.jetbrains.kotlin.psi.KtReferenceExpression +import org.jetbrains.kotlin.psi.KtStringTemplateExpression /** * This rule covers checks and fixes related to string concatenation. * Rule 3.8 prohibits string concatenation and suggests to use string templates instead - * // FixMe: fixes will be added - * // FixMe: .toString() method and functions that return strings are not supported + * if this expressions fits one line. For example: + * """ string """ + "string" will be converted to "string string" + * "string " + 1 will be converted to "string 1" + * "string one " + "string two " */ -class StringConcatenationRule(configRules: List) : DiktatRule("string-concatenation", configRules, listOf(STRING_CONCATENATION)) { +class StringConcatenationRule(configRules: List) : DiktatRule( + "string-concatenation", + configRules, + listOf( + STRING_CONCATENATION + ) +) { override fun logic(node: ASTNode) { if (node.elementType == BINARY_EXPRESSION) { // searching top-level binary expression to detect any operations with "plus" (+) @@ -33,25 +49,56 @@ class StringConcatenationRule(configRules: List) : DiktatRule("stri private fun isSingleLineStatement(node: ASTNode): Boolean = !node.text.contains("\n") - private fun detectStringConcatenation(node: ASTNode) { - node.findAllNodesWithSpecificType(BINARY_EXPRESSION).find { detectStringConcatenationInExpression(it, node) } + /** + * This method works only with top-level binary expressions. It should be checked before the call. + */ + @Suppress("AVOID_NULL_CHECKS") + private fun detectStringConcatenation(topLevelBinaryExpr: ASTNode) { + val allBinaryExpressions = topLevelBinaryExpr.findAllDescendantsWithSpecificType(BINARY_EXPRESSION) + val nodeWithBug = allBinaryExpressions.find { isDetectStringConcatenationInExpression(it) } + + if (nodeWithBug != null) { + STRING_CONCATENATION.warnAndFix( + configRules, emitWarn, + this.isFixMode, topLevelBinaryExpr.text.lines().first(), nodeWithBug.startOffset, nodeWithBug + ) { + fixBinaryExpressionWithConcatenation(nodeWithBug) + loop(topLevelBinaryExpr.treeParent) + } + } } - @Suppress("FUNCTION_BOOLEAN_PREFIX") - private fun detectStringConcatenationInExpression(node: ASTNode, parentNode: ASTNode): Boolean { - assert(node.elementType == BINARY_EXPRESSION) - val firstChild = node.firstChildNode - return if (isPlusBinaryExpression(node) && firstChild.elementType == STRING_TEMPLATE) { - STRING_CONCATENATION.warn(configRules, emitWarn, this.isFixMode, parentNode.text, firstChild.startOffset, firstChild) - true - } else { - false + private fun loop(parentTopLevelBinaryExpr: ASTNode) { + val allBinaryExpressions = parentTopLevelBinaryExpr.findAllDescendantsWithSpecificType(BINARY_EXPRESSION) + val nodeWithBug = allBinaryExpressions.find { isDetectStringConcatenationInExpression(it) } + + val bugDetected = nodeWithBug != null + if (bugDetected) { + fixBinaryExpressionWithConcatenation(nodeWithBug) + loop(parentTopLevelBinaryExpr) } } + /** + * We can detect string concatenation by the first (left) operand in binary expression. + * If it is of type string - then we found string concatenation. + * If the right value is not a constant string then don't change them to template. + */ + private fun isDetectStringConcatenationInExpression(node: ASTNode): Boolean { + require(node.elementType == BINARY_EXPRESSION) { + "cannot process non binary expression in the process of detecting string concatenation" + } + val firstChild = node.firstChildNode + val lastChild = node.lastChildNode + return isPlusBinaryExpression(node) && isStringVar(firstChild, lastChild) + } + + private fun isStringVar(firstChild: ASTNode, lastChild: ASTNode) = firstChild.elementType == STRING_TEMPLATE || + ((firstChild.text.endsWith("toString()")) && firstChild.elementType == DOT_QUALIFIED_EXPRESSION && lastChild.elementType == STRING_TEMPLATE) + @Suppress("COMMENT_WHITE_SPACE") private fun isPlusBinaryExpression(node: ASTNode): Boolean { - assert(node.elementType == BINARY_EXPRESSION) + require(node.elementType == BINARY_EXPRESSION) // binary expression // / | \ // expr1 operationRef expr2 @@ -60,4 +107,112 @@ class StringConcatenationRule(configRules: List) : DiktatRule("stri return operationReference ?.getFirstChildWithType(PLUS) != null } + + private fun fixBinaryExpressionWithConcatenation(node: ASTNode?) { + val binaryExpressionPsi = node?.psi as KtBinaryExpression + val parentNode = node.treeParent + val textNode = checkKtExpression(binaryExpressionPsi) + val newNode = KotlinParser().createNode("\"$textNode\"") + parentNode.replaceChild(node, newNode) + } + + private fun isPlusBinaryExpressionAndFirstElementString(binaryExpressionNode: KtBinaryExpression) = + (binaryExpressionNode.left is KtStringTemplateExpression) && PLUS == binaryExpressionNode.operationToken + + @Suppress( + "TOO_LONG_FUNCTION", + "NESTED_BLOCK", + "SAY_NO_TO_VAR", + "ComplexMethod") + private fun checkKtExpression(binaryExpressionPsi: KtBinaryExpression): String { + var lvalueText = binaryExpressionPsi.left?.text?.trim('"') + val rvalueText = binaryExpressionPsi.right?.text + + if (binaryExpressionPsi.isLvalueDotQualifiedExpression() && binaryExpressionPsi.firstChild.text.endsWith("toString()")) { + // =========== (1 + 2).toString() -> ${(1 + 2)} + val leftText = binaryExpressionPsi.firstChild.firstChild.text + lvalueText = "\${$leftText}" + } + if (binaryExpressionPsi.isLvalueReferenceExpression() || binaryExpressionPsi.isLvalueConstantExpression()) { + return binaryExpressionPsi.text + } + if (binaryExpressionPsi.isLvalueBinaryExpression()) { + val rightValue = checkKtExpression(binaryExpressionPsi.left as KtBinaryExpression) + val rightEx = binaryExpressionPsi.right + val rightVal = if (binaryExpressionPsi.isRvalueParenthesized()) { + checkKtExpression(rightEx?.children?.get(0) as KtBinaryExpression) + } else { + (rightEx?.text?.trim('"')) + } + if (binaryExpressionPsi.left?.text == rightValue) { + return binaryExpressionPsi.text + } + return "$rightValue$rightVal" + } else if (binaryExpressionPsi.isRvalueConstantExpression() || binaryExpressionPsi.isRvalueStringTemplateExpression()) { + // =========== "a " + "b" -> "a b" + val rvalueTextNew = rvalueText?.trim('"') + return "$lvalueText$rvalueTextNew" + } else if (binaryExpressionPsi.isRvalueCallExpression() || binaryExpressionPsi.isRvalueDotQualifiedExpression() || binaryExpressionPsi.isRvalueOperation()) { + // =========== "a " + foo() -> "a ${foo()}}" + return "$lvalueText\${$rvalueText}" + } else if (binaryExpressionPsi.isRvalueReferenceExpression()) { + // =========== "a " + b -> "a $b" + return "$lvalueText$$rvalueText" + } else if (binaryExpressionPsi.isRvalueParenthesized()) { + val binExpression = binaryExpressionPsi.right?.children?.first() + if (binExpression is KtBinaryExpression) { + if (isPlusBinaryExpressionAndFirstElementString(binExpression)) { + val rightValue = checkKtExpression(binExpression) + return "$lvalueText$rightValue" + } else if (binExpression.isLvalueBinaryExpression()) { + val rightValue = checkKtExpression(binExpression.left as KtBinaryExpression) + val rightEx = binExpression.right + val rightVal = if (binExpression.isRvalueParenthesized()) { + checkKtExpression(rightEx?.children?.get(0) as KtBinaryExpression) + } else { + (rightEx?.text?.trim('"')) + } + if (binExpression.left?.text == rightValue) { + return "$lvalueText\${$rvalueText}" + } + return "$lvalueText$rightValue$rightVal" + } + } + return "$lvalueText\${$rvalueText}" + } + return binaryExpressionPsi.text + } + + private fun KtBinaryExpression.isRvalueConstantExpression() = + this.right is KtConstantExpression + + private fun KtBinaryExpression.isRvalueStringTemplateExpression() = + this.right is KtStringTemplateExpression + + private fun KtBinaryExpression.isRvalueCallExpression() = + this.right is KtCallExpression + + private fun KtBinaryExpression.isRvalueReferenceExpression() = + this.right is KtReferenceExpression + + private fun KtBinaryExpression.isRvalueDotQualifiedExpression() = + this.right is KtDotQualifiedExpression + + private fun KtBinaryExpression.isRvalueParenthesized() = + this.right is KtParenthesizedExpression + + private fun KtBinaryExpression.isRvalueOperation() = + this.right is KtOperationExpression + + private fun KtBinaryExpression.isLvalueDotQualifiedExpression() = + this.left is KtDotQualifiedExpression + + private fun KtBinaryExpression.isLvalueBinaryExpression() = + this.left is KtBinaryExpression + + private fun KtBinaryExpression.isLvalueReferenceExpression() = + this.left is KtReferenceExpression + + private fun KtBinaryExpression.isLvalueConstantExpression() = + this.left is KtConstantExpression } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt index 00563eaf29..7d20ed9548 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/StringTemplateFormatRule.kt @@ -1,11 +1,10 @@ package org.cqfn.diktat.ruleset.rules.chapter3 import org.cqfn.diktat.common.config.rules.RulesConfig -import org.cqfn.diktat.ruleset.constants.EmitType import org.cqfn.diktat.ruleset.constants.Warnings.STRING_TEMPLATE_CURLY_BRACES import org.cqfn.diktat.ruleset.constants.Warnings.STRING_TEMPLATE_QUOTES import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasAnyChildOfTypes import com.pinterest.ktlint.core.ast.ElementType.ARRAY_ACCESS_EXPRESSION @@ -28,7 +27,9 @@ import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.LeafPsiElement * * FixMe: The important caveat here: in "$foo" kotlin compiler adds implicit call to foo.toString() in case foo type is not string. */ -class StringTemplateFormatRule(configRules: List) : DiktatRule("string-template-format", configRules, +class StringTemplateFormatRule(configRules: List) : DiktatRule( + "string-template-format", + configRules, listOf(STRING_TEMPLATE_CURLY_BRACES, STRING_TEMPLATE_QUOTES)) { override fun logic(node: ASTNode) { when (node.elementType) { @@ -82,13 +83,13 @@ class StringTemplateFormatRule(configRules: List) : DiktatRule("str @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX") private fun bracesCanBeOmitted(node: ASTNode): Boolean { val onlyOneRefExpr = node - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .singleOrNull() ?.treeParent ?.elementType == LONG_STRING_TEMPLATE_ENTRY val isArrayAccessExpression = node // this should be omitted in previous expression, used for safe warranties - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .singleOrNull() ?.treeParent ?.elementType == ARRAY_ACCESS_EXPRESSION diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/TrailingCommaRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/TrailingCommaRule.kt index acf14c8ad6..c1061ff8cc 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/TrailingCommaRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/TrailingCommaRule.kt @@ -49,7 +49,10 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings * [12] Destructuring declarations */ @Suppress("TOO_LONG_FUNCTION") -class TrailingCommaRule(configRules: List) : DiktatRule("trailing-comma", configRules, listOf(TRAILING_COMMA)) { +class TrailingCommaRule(configRules: List) : DiktatRule( + "trailing-comma", + configRules, + listOf(TRAILING_COMMA)) { private val commonConfig = configRules.getCommonConfiguration() private val trailingConfig = this.configRules.getRuleConfig(TRAILING_COMMA)?.configuration ?: emptyMap() private val configuration by lazy { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt index f40c874d7f..98f289431a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/WhenMustHaveElseRule.kt @@ -36,7 +36,10 @@ import org.jetbrains.kotlin.psi.KtWhenExpression * The compiler can issue a warning when it is missing. */ @Suppress("ForbiddenComment") -class WhenMustHaveElseRule(configRules: List) : DiktatRule("no-else-in-when", configRules, listOf(WHEN_WITHOUT_ELSE)) { +class WhenMustHaveElseRule(configRules: List) : DiktatRule( + "no-else-in-when", + configRules, + listOf(WHEN_WITHOUT_ELSE)) { override fun logic(node: ASTNode) { if (node.elementType == ElementType.WHEN && isStatement(node)) { checkEntries(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/BlankLinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/BlankLinesRule.kt index 2cd8be0c2e..d638f25096 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/BlankLinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/BlankLinesRule.kt @@ -3,6 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter3.files import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.TOO_MANY_BLANK_LINES import org.cqfn.diktat.ruleset.rules.DiktatRule +import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.leaveExactlyNumNewLines import org.cqfn.diktat.ruleset.utils.leaveOnlyOneNewLine @@ -12,6 +13,7 @@ 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_ARGUMENT import com.pinterest.ktlint.core.ast.ElementType.LBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.SCRIPT @@ -23,7 +25,10 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode * 1. Checks that no more than two consecutive blank lines are used in a row * 2. Checks that blank lines are not put in the beginning or at the end of code blocks with curly braces */ -class BlankLinesRule(configRules: List) : DiktatRule("blank-lines", configRules, listOf(TOO_MANY_BLANK_LINES)) { +class BlankLinesRule(configRules: List) : DiktatRule( + "blank-lines", + configRules, + listOf(TOO_MANY_BLANK_LINES)) { override fun logic(node: ASTNode) { if (node.elementType == WHITE_SPACE) { // note that no blank lines counts as one newline @@ -41,6 +46,13 @@ class BlankLinesRule(configRules: List) : DiktatRule("blank-lines", it.elementType == BLOCK && it.treeParent?.elementType != SCRIPT || it.elementType == CLASS_BODY || it.elementType == FUNCTION_LITERAL }) { + node.findParentNodeWithSpecificType(LAMBDA_ARGUMENT)?.let { + // Lambda body is always has a BLOCK -> run { } - (LBRACE, WHITE_SPACE, BLOCK "", RBRACE) + if (node.treeNext.text.isEmpty()) { + return + } + } + 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 diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileSize.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileSize.kt index e9d8086b2b..461c9ec925 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileSize.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileSize.kt @@ -15,7 +15,10 @@ import org.slf4j.LoggerFactory /** * Rule that checks number of lines in a file */ -class FileSize(configRules: List) : DiktatRule("file-size", configRules, listOf(FILE_IS_TOO_LONG)) { +class FileSize(configRules: List) : DiktatRule( + "file-size", + configRules, + listOf(FILE_IS_TOO_LONG)) { private val configuration by lazy { FileSizeConfiguration( this.configRules.getRuleConfig(FILE_IS_TOO_LONG)?.configuration ?: emptyMap() diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt index d0cbb3f466..f1c70a54dc 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/FileStructureRule.kt @@ -14,7 +14,7 @@ import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.rules.chapter1.PackageNaming.Companion.PACKAGE_SEPARATOR import org.cqfn.diktat.ruleset.utils.StandardPlatforms import org.cqfn.diktat.ruleset.utils.copyrightWords -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.handleIncorrectOrder import org.cqfn.diktat.ruleset.utils.moveChildBefore import org.cqfn.diktat.ruleset.utils.operatorMap @@ -55,7 +55,9 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings * 4. Ensures imports are ordered alphabetically without blank lines * 5. Ensures there are no wildcard imports */ -class FileStructureRule(configRules: List) : DiktatRule("file-structure", configRules, +class FileStructureRule(configRules: List) : DiktatRule( + "file-structure", + configRules, listOf(FILE_CONTAINS_ONLY_COMMENTS, FILE_INCORRECT_BLOCKS_ORDER, FILE_NO_BLANK_LINE_BETWEEN_BLOCKS, FILE_UNORDERED_IMPORTS, FILE_WILDCARD_IMPORTS, UNUSED_IMPORT)) { private val domainName by lazy { @@ -106,7 +108,10 @@ class FileStructureRule(configRules: List) : DiktatRule("file-struc return hasCode } - @Suppress("ComplexMethod", "TOO_LONG_FUNCTION") + @Suppress( + "ComplexMethod", + "TOO_LONG_FUNCTION", + "SpreadOperator") private fun checkCodeBlocksOrderAndEmptyLines(node: ASTNode) { // From KtFile.kt: 'scripts have no package directive, all other files must have package directives'. // Kotlin compiler itself enforces it's position in the file if it is present. @@ -245,7 +250,7 @@ class FileStructureRule(configRules: List) : DiktatRule("file-struc } private fun findAllReferences(node: ASTNode) { - node.findAllNodesWithSpecificType(OPERATION_REFERENCE).forEach { ref -> + node.findAllDescendantsWithSpecificType(OPERATION_REFERENCE).forEach { ref -> if (!ref.isPartOf(IMPORT_DIRECTIVE)) { val references = operatorMap.filterValues { it == ref.text } if (references.isNotEmpty()) { @@ -256,7 +261,7 @@ class FileStructureRule(configRules: List) : DiktatRule("file-struc } } } - node.findAllNodesWithSpecificType(REFERENCE_EXPRESSION).forEach { + node.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION).forEach { if (!it.isPartOf(IMPORT_DIRECTIVE)) { // the importedName method removes the quotes, but the node.text method does not refSet.add(it.text.replace("`", "")) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt index 79a3aac6cc..a2c6220c0a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/IndentationRule.kt @@ -33,11 +33,15 @@ import com.pinterest.ktlint.core.ast.ElementType.FILE import com.pinterest.ktlint.core.ast.ElementType.LBRACE import com.pinterest.ktlint.core.ast.ElementType.LBRACKET import com.pinterest.ktlint.core.ast.ElementType.LITERAL_STRING_TEMPLATE_ENTRY +import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY +import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_END +import com.pinterest.ktlint.core.ast.ElementType.LONG_TEMPLATE_ENTRY_START import com.pinterest.ktlint.core.ast.ElementType.LPAR import com.pinterest.ktlint.core.ast.ElementType.RBRACE import com.pinterest.ktlint.core.ast.ElementType.RBRACKET import com.pinterest.ktlint.core.ast.ElementType.RPAR import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.SHORT_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE import com.pinterest.ktlint.core.ast.ElementType.THEN import com.pinterest.ktlint.core.ast.ElementType.WHITE_SPACE @@ -56,6 +60,8 @@ import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf import org.jetbrains.kotlin.psi.psiUtil.startOffset import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult +import java.lang.StringBuilder +import kotlin.math.abs /** * Rule that checks indentation. The following general rules are checked: @@ -64,7 +70,10 @@ import org.jetbrains.kotlin.utils.addToStdlib.firstNotNullResult * Additionally, a set of CustomIndentationChecker objects checks all WHITE_SPACE node if they are exceptions from general rules. * @see CustomIndentationChecker */ -class IndentationRule(configRules: List) : DiktatRule("indentation", configRules, listOf(WRONG_INDENTATION)) { +class IndentationRule(configRules: List) : DiktatRule( + "indentation", + configRules, + listOf(WRONG_INDENTATION)) { private val configuration: IndentationConfig by lazy { IndentationConfig(configRules.getRuleConfig(WRONG_INDENTATION)?.configuration ?: emptyMap()) } @@ -177,14 +186,19 @@ class IndentationRule(configRules: List) : DiktatRule("indentation" } val expectedIndent = checkResult?.expectedIndent ?: indentError.expected - if (checkResult?.adjustNext == true) { + if (checkResult?.adjustNext == true && astNode.parents().none { it.elementType == LONG_STRING_TEMPLATE_ENTRY }) { val exceptionInitiatorNode = astNode.getExceptionalIndentInitiator() context.addException(exceptionInitiatorNode, expectedIndent - indentError.expected, checkResult.includeLastChild) } + + if (astNode.treeParent.elementType == LONG_STRING_TEMPLATE_ENTRY && indentError.expected != indentError.actual) { + context.addException(astNode.treeParent, abs(indentError.expected - indentError.actual), false) + } + if (checkResult?.isCorrect != true && expectedIndent != indentError.actual) { WRONG_INDENTATION.warnAndFix(configRules, emitWarn, isFixMode, "expected $expectedIndent but was ${indentError.actual}", whiteSpace.startOffset + whiteSpace.text.lastIndexOf('\n') + 1, whiteSpace.node) { - checkStringLiteral(whiteSpace, expectedIndent) + checkStringLiteral(whiteSpace, expectedIndent, indentError.actual) whiteSpace.node.indentBy(expectedIndent) } } @@ -193,7 +207,11 @@ class IndentationRule(configRules: List) : DiktatRule("indentation" /** * Checks if it is triple-quoted string template with trimIndent() or trimMargin() function. */ - private fun checkStringLiteral(whiteSpace: PsiWhiteSpace, expectedIndent: Int) { + private fun checkStringLiteral( + whiteSpace: PsiWhiteSpace, + expectedIndent: Int, + actualIndent: Int + ) { val nextNode = whiteSpace.node.treeNext if (nextNode != null && nextNode.elementType == DOT_QUALIFIED_EXPRESSION && @@ -203,19 +221,23 @@ class IndentationRule(configRules: List) : DiktatRule("indentation" it == "trimIndent()" || it == "trimMargin()" } == true) { - fixStringLiteral(whiteSpace, expectedIndent) + fixStringLiteral(whiteSpace, expectedIndent, actualIndent) } } /** * If it is triple-quoted string template we need to indent all its parts */ - private fun fixStringLiteral(whiteSpace: PsiWhiteSpace, expectedIndent: Int) { + private fun fixStringLiteral( + whiteSpace: PsiWhiteSpace, + expectedIndent: Int, + actualIndent: Int + ) { val textIndent = " ".repeat(expectedIndent + INDENT_SIZE) val templateEntries = whiteSpace.node.treeNext.firstChildNode.getAllChildrenWithType(LITERAL_STRING_TEMPLATE_ENTRY) - templateEntries.forEach { - if (!it.text.contains("\n") && it.text.isNotBlank()) { - (it.firstChildNode as LeafPsiElement).rawReplaceWithText(textIndent + it.firstChildNode.text.trim()) + templateEntries.forEach { node -> + if (!node.text.contains("\n")) { + fixFirstTemplateEntries(node, textIndent, actualIndent) } } (templateEntries.last().firstChildNode as LeafPsiElement) @@ -226,12 +248,43 @@ class IndentationRule(configRules: List) : DiktatRule("indentation" .trim()) } + /** + * This method fixes all lines of string template except the last one + * Also it considers $foo insertions in string + */ + private fun fixFirstTemplateEntries( + node: ASTNode, + textIndent: String, + actualIndent: Int + ) { + val correctedText = StringBuilder() + // shift of the node depending on its initial string template indent + val nodeStartIndent = if (node.firstChildNode.text.takeWhile { it == ' ' }.count() - actualIndent - INDENT_SIZE > 0) { + node.firstChildNode.text.takeWhile { it == ' ' }.count() - actualIndent - INDENT_SIZE + } else { + 0 + } + val isPrevStringTemplate = node.treePrev.elementType in stringLiteralTokens + val isNextStringTemplate = node.treeNext.elementType in stringLiteralTokens + when { + // if string template is before literal_string + isPrevStringTemplate && !isNextStringTemplate -> correctedText.append(node.firstChildNode.text.trimEnd()) + // if string template is after literal_string + !isPrevStringTemplate && isNextStringTemplate -> correctedText.append(textIndent + " ".repeat(nodeStartIndent) + node.firstChildNode.text.trimStart()) + // if there is no string template in literal_string + !isPrevStringTemplate && !isNextStringTemplate -> correctedText.append(textIndent + " ".repeat(nodeStartIndent) + node.firstChildNode.text.trim()) + isPrevStringTemplate && isNextStringTemplate -> correctedText.append(node.firstChildNode.text) + node.text.isBlank() -> correctedText.append(textIndent) + else -> {} + } + (node.firstChildNode as LeafPsiElement).rawReplaceWithText(correctedText.toString()) + } + private fun ASTNode.getExceptionalIndentInitiator() = treeParent.let { parent -> when (parent.psi) { // fixme: custom logic for determining exceptional indent initiator, should be moved elsewhere - is KtDotQualifiedExpression -> - // get the topmost expression to keep extended indent for the whole chain of dot call expressions - parents().takeWhile { it.elementType == DOT_QUALIFIED_EXPRESSION || it.elementType == SAFE_ACCESS_EXPRESSION }.last() + // get the topmost expression to keep extended indent for the whole chain of dot call expressions + is KtDotQualifiedExpression -> parents().takeWhile { it.elementType == DOT_QUALIFIED_EXPRESSION || it.elementType == SAFE_ACCESS_EXPRESSION }.last() is KtIfExpression -> parent.findChildByType(THEN) ?: parent.findChildByType(ELSE) ?: parent is KtLoopExpression -> (parent.psi as KtLoopExpression).body?.node ?: parent else -> parent @@ -325,15 +378,16 @@ class IndentationRule(configRules: List) : DiktatRule("indentation" companion object { const val INDENT_SIZE = 4 - private val increasingTokens = listOf(LPAR, LBRACE, LBRACKET) - private val decreasingTokens = listOf(RPAR, RBRACE, RBRACKET) + private val increasingTokens = listOf(LPAR, LBRACE, LBRACKET, LONG_TEMPLATE_ENTRY_START) + private val decreasingTokens = listOf(RPAR, RBRACE, RBRACKET, LONG_TEMPLATE_ENTRY_END) private val matchingTokens = increasingTokens.zip(decreasingTokens) + private val stringLiteralTokens = listOf(SHORT_STRING_TEMPLATE_ENTRY, LONG_STRING_TEMPLATE_ENTRY) } } /** * @property expected expected indentation as a number of spaces - * @property actual actial indentation as a number of spaces + * @property actual actual indentation as a number of spaces */ internal data class IndentationError(val expected: Int, val actual: Int) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt index d75eedfcaa..c005759454 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/NewlinesRule.kt @@ -8,7 +8,19 @@ import org.cqfn.diktat.ruleset.constants.Warnings.COMPLEX_EXPRESSION import org.cqfn.diktat.ruleset.constants.Warnings.REDUNDANT_SEMICOLON import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NEWLINES import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.* +import org.cqfn.diktat.ruleset.utils.appendNewlineMergingWhiteSpace +import org.cqfn.diktat.ruleset.utils.emptyBlockList +import org.cqfn.diktat.ruleset.utils.extractLineOfText +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType +import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType +import org.cqfn.diktat.ruleset.utils.getIdentifierName +import org.cqfn.diktat.ruleset.utils.hasParent +import org.cqfn.diktat.ruleset.utils.isBeginByNewline +import org.cqfn.diktat.ruleset.utils.isEol +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 com.pinterest.ktlint.core.ast.ElementType.ANDAND import com.pinterest.ktlint.core.ast.ElementType.ARROW @@ -51,6 +63,7 @@ import com.pinterest.ktlint.core.ast.ElementType.PLUS import com.pinterest.ktlint.core.ast.ElementType.PLUSEQ import com.pinterest.ktlint.core.ast.ElementType.POSTFIX_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.PRIMARY_CONSTRUCTOR +import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.RETURN import com.pinterest.ktlint.core.ast.ElementType.RETURN_KEYWORD import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS @@ -77,6 +90,7 @@ import org.jetbrains.kotlin.com.intellij.psi.tree.TokenSet import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtParameterList import org.jetbrains.kotlin.psi.KtSuperTypeList +import org.jetbrains.kotlin.psi.KtValueArgumentList import org.jetbrains.kotlin.psi.psiUtil.children import org.jetbrains.kotlin.psi.psiUtil.parents import org.jetbrains.kotlin.psi.psiUtil.siblings @@ -95,7 +109,10 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings * 10. Complex expression inside condition replaced with new variable */ @Suppress("ForbiddenComment") -class NewlinesRule(configRules: List) : DiktatRule("newlines", configRules, listOf(COMPLEX_EXPRESSION, REDUNDANT_SEMICOLON, WRONG_NEWLINES)) { +class NewlinesRule(configRules: List) : DiktatRule( + "newlines", + configRules, + listOf(COMPLEX_EXPRESSION, REDUNDANT_SEMICOLON, WRONG_NEWLINES)) { private val configuration by lazy { NewlinesRuleConfiguration(configRules.getRuleConfig(WRONG_NEWLINES)?.configuration ?: emptyMap()) } @@ -106,9 +123,10 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf in lineBreakBeforeOperators -> handleOperatorWithLineBreakBefore(node) LPAR -> handleOpeningParentheses(node) COMMA -> handleComma(node) + COLON -> handleColon(node) BLOCK -> handleLambdaBody(node) RETURN -> handleReturnStatement(node) - SUPER_TYPE_LIST, VALUE_PARAMETER_LIST -> handleList(node) + SUPER_TYPE_LIST, VALUE_PARAMETER_LIST, VALUE_ARGUMENT_LIST -> handleList(node) else -> { } } @@ -226,6 +244,40 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf } } + /** + * Check that newline is not placed before or after a colon + */ + private fun handleColon(node: ASTNode) { + val prevNewLine = node + .parent({ it.treePrev != null }, strict = false) + ?.treePrev + ?.takeIf { + it.elementType == WHITE_SPACE && it.text.contains("\n") + } + prevNewLine?.let { whiteSpace -> + WRONG_NEWLINES.warnAndFix(configRules, emitWarn, isFixMode, "newline shouldn't be placed before colon", node.startOffset, node) { + whiteSpace.treeParent.removeChild(whiteSpace) + + if (node.hasParent(VALUE_PARAMETER_LIST)) { + // If inside the list of arguments the rule for a new line before the colon has worked, + // then we delete the new line on both sides of the colon + whiteSpace.treeParent?.let { + val nextNewLine = node + .parent({ it.treeNext != null }, strict = false) + ?.treeNext + ?.takeIf { + it.elementType == WHITE_SPACE && it.text.contains("\n") + } + nextNewLine?.let { + it.treeParent.addChild(PsiWhiteSpaceImpl(" "), it) + it.treeParent.removeChild(it) + } + } + } + } + } + } + private fun handleLambdaBody(node: ASTNode) { if (node.treeParent.elementType == FUNCTION_LITERAL) { val isSingleLineLambda = node.treeParent.text.lines().size == 1 @@ -299,15 +351,28 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf * Checks that members of [VALUE_PARAMETER_LIST] (list of function parameters at declaration site) are separated with newlines. * Also checks that entries of [SUPER_TYPE_LIST] are separated by newlines. */ + @Suppress("ComplexMethod") private fun handleList(node: ASTNode) { - if (node.elementType == VALUE_PARAMETER_LIST && node.treeParent.elementType.let { it == FUNCTION_TYPE || it == FUNCTION_TYPE_RECEIVER }) { + if (node.elementType == VALUE_PARAMETER_LIST) { + // do not check list parameter in lambda + node.findParentNodeWithSpecificType(LAMBDA_ARGUMENT)?.let { + return + } // do not check other value lists + if (node.treeParent.elementType.let { it == FUNCTION_TYPE || it == FUNCTION_TYPE_RECEIVER }) { + return + } + } + + if (node.elementType == VALUE_ARGUMENT_LIST && node.siblings(forward = false).any { it.elementType == REFERENCE_EXPRESSION }) { + // check that it is not function invocation, but only supertype constructor calls return } val (numEntries, entryType) = when (node.elementType) { VALUE_PARAMETER_LIST -> (node.psi as KtParameterList).parameters.size to "value parameters" SUPER_TYPE_LIST -> (node.psi as KtSuperTypeList).entries.size to "supertype list entries" + VALUE_ARGUMENT_LIST -> (node.psi as KtValueArgumentList).arguments.size to "value arguments" else -> { log.warn("Unexpected node element type ${node.elementType}") return @@ -315,7 +380,10 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf } if (numEntries > configuration.maxParametersInOneLine) { when (node.elementType) { - VALUE_PARAMETER_LIST -> handleFirstValueParameter(node) + VALUE_PARAMETER_LIST -> handleFirstValue(node, VALUE_PARAMETER, "first parameter should be placed on a separate line " + + "or all other parameters should be aligned with it in declaration of <${node.getParentIdentifier()}>") + VALUE_ARGUMENT_LIST -> handleFirstValue(node, VALUE_ARGUMENT, "first value argument (%s) should be placed on the new line " + + "or all other parameters should be aligned with it") else -> { } } @@ -324,24 +392,33 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf } } - private fun handleFirstValueParameter(node: ASTNode) = node + private fun handleFirstValue(node: ASTNode, + filterType: IElementType, + warnText: String) = node .children() .takeWhile { !it.textContains('\n') } - .filter { it.elementType == VALUE_PARAMETER } + .filter { it.elementType == filterType } .toList() .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() - ) + ?.let { list -> + val freeText = if (filterType == VALUE_ARGUMENT) { + warnText.format(list.first().text) + } else { + warnText + } + WRONG_NEWLINES.warnAndFix(configRules, emitWarn, isFixMode, freeText, node.startOffset, node) { + list.first().treePrev?.let { + node.appendNewlineMergingWhiteSpace( + list.first() + .treePrev + .takeIf { it.elementType == WHITE_SPACE }, + list.first() + ) + } } } + @Suppress("AVOID_NULL_CHECKS") private fun handleValueParameterList(node: ASTNode, entryType: String) = node .children() .filter { @@ -351,8 +428,13 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf .toList() .takeIf { it.isNotEmpty() } ?.let { invalidCommas -> + val warnText = if (node.getParentIdentifier() != null) { + "$entryType should be placed on different lines in declaration of <${node.getParentIdentifier()}>" + } else { + "$entryType should be placed on different lines" + } WRONG_NEWLINES.warnAndFix(configRules, emitWarn, isFixMode, - "$entryType should be placed on different lines in declaration of <${node.getParentIdentifier()}>", node.startOffset, node) { + warnText, node.startOffset, node) { invalidCommas.forEach { comma -> val nextWhiteSpace = comma.treeNext.takeIf { it.elementType == WHITE_SPACE } comma.appendNewlineMergingWhiteSpace(nextWhiteSpace, nextWhiteSpace?.treeNext ?: comma.treeNext) @@ -485,7 +567,7 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf parents().takeWhile { it.elementType in chainExpressionTypes && it.elementType != LAMBDA_ARGUMENT } private fun isMultilineLambda(node: ASTNode): Boolean = - node.findAllNodesWithSpecificType(LAMBDA_ARGUMENT) + node.findAllDescendantsWithSpecificType(LAMBDA_ARGUMENT) .firstOrNull() ?.text ?.count { it == '\n' } ?: -1 > 0 @@ -499,7 +581,7 @@ class NewlinesRule(configRules: List) : DiktatRule("newlines", conf val firstCallee = mutableListOf().also { getOrderedCallExpressions(psi, it) }.first() - findAllNodesWithSpecificType(firstCallee.elementType, false).first() === this@isFirstCall + findAllDescendantsWithSpecificType(firstCallee.elementType, false).first() === this@isFirstCall } ?: false /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/TopLevelOrderRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/TopLevelOrderRule.kt index ac967e1e46..473d3b3627 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/TopLevelOrderRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/TopLevelOrderRule.kt @@ -25,7 +25,10 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings /** * Rule that checks order in top level */ -class TopLevelOrderRule(configRules: List) : DiktatRule("top-level-order", configRules, listOf(TOP_LEVEL_ORDER)) { +class TopLevelOrderRule(configRules: List) : DiktatRule( + "top-level-order", + configRules, + listOf(TOP_LEVEL_ORDER)) { override fun logic(node: ASTNode) { if (node.elementType == FILE) { checkNode(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt index b05694f620..254de14f43 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/files/WhiteSpaceRule.kt @@ -92,7 +92,10 @@ import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf * 10. There should be no spaces between prefix/postfix operator (like `!!` or `++`) and it's operand */ @Suppress("ForbiddenComment") -class WhiteSpaceRule(configRules: List) : DiktatRule("horizontal-whitespace", configRules, listOf(WRONG_WHITESPACE)) { +class WhiteSpaceRule(configRules: List) : DiktatRule( + "horizontal-whitespace", + configRules, + listOf(WRONG_WHITESPACE)) { @Suppress("ComplexMethod") override fun logic(node: ASTNode) { when (node.elementType) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/identifiers/LocalVariablesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/identifiers/LocalVariablesRule.kt index 6f5c2880c9..8170e552c3 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/identifiers/LocalVariablesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter3/identifiers/LocalVariablesRule.kt @@ -37,7 +37,10 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset * * Only properties without initialization or initialized with expressions based on constants are supported. * * Properties initialized with constructor calls cannot be distinguished from method call and are no supported. */ -class LocalVariablesRule(configRules: List) : DiktatRule("local-variables", configRules, listOf(LOCAL_VARIABLE_EARLY_DECLARATION)) { +class LocalVariablesRule(configRules: List) : DiktatRule( + "local-variables", + configRules, + listOf(LOCAL_VARIABLE_EARLY_DECLARATION)) { override fun logic(node: ASTNode) { if (node.elementType == FILE) { // collect all local properties and associate with corresponding references diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/ImmutableValNoVarRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/ImmutableValNoVarRule.kt index 1c1c41889b..520772ed87 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/ImmutableValNoVarRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/ImmutableValNoVarRule.kt @@ -19,7 +19,10 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfType * because `var` variables can be reassigned several times in the business logic. Of course, in some scenarios with loops or accumulators only `var`s can be used and are allowed. * FixMe: here we should also raise warnings for a reassignment of a var (if var has no assignments except in declaration - it can be final) */ -class ImmutableValNoVarRule(configRules: List) : DiktatRule("no-var-rule", configRules, listOf(SAY_NO_TO_VAR)) { +class ImmutableValNoVarRule(configRules: List) : DiktatRule( + "no-var-rule", + configRules, + listOf(SAY_NO_TO_VAR)) { override fun logic(node: ASTNode) { if (node.elementType == ElementType.FILE) { // we will raise warning for cases when var property has no assignments diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt index 366cff96d0..6627d48e15 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/NullChecksRule.kt @@ -26,7 +26,10 @@ import org.jetbrains.kotlin.psi.KtIfExpression * This rule check and fixes explicit null checks (explicit comparison with `null`) * There are several code-structures that can be used in Kotlin to avoid null-checks. For example: `?:`, `.let {}`, `.also {}`, e.t.c */ -class NullChecksRule(configRules: List) : DiktatRule("null-checks", configRules, listOf(AVOID_NULL_CHECKS)) { +class NullChecksRule(configRules: List) : DiktatRule( + "null-checks", + configRules, + listOf(AVOID_NULL_CHECKS)) { override fun logic(node: ASTNode) { if (node.elementType == CONDITION) { node.parent(IF)?.let { @@ -58,7 +61,7 @@ class NullChecksRule(configRules: List) : DiktatRule("null-checks", } private fun conditionInIfStatement(node: ASTNode) { - node.findAllNodesWithSpecificType(BINARY_EXPRESSION).forEach { binaryExprNode -> + node.findAllDescendantsWithSpecificType(BINARY_EXPRESSION).forEach { binaryExprNode -> val condition = (binaryExprNode.psi as KtBinaryExpression) if (isNullCheckBinaryExpression(condition)) { when (condition.operationToken) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt index 45bbe16327..f0a09c9aae 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/SmartCastRule.kt @@ -4,7 +4,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.SMART_CAST_NEEDED import org.cqfn.diktat.ruleset.rules.DiktatRule import org.cqfn.diktat.ruleset.utils.KotlinParser -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.findParentNodeWithSpecificType import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType @@ -39,7 +39,10 @@ import org.jetbrains.kotlin.psi.psiUtil.parents /** * Rule that detects redundant explicit casts */ -class SmartCastRule(configRules: List) : DiktatRule("smart-cast-rule", configRules, listOf(SMART_CAST_NEEDED)) { +class SmartCastRule(configRules: List) : DiktatRule( + "smart-cast-rule", + configRules, + listOf(SMART_CAST_NEEDED)) { override fun logic(node: ASTNode) { if (node.elementType == FILE) { val usages = collectLocalPropertiesWithUsages(node) @@ -114,8 +117,7 @@ class SmartCastRule(configRules: List) : DiktatRule("smart-cast-rul @Suppress("TYPE_ALIAS") private fun groupIsAndAsExpr(isExpr: List, asExpr: List, - prop: KtProperty) - : Map> { + prop: KtProperty): Map> { if (isExpr.isEmpty() && asExpr.isNotEmpty()) { handleZeroIsCase(asExpr, prop) return emptyMap() @@ -170,7 +172,7 @@ class SmartCastRule(configRules: List) : DiktatRule("smart-cast-rul thenBlock?.let { // Find all as expressions that are inside this current block val asList = thenBlock - .findAllNodesWithSpecificType(BINARY_WITH_TYPE) + .findAllDescendantsWithSpecificType(BINARY_WITH_TYPE) .filter { it.text.contains(" as ") && it.findParentNodeWithSpecificType(BLOCK) == thenBlock } @@ -178,7 +180,7 @@ class SmartCastRule(configRules: List) : DiktatRule("smart-cast-rul checkAsExpressions(asList, blocks) } ?: run { - val asList = then.findAllNodesWithSpecificType(BINARY_WITH_TYPE).filter { it.text.contains(KtTokens.AS_KEYWORD.value) } + val asList = then.findAllDescendantsWithSpecificType(BINARY_WITH_TYPE).filter { it.text.contains(KtTokens.AS_KEYWORD.value) } checkAsExpressions(asList, blocks) } } @@ -227,7 +229,7 @@ class SmartCastRule(configRules: List) : DiktatRule("smart-cast-rul val type = entry.getFirstChildWithType(WHEN_CONDITION_IS_PATTERN)!! .getFirstChildWithType(TYPE_REFERENCE)?.text - val callExpr = entry.findAllNodesWithSpecificType(BINARY_WITH_TYPE).firstOrNull() + val callExpr = entry.findAllDescendantsWithSpecificType(BINARY_WITH_TYPE).firstOrNull() val blocks = listOf(IsExpressions(identifier, type ?: "")) callExpr?.let { @@ -264,8 +266,7 @@ class SmartCastRule(configRules: List) : DiktatRule("smart-cast-rul * @return Map of property and list of expressions */ @Suppress("TYPE_ALIAS") - private fun collectReferenceList(propertiesToUsages: Map>) - : Map> = + private fun collectReferenceList(propertiesToUsages: Map>): Map> = propertiesToUsages.mapValues { (_, value) -> value.filter { entry -> entry.parent.node.elementType == IS_EXPRESSION || entry.parent.node.elementType == BINARY_WITH_TYPE diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt index 41e46615b6..cd8d5dca83 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/TypeAliasRule.kt @@ -19,7 +19,10 @@ import org.jetbrains.kotlin.psi.psiUtil.parents * This rule checks if variable has long type reference and two or more nested generics. * Length type reference can be configured */ -class TypeAliasRule(configRules: List) : DiktatRule("type-alias", configRules, listOf(TYPE_ALIAS)) { +class TypeAliasRule(configRules: List) : DiktatRule( + "type-alias", + configRules, + listOf(TYPE_ALIAS)) { override fun logic(node: ASTNode) { if (node.elementType == TYPE_REFERENCE && node .parents() @@ -34,7 +37,7 @@ class TypeAliasRule(configRules: List) : DiktatRule("type-alias", c */ private fun checkTypeReference(node: ASTNode, config: TypeAliasConfiguration) { if (node.textLength > config.typeReferenceLength) { - if (node.findAllNodesWithSpecificType(LT).size > 1 || node.findAllNodesWithSpecificType(VALUE_PARAMETER).size > 1) { + if (node.findAllDescendantsWithSpecificType(LT).size > 1 || node.findAllDescendantsWithSpecificType(VALUE_PARAMETER).size > 1) { TYPE_ALIAS.warn(configRules, emitWarn, isFixMode, "too long type reference", node.startOffset, node) } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/VariableGenericTypeDeclarationRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/VariableGenericTypeDeclarationRule.kt index 07a24644c7..b2027af05c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/VariableGenericTypeDeclarationRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/VariableGenericTypeDeclarationRule.kt @@ -21,7 +21,9 @@ import org.jetbrains.kotlin.psi.KtProperty * Recommended: val myVariable: Map = emptyMap() */ // FIXME: we now don't have access to return types, so we can perform this check only if explicit type is present, but should be able also if it's not. -class VariableGenericTypeDeclarationRule(configRules: List) : DiktatRule("variable-generic-type", configRules, +class VariableGenericTypeDeclarationRule(configRules: List) : DiktatRule( + "variable-generic-type", + configRules, listOf(GENERIC_VARIABLE_WRONG_DECLARATION)) { override fun logic(node: ASTNode) { when (node.elementType) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/calculations/AccurateCalculationsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/calculations/AccurateCalculationsRule.kt index cb40295a68..fb32b85809 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/calculations/AccurateCalculationsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter4/calculations/AccurateCalculationsRule.kt @@ -24,7 +24,10 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset * Exception: allows arithmetic operations only when absolute value of result is immediately used in comparison * Fixme: detect variables by type, not only floating-point literals */ -class AccurateCalculationsRule(configRules: List) : DiktatRule("accurate-calculations", configRules, listOf(FLOAT_IN_ACCURATE_CALCULATIONS)) { +class AccurateCalculationsRule(configRules: List) : DiktatRule( + "accurate-calculations", + configRules, + listOf(FLOAT_IN_ACCURATE_CALCULATIONS)) { private fun KtCallExpression?.isAbsOfFloat() = this ?.run { (calleeExpression as? KtNameReferenceExpression) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AsyncAndSyncRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AsyncAndSyncRule.kt index 927b6fb637..e79140b160 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AsyncAndSyncRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AsyncAndSyncRule.kt @@ -17,7 +17,10 @@ import org.jetbrains.kotlin.psi.psiUtil.hasSuspendModifier /** * This rule finds if using runBlocking in asynchronous code */ -class AsyncAndSyncRule(configRules: List) : DiktatRule("sync-in-async", configRules, listOf(RUN_BLOCKING_INSIDE_ASYNC)) { +class AsyncAndSyncRule(configRules: List) : DiktatRule( + "sync-in-async", + configRules, + listOf(RUN_BLOCKING_INSIDE_ASYNC)) { private val asyncList = listOf("async", "launch") override fun logic(node: ASTNode) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt index e597764640..56e047d780 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/AvoidNestedFunctionsRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter5 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.AVOID_NESTED_FUNCTIONS import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.hasChildOfType import org.cqfn.diktat.ruleset.utils.hasParent @@ -24,7 +24,10 @@ import org.jetbrains.kotlin.psi.psiUtil.parents /** * This rule checks for nested functions and warns if it finds any. */ -class AvoidNestedFunctionsRule(configRules: List) : DiktatRule("avoid-nested-functions", configRules, listOf(AVOID_NESTED_FUNCTIONS)) { +class AvoidNestedFunctionsRule(configRules: List) : DiktatRule( + "avoid-nested-functions", + configRules, + listOf(AVOID_NESTED_FUNCTIONS)) { override fun logic(node: ASTNode) { if (node.elementType == FUN) { handleNestedFunctions(node) @@ -40,7 +43,7 @@ class AvoidNestedFunctionsRule(configRules: List) : DiktatRule("avo AVOID_NESTED_FUNCTIONS.warnAndFix(configRules, emitWarn, isFixMode, "fun $funcName", node.startOffset, node, canBeAutoCorrected = checkFunctionReferences(node)) { // We take last nested function, then add and remove child from bottom to top - val lastFunc = node.findAllNodesWithSpecificType(FUN).last() + val lastFunc = node.findAllDescendantsWithSpecificType(FUN).last() val funcSeq = lastFunc .parents() .filter { it.elementType == FUN } @@ -75,14 +78,14 @@ class AvoidNestedFunctionsRule(configRules: List) : DiktatRule("avo @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX") private fun checkFunctionReferences(func: ASTNode): Boolean { val localProperties: MutableList = mutableListOf() - localProperties.addAll(func.findAllNodesWithSpecificType(PROPERTY)) + localProperties.addAll(func.findAllDescendantsWithSpecificType(PROPERTY)) val propertiesNames: List = mutableListOf().apply { addAll(localProperties.map { it.getFirstChildWithType(IDENTIFIER)!!.text }) addAll(getParameterNames(func)) } .toList() - return func.findAllNodesWithSpecificType(REFERENCE_EXPRESSION).all { propertiesNames.contains(it.text) } + return func.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION).all { propertiesNames.contains(it.text) } } /** diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CheckInverseMethodRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CheckInverseMethodRule.kt index 8c92f48a18..c5504798d7 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CheckInverseMethodRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CheckInverseMethodRule.kt @@ -22,7 +22,10 @@ import org.jetbrains.kotlin.psi.psiUtil.siblings * This rule checks if inverse method can be used. * For example if there is !isEmpty() on collection call that it changes it to isNotEmpty() */ -class CheckInverseMethodRule(configRules: List) : DiktatRule("inverse-method", configRules, listOf(INVERSE_FUNCTION_PREFERRED)) { +class CheckInverseMethodRule(configRules: List) : DiktatRule( + "inverse-method", + configRules, + listOf(INVERSE_FUNCTION_PREFERRED)) { override fun logic(node: ASTNode) { if (node.elementType == CALL_EXPRESSION && node.text in methodMap.keys) { checkCallExpressionName(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CustomLabel.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CustomLabel.kt index 518040b1b3..2359b3a8aa 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CustomLabel.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/CustomLabel.kt @@ -17,7 +17,10 @@ import org.jetbrains.kotlin.psi.psiUtil.parents /** * Rule that checks using custom label */ -class CustomLabel(configRules: List) : DiktatRule("custom-label", configRules, listOf(CUSTOM_LABEL)) { +class CustomLabel(configRules: List) : DiktatRule( + "custom-label", + configRules, + listOf(CUSTOM_LABEL)) { private val forEachReference = listOf("forEach", "forEachIndexed") private val labels = listOf("@loop", "@forEach", "@forEachIndexed") private val stopWords = listOf(RETURN, BREAK, CONTINUE) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionArgumentsSize.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionArgumentsSize.kt index 5f07f8a9f2..a5ee1f9045 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionArgumentsSize.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionArgumentsSize.kt @@ -14,7 +14,10 @@ import org.jetbrains.kotlin.psi.KtFunction /** * Rule that checks that function doesn't contains too many parameters */ -class FunctionArgumentsSize(configRules: List) : DiktatRule("argument-size", configRules, listOf(TOO_MANY_PARAMETERS)) { +class FunctionArgumentsSize(configRules: List) : DiktatRule( + "argument-size", + configRules, + listOf(TOO_MANY_PARAMETERS)) { private val configuration: FunctionArgumentsSizeConfiguration by lazy { FunctionArgumentsSizeConfiguration(configRules.getRuleConfig(TOO_MANY_PARAMETERS)?.configuration ?: emptyMap()) } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionLength.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionLength.kt index 670fef2377..22de1d88a6 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionLength.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/FunctionLength.kt @@ -14,7 +14,10 @@ import org.jetbrains.kotlin.psi.KtFunction /** * Rule 5.1.1 check function length */ -class FunctionLength(configRules: List) : DiktatRule("function-length", configRules, listOf(TOO_LONG_FUNCTION)) { +class FunctionLength(configRules: List) : DiktatRule( + "function-length", + configRules, + listOf(TOO_LONG_FUNCTION)) { override fun logic(node: ASTNode) { val configuration = FunctionLengthConfiguration( configRules.getRuleConfig(TOO_LONG_FUNCTION)?.configuration ?: emptyMap() diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt index f231535a74..de961cdbde 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaLengthRule.kt @@ -13,7 +13,10 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode /** * Rule 5.2.5 check lambda length without parameters */ -class LambdaLengthRule(configRules: List) : DiktatRule("lambda-length", configRules, listOf(TOO_MANY_LINES_IN_LAMBDA)) { +class LambdaLengthRule(configRules: List) : DiktatRule( + "lambda-length", + configRules, + listOf(TOO_MANY_LINES_IN_LAMBDA)) { private val configuration by lazy { LambdaLengthConfiguration( this.configRules.getRuleConfig(TOO_MANY_LINES_IN_LAMBDA)?.configuration ?: emptyMap() @@ -35,7 +38,7 @@ class LambdaLengthRule(configRules: List) : DiktatRule("lambda-leng node.treeParent.removeChild(node) } } - val isIt = copyNode.findAllNodesWithSpecificType(ElementType.REFERENCE_EXPRESSION).map { re -> re.text }.contains("it") + val isIt = copyNode.findAllDescendantsWithSpecificType(ElementType.REFERENCE_EXPRESSION).map { re -> re.text }.contains("it") val parameters = node.findChildByType(ElementType.FUNCTION_LITERAL)?.findChildByType(ElementType.VALUE_PARAMETER_LIST) if (parameters == null && isIt) { TOO_MANY_LINES_IN_LAMBDA.warn(configRules, emitWarn, isFixMode, diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaParameterOrder.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaParameterOrder.kt index 28bf1a9082..efa7d4fae2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaParameterOrder.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/LambdaParameterOrder.kt @@ -15,7 +15,10 @@ import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty /** * Rule that checks if parameter with function type is the last in parameter list */ -class LambdaParameterOrder(configRules: List) : DiktatRule("lambda-parameter-order", configRules, listOf(LAMBDA_IS_NOT_LAST_PARAMETER)) { +class LambdaParameterOrder(configRules: List) : DiktatRule( + "lambda-parameter-order", + configRules, + listOf(LAMBDA_IS_NOT_LAST_PARAMETER)) { override fun logic(node: ASTNode) { if (node.elementType == ElementType.FUN) { checkArguments(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt index dc537c668a..bb93f77f3e 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/NestedFunctionBlock.kt @@ -5,7 +5,7 @@ import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.common.config.rules.getRuleConfig import org.cqfn.diktat.ruleset.constants.Warnings.NESTED_BLOCK import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.hasChildOfType import com.pinterest.ktlint.core.ast.ElementType.CLASS @@ -20,7 +20,10 @@ import org.jetbrains.kotlin.psi.psiUtil.parents /** * Rule 5.1.2 Nested blokcs */ -class NestedFunctionBlock(configRules: List) : DiktatRule("nested-block", configRules, listOf(NESTED_BLOCK)) { +class NestedFunctionBlock(configRules: List) : DiktatRule( + "nested-block", + configRules, + listOf(NESTED_BLOCK)) { private val configuration: NestedBlockConfiguration by lazy { NestedBlockConfiguration(configRules.getRuleConfig(NESTED_BLOCK)?.configuration ?: emptyMap()) } @@ -32,7 +35,7 @@ class NestedFunctionBlock(configRules: List) : DiktatRule("nested-b } private fun countNestedBlocks(node: ASTNode, maxNestedBlockCount: Long) { - node.findAllNodesWithSpecificType(LBRACE) + node.findAllDescendantsWithSpecificType(LBRACE) .reversed() .forEach { lbraceNode -> val blockParent = lbraceNode diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/OverloadingArgumentsFunction.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/OverloadingArgumentsFunction.kt index 632db0609e..9c67a1b7fe 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/OverloadingArgumentsFunction.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter5/OverloadingArgumentsFunction.kt @@ -17,7 +17,9 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset /** * Rule that suggests to use functions with default parameters instead of multiple overloads */ -class OverloadingArgumentsFunction(configRules: List) : DiktatRule("overloading-default-values", configRules, +class OverloadingArgumentsFunction(configRules: List) : DiktatRule( + "overloading-default-values", + configRules, listOf(WRONG_OVERLOADING_FUNCTION_ARGUMENTS)) { override fun logic(node: ASTNode) { if (node.elementType == FUN) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidEmptyPrimaryConstructor.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidEmptyPrimaryConstructor.kt index d90f3ae304..d5a500660f 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidEmptyPrimaryConstructor.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidEmptyPrimaryConstructor.kt @@ -11,7 +11,10 @@ import org.jetbrains.kotlin.psi.KtClass /** * This rule checks if a class has an empty primary constructor. */ -class AvoidEmptyPrimaryConstructor(configRules: List) : DiktatRule("avoid-empty-primary-constructor", configRules, listOf(EMPTY_PRIMARY_CONSTRUCTOR)) { +class AvoidEmptyPrimaryConstructor(configRules: List) : DiktatRule( + "avoid-empty-primary-constructor", + configRules, + listOf(EMPTY_PRIMARY_CONSTRUCTOR)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS) { checkClass(node.psi as KtClass) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidUtilityClass.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidUtilityClass.kt index 627f55f1e2..9144aac9d5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidUtilityClass.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/AvoidUtilityClass.kt @@ -24,7 +24,10 @@ import org.jetbrains.kotlin.psi.psiUtil.children /** * Rule 6.4.1 checks that class/object, with a word "util" in its name, has only functions. */ -class AvoidUtilityClass(configRules: List) : DiktatRule("avoid-utility-class", configRules, listOf(AVOID_USING_UTILITY_CLASS)) { +class AvoidUtilityClass(configRules: List) : DiktatRule( + "avoid-utility-class", + configRules, + listOf(AVOID_USING_UTILITY_CLASS)) { override fun logic(node: ASTNode) { val config = configRules.getCommonConfiguration() val filePath = node.getRootNode().getFilePath() diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/CustomGetterSetterRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/CustomGetterSetterRule.kt index 9002f51737..647ef47b84 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/CustomGetterSetterRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/CustomGetterSetterRule.kt @@ -15,7 +15,10 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode /** * Inspection that checks that no custom getters and setters are used for properties. */ -class CustomGetterSetterRule(configRules: List) : DiktatRule("custom-getter-setter", configRules, listOf(CUSTOM_GETTERS_SETTERS)) { +class CustomGetterSetterRule(configRules: List) : DiktatRule( + "custom-getter-setter", + configRules, + listOf(CUSTOM_GETTERS_SETTERS)) { override fun logic(node: ASTNode) { if (node.elementType == PROPERTY_ACCESSOR) { checkForCustomGetersSetters(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt index e3a66b4389..ebf37b90d5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsInFileRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.EXTENSION_FUNCTION_WITH_CLASS import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType @@ -20,7 +20,9 @@ import org.jetbrains.kotlin.psi.KtFunction /** * This rule checks if there are any extension functions for the class in the same file, where it is defined */ -class ExtensionFunctionsInFileRule(configRules: List) : DiktatRule("extension-functions-class-file", configRules, +class ExtensionFunctionsInFileRule(configRules: List) : DiktatRule( + "extension-functions-class-file", + configRules, listOf(EXTENSION_FUNCTION_WITH_CLASS)) { override fun logic(node: ASTNode) { if (node.elementType == ElementType.FILE) { @@ -34,7 +36,7 @@ class ExtensionFunctionsInFileRule(configRules: List) : DiktatRule( @Suppress("UnsafeCallOnNullableType") private fun collectAllClassNames(node: ASTNode): List { - val classes = node.findAllNodesWithSpecificType(CLASS) + val classes = node.findAllDescendantsWithSpecificType(CLASS) return classes.map { (it.psi as KtClass).name!! } } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt index 58e4ea2f72..0ff833175a 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ExtensionFunctionsSameNameRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.EXTENSION_FUNCTION_SAME_SIGNATURE import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.findChildAfter import org.cqfn.diktat.ruleset.utils.findChildBefore import org.cqfn.diktat.ruleset.utils.findLeafWithSpecificType @@ -32,7 +32,9 @@ internal typealias SimilarSignatures = List) : DiktatRule("extension-functions-same-name", configRules, +class ExtensionFunctionsSameNameRule(configRules: List) : DiktatRule( + "extension-functions-same-name", + configRules, listOf(EXTENSION_FUNCTION_SAME_SIGNATURE)) { override fun logic(node: ASTNode) { /** @@ -51,7 +53,7 @@ class ExtensionFunctionsSameNameRule(configRules: List) : DiktatRul @Suppress("UnsafeCallOnNullableType", "TYPE_ALIAS") private fun collectAllRelatedClasses(node: ASTNode): List> { val classListWithInheritance = node - .findAllNodesWithSpecificType(CLASS) + .findAllDescendantsWithSpecificType(CLASS) .filterNot { (it.psi as KtClass).isInterface() } .filter { it.hasChildOfType(SUPER_TYPE_LIST) } @@ -70,7 +72,7 @@ class ExtensionFunctionsSameNameRule(configRules: List) : DiktatRul @Suppress("UnsafeCallOnNullableType", "TYPE_ALIAS") private fun collectAllExtensionFunctions(node: ASTNode): SimilarSignatures { - val extensionFunctionList = node.findAllNodesWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } + val extensionFunctionList = node.findAllDescendantsWithSpecificType(FUN).filter { it.hasChildOfType(TYPE_REFERENCE) && it.hasChildOfType(DOT) } val distinctFunctionSignatures: MutableMap = mutableMapOf() // maps function signatures on node it is used by val extensionFunctionsPairs: MutableList> = mutableListOf() // pairs extension functions with same signature diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt index be46671279..53a4398bd2 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/ImplicitBackingPropertyRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.NO_CORRESPONDING_PROPERTY import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.hasAnyChildOfTypes import org.cqfn.diktat.ruleset.utils.hasChildOfType @@ -24,7 +24,9 @@ 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(configRules: List) : DiktatRule("implicit-backing-property", configRules, +class ImplicitBackingPropertyRule(configRules: List) : DiktatRule( + "implicit-backing-property", + configRules, listOf(NO_CORRESPONDING_PROPERTY)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS_BODY) { @@ -48,7 +50,7 @@ class ImplicitBackingPropertyRule(configRules: List) : DiktatRule(" } private fun validateAccessors(node: ASTNode, propsWithBackSymbol: List) { - val accessors = node.findAllNodesWithSpecificType(PROPERTY_ACCESSOR).filter { it.hasChildOfType(BLOCK) } // exclude get with expression body + val accessors = node.findAllDescendantsWithSpecificType(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) } @@ -60,12 +62,12 @@ class ImplicitBackingPropertyRule(configRules: List) : DiktatRule(" node: ASTNode, propsWithBackSymbol: List) { val refExprs = accessor - .findAllNodesWithSpecificType(RETURN) + .findAllDescendantsWithSpecificType(RETURN) .filterNot { it.hasChildOfType(DOT_QUALIFIED_EXPRESSION) } - .flatMap { it.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) } + .flatMap { it.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) } val localProps = accessor - .findAllNodesWithSpecificType(PROPERTY) + .findAllDescendantsWithSpecificType(PROPERTY) .map { (it.psi as KtProperty).name!! } // If refExprs is empty then we assume that it returns some constant if (refExprs.isNotEmpty()) { @@ -78,7 +80,7 @@ class ImplicitBackingPropertyRule(configRules: List) : DiktatRule(" accessor: ASTNode, node: ASTNode, propsWithBackSymbol: List) { - val refExprs = accessor.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + val refExprs = accessor.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) // In set we don't check for local properties. At least one reference expression should contain field or _prop if (refExprs.isNotEmpty()) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt index cdd75f4376..7ac7671968 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/PropertyAccessorFields.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.isGoingAfter import com.pinterest.ktlint.core.ast.ElementType.BLOCK @@ -20,7 +20,10 @@ import org.jetbrains.kotlin.psi.KtProperty /** * Rule check that never use the name of a variable in the custom getter or setter */ -class PropertyAccessorFields(configRules: List) : DiktatRule("getter-setter-fields", configRules, listOf(WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR)) { +class PropertyAccessorFields(configRules: List) : DiktatRule( + "getter-setter-fields", + configRules, + listOf(WRONG_NAME_OF_VARIABLE_INSIDE_ACCESSOR)) { override fun logic(node: ASTNode) { if (node.elementType == PROPERTY_ACCESSOR) { checkPropertyAccessor(node) @@ -31,7 +34,7 @@ class PropertyAccessorFields(configRules: List) : DiktatRule("gette private fun checkPropertyAccessor(node: ASTNode) { val leftValue = node.treeParent.findChildByType(IDENTIFIER) ?: return val firstReferenceWithSameName = node - .findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) .mapNotNull { it.findChildByType(IDENTIFIER) } .firstOrNull { it.text == leftValue.text && diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/RunInScript.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/RunInScript.kt new file mode 100644 index 0000000000..ab48e27c1b --- /dev/null +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/RunInScript.kt @@ -0,0 +1,80 @@ +package org.cqfn.diktat.ruleset.rules.chapter6 + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.EmitType +import org.cqfn.diktat.ruleset.constants.Warnings.RUN_IN_SCRIPT +import org.cqfn.diktat.ruleset.utils.* + +import com.pinterest.ktlint.core.Rule +import com.pinterest.ktlint.core.ast.ElementType.CALL_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.DOT_QUALIFIED_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_ARGUMENT +import com.pinterest.ktlint.core.ast.ElementType.LAMBDA_EXPRESSION +import com.pinterest.ktlint.core.ast.ElementType.PARENTHESIZED +import com.pinterest.ktlint.core.ast.ElementType.SCRIPT_INITIALIZER +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT +import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT_LIST +import org.jetbrains.kotlin.com.intellij.lang.ASTNode +import org.jetbrains.kotlin.com.intellij.psi.impl.source.tree.CompositeElement + +/** + * Rule that checks if kts script contains other functions except run code + * In .kts files allow use only property declaration, function, classes, and code inside `run` block + * In gradle.kts files allow to call expression and dot qualified expression in addition to everything used in .kts files + */ +class RunInScript(private val configRules: List) : Rule("run-script") { + private var isFixMode: Boolean = false + private lateinit var emitWarn: EmitType + + override fun visit( + node: ASTNode, + autoCorrect: Boolean, + emit: EmitType + ) { + isFixMode = autoCorrect + emitWarn = emit + + if (node.elementType == SCRIPT_INITIALIZER && node.getRootNode().getFilePath().isKotlinScript()) { + if (node.getRootNode().getFilePath().isGradleScript()) { + checkGradleNode(node) + } else { + checkScript(node) + } + } + } + + private fun checkGradleNode(node: ASTNode) { + val astNode = if (node.firstChildNode.elementType == PARENTHESIZED) { + node.firstChildNode + } else { + node + } + if (!astNode.hasChildOfType(CALL_EXPRESSION) && !astNode.hasChildOfType(DOT_QUALIFIED_EXPRESSION)) { + RUN_IN_SCRIPT.warnAndFix(configRules, emitWarn, isFixMode, astNode.text, astNode.startOffset, astNode) { + val parent = astNode.treeParent + val newNode = KotlinParser().createNode("run {\n ${astNode.text}\n} \n") + val newScript = CompositeElement(SCRIPT_INITIALIZER) + parent.addChild(newScript, astNode) + newScript.addChild(newNode) + parent.removeChild(astNode) + } + } + } + + private fun checkScript(node: ASTNode) { + val isLambdaArgument = node.firstChildNode.hasChildOfType(LAMBDA_ARGUMENT) + val isLambdaInsideValueArgument = node.firstChildNode.findChildByType(VALUE_ARGUMENT_LIST)?.findChildByType(VALUE_ARGUMENT)?.findChildByType(LAMBDA_EXPRESSION) != null + if (!(isLambdaArgument || isLambdaInsideValueArgument)) { + RUN_IN_SCRIPT.warnAndFix(configRules, emitWarn, isFixMode, node.text, node.startOffset, node) { + if (node.firstChildNode.elementType != DOT_QUALIFIED_EXPRESSION) { + val parent = node.treeParent + val newNode = KotlinParser().createNode("run {\n ${node.text}\n} \n") + val newScript = CompositeElement(SCRIPT_INITIALIZER) + parent.addChild(newScript, node) + newScript.addChild(newNode) + parent.removeChild(node) + } + } + } + } +} diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt index c2c821a9c1..144cf594b1 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/TrivialPropertyAccessors.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6 import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.getIdentifierName import org.cqfn.diktat.ruleset.utils.hasChildOfType @@ -26,7 +26,9 @@ import org.jetbrains.kotlin.psi.KtPropertyAccessor /** * This rule checks if there are any trivial getters and setters and, if so, deletes them */ -class TrivialPropertyAccessors(configRules: List) : DiktatRule("trivial-property-accessors", configRules, +class TrivialPropertyAccessors(configRules: List) : DiktatRule( + "trivial-property-accessors", + configRules, listOf(TRIVIAL_ACCESSORS_ARE_NOT_RECOMMENDED)) { override fun logic(node: ASTNode) { if (node.elementType == PROPERTY_ACCESSOR) { @@ -68,7 +70,7 @@ class TrivialPropertyAccessors(configRules: List) : DiktatRule("tri @Suppress("UnsafeCallOnNullableType") private fun handleGetAccessor(node: ASTNode) { // It handles both cases: get() = ... and get() { return ... } - val references = node.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + val references = node.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) if (references.singleOrNull()?.text == "field") { raiseWarning(node) } else if (node.getChildren(null).size == ONE_CHILD_IN_ARRAY) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt index c740b623aa..9e041c9309 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/UselessSupertype.kt @@ -31,7 +31,10 @@ import java.util.HashMap * Explicit supertype qualification should not be used if there is not clash between called methods * fixme can't fix supertypes that are defined in other files. */ -class UselessSupertype(configRules: List) : DiktatRule("useless-override", configRules, listOf(USELESS_SUPERTYPE)) { +class UselessSupertype(configRules: List) : DiktatRule( + "useless-override", + configRules, + listOf(USELESS_SUPERTYPE)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS) { checkClass(node) @@ -44,7 +47,7 @@ class UselessSupertype(configRules: List) : DiktatRule("useless-ove ?.findAllNodesWithCondition({ it.elementType in superType }) ?.takeIf { it.isNotEmpty() } ?: return val qualifiedSuperCalls = node - .findAllNodesWithSpecificType(DOT_QUALIFIED_EXPRESSION) + .findAllDescendantsWithSpecificType(DOT_QUALIFIED_EXPRESSION) .mapNotNull { findFunWithSuper(it) } .ifEmpty { return } if (superNodes.size == 1) { @@ -90,10 +93,10 @@ class UselessSupertype(configRules: List) : DiktatRule("useless-ove private fun findFunWithSuper(node: ASTNode) = Pair( node.findChildByType(SUPER_EXPRESSION) ?.findChildByType(TYPE_REFERENCE) - ?.findAllNodesWithSpecificType(IDENTIFIER) + ?.findAllDescendantsWithSpecificType(IDENTIFIER) ?.firstOrNull(), node.findChildByType(CALL_EXPRESSION) - ?.findAllNodesWithSpecificType(IDENTIFIER) + ?.findAllDescendantsWithSpecificType(IDENTIFIER) ?.firstOrNull()) .run { if (first == null || second == null) null else first!! to second!! @@ -111,7 +114,7 @@ class UselessSupertype(configRules: List) : DiktatRule("useless-ove private fun findAllSupers(superTypeList: List, methodsName: List): Map? { val fileNode = superTypeList.first().parent({ it.elementType == FILE })!! val superNodesIdentifier = superTypeList.map { - it.findAllNodesWithSpecificType(IDENTIFIER) + it.findAllDescendantsWithSpecificType(IDENTIFIER) .first() .text } @@ -124,7 +127,7 @@ class UselessSupertype(configRules: List) : DiktatRule("useless-ove } val functionNameMap: HashMap = hashMapOf() superNodes.forEach { classBody -> - val overrideFunctions = classBody.findAllNodesWithSpecificType(FUN) + val overrideFunctions = classBody.findAllDescendantsWithSpecificType(FUN) .filter { (if (classBody.treeParent.hasChildOfType(CLASS_KEYWORD)) it.findChildByType(MODIFIER_LIST)!!.hasChildOfType(OPEN_KEYWORD) else true) && it.getIdentifierName()!!.text in methodsName diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt index 4755f42b55..66b9fcc8c6 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/AbstractClassesRule.kt @@ -19,7 +19,10 @@ import org.jetbrains.kotlin.com.intellij.lang.ASTNode /** * Checks if abstract class has any abstract method. If not, warns that class should not be abstract */ -class AbstractClassesRule(configRules: List) : DiktatRule("abstract-classes", configRules, listOf(CLASS_SHOULD_NOT_BE_ABSTRACT)) { +class AbstractClassesRule(configRules: List) : DiktatRule( + "abstract-classes", + configRules, + listOf(CLASS_SHOULD_NOT_BE_ABSTRACT)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS) { val classBody = node.getFirstChildWithType(CLASS_BODY) ?: return diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/CompactInitialization.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/CompactInitialization.kt index 807459f103..f9dfcf8b9c 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/CompactInitialization.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/CompactInitialization.kt @@ -27,7 +27,10 @@ import org.jetbrains.kotlin.psi.psiUtil.startOffset * FixMe: When assigned variable's name is also a `this@apply`'s property, it should be changed to qualified name, * e.g `this@Foo`. But for this we need a mechanism to determine declaration scope and it's label. */ -class CompactInitialization(configRules: List) : DiktatRule("class-compact-initialization", configRules, listOf(COMPACT_OBJECT_INITIALIZATION)) { +class CompactInitialization(configRules: List) : DiktatRule( + "class-compact-initialization", + configRules, + listOf(COMPACT_OBJECT_INITIALIZATION)) { private val kotlinParser by lazy { KotlinParser() } override fun logic(node: ASTNode) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt index de0f2b6d91..2f03d76afa 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/DataClassesRule.kt @@ -31,7 +31,10 @@ import org.jetbrains.kotlin.psi.KtPrimaryConstructor /** * This rule checks if class can be made as data class */ -class DataClassesRule(configRules: List) : DiktatRule("data-classes", configRules, listOf(USE_DATA_CLASS)) { +class DataClassesRule(configRules: List) : DiktatRule( + "data-classes", + configRules, + listOf(USE_DATA_CLASS)) { override fun logic(node: ASTNode) { if (node.elementType == CLASS) { handleClass(node) @@ -53,7 +56,11 @@ class DataClassesRule(configRules: List) : DiktatRule("data-classes USE_DATA_CLASS.warn(configRules, emitWarn, isFixMode, "${(node.psi as KtClass).name}", node.startOffset, node) } - @Suppress("UnsafeCallOnNullableType", "FUNCTION_BOOLEAN_PREFIX", "ComplexMethod") + @Suppress( + "UnsafeCallOnNullableType", + "FUNCTION_BOOLEAN_PREFIX", + "ComplexMethod" + ) private fun ASTNode.canBeDataClass(): Boolean { val isNotPropertyInClassBody = findChildByType(CLASS_BODY)?.let { (it.psi as KtClassBody).properties.isEmpty() } ?: true val constructorParametersNames: MutableList = mutableListOf() @@ -75,7 +82,7 @@ class DataClassesRule(configRules: List) : DiktatRule("data-classes if (constructorParametersNames.isNotEmpty()) { val initBlocks = findChildByType(CLASS_BODY)?.getAllChildrenWithType(CLASS_INITIALIZER) initBlocks?.forEach { init -> - val refExpressions = init.findAllNodesWithSpecificType(REFERENCE_EXPRESSION) + val refExpressions = init.findAllDescendantsWithSpecificType(REFERENCE_EXPRESSION) if (refExpressions.any { it.text in constructorParametersNames }) { return false } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/InlineClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/InlineClassesRule.kt index 24ba0c7fbd..7b42eba4a4 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/InlineClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/InlineClassesRule.kt @@ -25,7 +25,10 @@ import org.jetbrains.kotlin.psi.psiUtil.visibilityModifierType /** * This rule checks if inline class can be used. */ -class InlineClassesRule(configRules: List) : DiktatRule("inline-classes", configRules, listOf(INLINE_CLASS_CAN_BE_USED)) { +class InlineClassesRule(configRules: List) : DiktatRule( + "inline-classes", + configRules, + listOf(INLINE_CLASS_CAN_BE_USED)) { override fun logic(node: ASTNode) { val configuration = configRules.getCommonConfiguration() if (node.elementType == CLASS && configuration.kotlinVersion >= ktVersion) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleConstructorRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleConstructorRule.kt index 802c2fde9c..6924e6a508 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleConstructorRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleConstructorRule.kt @@ -33,7 +33,10 @@ import org.jetbrains.kotlin.psi.psiUtil.collectDescendantsOfType * This rule ensures that if a class has a single constructor, this constructor is primary. * Secondary constructor is converted into primary, statements that are not assignments are moved into an `init` block. */ -class SingleConstructorRule(configRules: List) : DiktatRule("single-constructor", configRules, listOf(SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY)) { +class SingleConstructorRule(configRules: List) : DiktatRule( + "single-constructor", + configRules, + listOf(SINGLE_CONSTRUCTOR_SHOULD_BE_PRIMARY)) { private val kotlinParser by lazy { KotlinParser() } override fun logic(node: ASTNode) { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt index eaa56a3620..8e0fbf7649 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/SingleInitRule.kt @@ -25,7 +25,10 @@ import org.jetbrains.kotlin.psi.psiUtil.children /** * The rule that checks whether a class has a single `init` block or multiple. Having multiple `init` blocks is a bad practice. */ -class SingleInitRule(configRules: List) : DiktatRule("multiple-init-block", configRules, listOf(MULTIPLE_INIT_BLOCKS)) { +class SingleInitRule(configRules: List) : DiktatRule( + "multiple-init-block", + configRules, + listOf(MULTIPLE_INIT_BLOCKS)) { override fun logic(node: ASTNode) { when (node.elementType) { CLASS_BODY -> handleInitBlocks(node) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt index 7ed2903736..f3c6ab6eb0 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/rules/chapter6/classes/StatelessClassesRule.kt @@ -3,7 +3,7 @@ package org.cqfn.diktat.ruleset.rules.chapter6.classes import org.cqfn.diktat.common.config.rules.RulesConfig import org.cqfn.diktat.ruleset.constants.Warnings.OBJECT_IS_PREFERRED import org.cqfn.diktat.ruleset.rules.DiktatRule -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getAllChildrenWithType import org.cqfn.diktat.ruleset.utils.getFirstChildWithType import org.cqfn.diktat.ruleset.utils.hasChildOfType @@ -27,15 +27,18 @@ import org.jetbrains.kotlin.psi.KtClass /** * This rule checks if class is stateless and if so changes it to object. */ -class StatelessClassesRule(configRules: List) : DiktatRule("stateless-class", configRules, listOf(OBJECT_IS_PREFERRED)) { +class StatelessClassesRule(configRules: List) : DiktatRule( + "stateless-class", + configRules, + listOf(OBJECT_IS_PREFERRED)) { override fun logic(node: ASTNode) { // Fixme: We should find interfaces in all project and then check them if (node.elementType == FILE) { val interfacesNodes = node - .findAllNodesWithSpecificType(CLASS) + .findAllDescendantsWithSpecificType(CLASS) .filter { it.hasChildOfType(INTERFACE_KEYWORD) } node - .findAllNodesWithSpecificType(CLASS) + .findAllDescendantsWithSpecificType(CLASS) .filterNot { it.hasChildOfType(INTERFACE_KEYWORD) } .forEach { handleClass(it, interfacesNodes) } } @@ -61,8 +64,8 @@ class StatelessClassesRule(configRules: List) : DiktatRule("statele private fun isStatelessClass(node: ASTNode): Boolean { val properties = (node.psi as KtClass).getProperties() - val functions = node.findAllNodesWithSpecificType(FUN) - return properties.isNullOrEmpty() && + val functions = node.findAllDescendantsWithSpecificType(FUN) + return properties.isEmpty() && functions.isNotEmpty() && !(node.psi as KtClass).hasExplicitPrimaryConstructor() } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt index 8352db3b0f..9905f4a9a3 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtils.kt @@ -3,7 +3,11 @@ * FixMe: fix suppressed inspections on KDocs */ -@file:Suppress("FILE_NAME_MATCH_CLASS", "KDOC_WITHOUT_RETURN_TAG", "KDOC_WITHOUT_PARAM_TAG") +@file:Suppress( + "FILE_NAME_MATCH_CLASS", + "KDOC_WITHOUT_RETURN_TAG", + "KDOC_WITHOUT_PARAM_TAG" +) package org.cqfn.diktat.ruleset.utils @@ -88,7 +92,7 @@ fun ASTNode.isTextLengthInRange(range: IntRange): Boolean = this.textLength in r * @return node with type [IDENTIFIER] or null if it is not present */ fun ASTNode.getIdentifierName(): ASTNode? = - this.getChildren(null).find { it.elementType == ElementType.IDENTIFIER } + this.getFirstChildWithType(ElementType.IDENTIFIER) /** * getting first child name with TYPE_PARAMETER_LIST type @@ -96,20 +100,12 @@ fun ASTNode.getIdentifierName(): ASTNode? = * @return a node with type TYPE_PARAMETER_LIST or null if it is not present */ fun ASTNode.getTypeParameterList(): ASTNode? = - this.getChildren(null).find { it.elementType == ElementType.TYPE_PARAMETER_LIST } - -/** - * getting all children that have IDENTIFIER type - * - * @return a list of nodes - */ -fun ASTNode.getAllIdentifierChildren(): List = - this.getChildren(null).filter { it.elementType == ElementType.IDENTIFIER } + this.getFirstChildWithType(ElementType.TYPE_PARAMETER_LIST) /** * @return true if this node contains no error elements, false otherwise */ -fun ASTNode.isCorrect() = this.findAllNodesWithSpecificType(TokenType.ERROR_ELEMENT).isEmpty() +fun ASTNode.isCorrect() = this.findAllDescendantsWithSpecificType(TokenType.ERROR_ELEMENT).isEmpty() /** * obviously returns list with children that match particular element type @@ -139,7 +135,7 @@ fun ASTNode.replaceWhiteSpaceText(beforeNode: ASTNode, text: String) { * @return a node or null if it was not found */ fun ASTNode.getFirstChildWithType(elementType: IElementType): ASTNode? = - this.getChildren(null).find { it.elementType == elementType } + this.findChildByType(elementType) /** * Checks if the symbols in this node are at the end of line @@ -411,14 +407,15 @@ fun ASTNode.numNewLines() = text.count { it == '\n' } /** * This method performs tree traversal and returns all nodes with specific element type */ -fun ASTNode.findAllNodesWithSpecificType(elementType: IElementType, withSelf: Boolean = true) = +fun ASTNode.findAllDescendantsWithSpecificType(elementType: IElementType, withSelf: Boolean = true) = findAllNodesWithCondition({ it.elementType == elementType }, withSelf) /** * This method performs tree traversal and returns all nodes which satisfy the condition */ @Suppress("LAMBDA_IS_NOT_LAST_PARAMETER") -fun ASTNode.findAllNodesWithCondition(condition: (ASTNode) -> Boolean, withSelf: Boolean = true): List { +fun ASTNode.findAllNodesWithCondition(condition: (ASTNode) -> Boolean, + withSelf: Boolean = true): List { val result = if (condition(this) && withSelf) mutableListOf(this) else mutableListOf() return result + this.getChildren(null).flatMap { it.findAllNodesWithCondition(condition) @@ -439,14 +436,16 @@ fun ASTNode.findParentNodeWithSpecificType(elementType: IElementType) = /** * Finds all children of optional type which match the predicate */ -fun ASTNode.findChildrenMatching(elementType: IElementType? = null, predicate: (ASTNode) -> Boolean): List = +fun ASTNode.findChildrenMatching(elementType: IElementType? = null, + predicate: (ASTNode) -> Boolean): List = getChildren(elementType?.let { TokenSet.create(it) }) .filter(predicate) /** * Check if this node has any children of optional type matching the predicate */ -fun ASTNode.hasChildMatching(elementType: IElementType? = null, predicate: (ASTNode) -> Boolean): Boolean = +fun ASTNode.hasChildMatching(elementType: IElementType? = null, + predicate: (ASTNode) -> Boolean): Boolean = findChildrenMatching(elementType, predicate).isNotEmpty() /** @@ -496,7 +495,7 @@ fun ASTNode.hasSuppress(warningName: String) = parent({ node -> } else { node.findChildByType(FILE_ANNOTATION_LIST) } - annotationNode?.findAllNodesWithSpecificType(ANNOTATION_ENTRY) + annotationNode?.findAllDescendantsWithSpecificType(ANNOTATION_ENTRY) ?.map { it.psi as KtAnnotationEntry } ?.any { it.shortName.toString() == Suppress::class.simpleName && @@ -712,7 +711,7 @@ fun ASTNode.extractLineOfText(): String { */ fun ASTNode.hasTestAnnotation() = findChildByType(MODIFIER_LIST) ?.getAllChildrenWithType(ANNOTATION_ENTRY) - ?.flatMap { it.findAllNodesWithSpecificType(ElementType.CONSTRUCTOR_CALLEE) } + ?.flatMap { it.findAllDescendantsWithSpecificType(ElementType.CONSTRUCTOR_CALLEE) } ?.any { it.findLeafWithSpecificType(ElementType.IDENTIFIER)?.text == "Test" } ?: false diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt index d5e0cbc0a6..abeb23661b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParser.kt @@ -10,7 +10,6 @@ import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.com.intellij.lang.ASTNode import org.jetbrains.kotlin.com.intellij.mock.MockProject -import org.jetbrains.kotlin.com.intellij.openapi.Disposable import org.jetbrains.kotlin.com.intellij.openapi.project.Project import org.jetbrains.kotlin.com.intellij.openapi.util.UserDataHolderBase import org.jetbrains.kotlin.com.intellij.pom.PomModel @@ -49,8 +48,7 @@ class KotlinParser { return null } } // I don't really understand what's going on here, but thanks to this, you can use this node in the future - val project = KotlinCoreEnvironment.createForProduction( - Disposable { }, + val project = KotlinCoreEnvironment.createForProduction({}, compilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES ).project // create project @@ -89,7 +87,11 @@ class KotlinParser { * Else, try to create node based on text. * If this node will contain ERROR_ELEMENT type children this mean that cannot create node based on this text */ - @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION", "SAY_NO_TO_VAR") + @Suppress( + "UnsafeCallOnNullableType", + "TOO_LONG_FUNCTION", + "SAY_NO_TO_VAR" + ) private fun makeNode(text: String, isPackage: Boolean = false): ASTNode? { if (text.isEmpty()) { return null diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt index cb7906d23d..28d8fd2874 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/PositionInTextLocator.kt @@ -7,7 +7,11 @@ package org.cqfn.diktat.ruleset.utils internal typealias LineAndColumn = Pair -@Suppress("MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") +@Suppress( + "MISSING_KDOC_ON_FUNCTION", + "KDOC_WITHOUT_PARAM_TAG", + "KDOC_WITHOUT_RETURN_TAG" +) private class SegmentTree(sortedArray: IntArray) { private val segments: List = sortedArray .dropLast(1) diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt index 022d1c7913..70dc19803b 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/StringUtils.kt @@ -118,3 +118,10 @@ fun String.removePrefix(): String { * @return true if this is a kotlin script file name, false otherwise */ fun String.isKotlinScript() = endsWith(".kts") + +/** + * Checks if [this] String is a name of a gradle kotlin script file by checking whether file extension equals 'gradle.kts' + * + * @return true if this is a gradle kotlin script file name, false otherwise + */ +fun String.isGradleScript() = endsWith("gradle.kts") diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt index 875df6755c..35b62e78aa 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/indentation/Checkers.kt @@ -6,6 +6,7 @@ package org.cqfn.diktat.ruleset.utils.indentation import org.cqfn.diktat.ruleset.rules.chapter3.files.IndentationError import org.cqfn.diktat.ruleset.rules.chapter3.files.lastIndent +import org.cqfn.diktat.ruleset.utils.hasParent import com.pinterest.ktlint.core.ast.ElementType.ARROW import com.pinterest.ktlint.core.ast.ElementType.AS_KEYWORD @@ -24,10 +25,12 @@ import com.pinterest.ktlint.core.ast.ElementType.IS_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.KDOC_END import com.pinterest.ktlint.core.ast.ElementType.KDOC_LEADING_ASTERISK import com.pinterest.ktlint.core.ast.ElementType.KDOC_SECTION +import com.pinterest.ktlint.core.ast.ElementType.LONG_STRING_TEMPLATE_ENTRY import com.pinterest.ktlint.core.ast.ElementType.LPAR import com.pinterest.ktlint.core.ast.ElementType.OPERATION_REFERENCE import com.pinterest.ktlint.core.ast.ElementType.REFERENCE_EXPRESSION import com.pinterest.ktlint.core.ast.ElementType.SAFE_ACCESS +import com.pinterest.ktlint.core.ast.ElementType.STRING_TEMPLATE import com.pinterest.ktlint.core.ast.ElementType.SUPER_TYPE_LIST import com.pinterest.ktlint.core.ast.ElementType.THEN import com.pinterest.ktlint.core.ast.ElementType.VALUE_ARGUMENT @@ -194,16 +197,25 @@ internal class DotCallChecker(config: IndentationConfig) : CustomIndentationChec return false } + private fun ASTNode.isFromStringTemplate(): Boolean = + hasParent(LONG_STRING_TEMPLATE_ENTRY) + @Suppress("ComplexMethod") override fun checkNode(whiteSpace: PsiWhiteSpace, indentError: IndentationError): CheckResult? { whiteSpace.nextSibling.node .takeIf { nextNode -> - nextNode.isDotBeforeCallOrReference() || - nextNode.elementType == OPERATION_REFERENCE && nextNode.firstChildNode.elementType.let { - it == ELVIS || it == IS_EXPRESSION || it == AS_KEYWORD || it == AS_SAFE - } || nextNode.isCommentBeforeDot() + (nextNode.isDotBeforeCallOrReference() || + nextNode.elementType == OPERATION_REFERENCE && nextNode.firstChildNode.elementType.let { type -> + type == ELVIS || type == IS_EXPRESSION || type == AS_KEYWORD || type == AS_SAFE + } || nextNode.isCommentBeforeDot()) && whiteSpace.parents.none { it.node.elementType == LONG_STRING_TEMPLATE_ENTRY } } - ?.let { + ?.let { node -> + if (node.isFromStringTemplate()) { + val template = node.parents().takeWhile { it.elementType != STRING_TEMPLATE }.last() + return CheckResult.from(indentError.actual, indentError.expected + + (if (configuration.extendedIndentBeforeDot) 2 else 1) * configuration.indentationSize, true) + } + // we need to get indent before the first expression in calls chain return CheckResult.from(indentError.actual, (whiteSpace.run { parents.takeWhile { it is KtDotQualifiedExpression || it is KtSafeQualifiedExpression }.lastOrNull() ?: this diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt index fbc1789e8d..7a04e90fa5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesSearch.kt @@ -1,9 +1,13 @@ -@file:Suppress("KDOC_NO_CONSTRUCTOR_PROPERTY", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", +@file:Suppress( + "KDOC_NO_CONSTRUCTOR_PROPERTY", + "MISSING_KDOC_CLASS_ELEMENTS", + "MISSING_KDOC_ON_FUNCTION", + "KDOC_WITHOUT_PARAM_TAG", "KDOC_WITHOUT_RETURN_TAG") package org.cqfn.diktat.ruleset.utils.search -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.getDeclarationScope import org.cqfn.diktat.ruleset.utils.isGoingAfter @@ -27,7 +31,8 @@ import org.jetbrains.kotlin.psi.psiUtil.referenceExpression * it should be ONLY node of File elementType * @param filterForVariables condition to filter */ -abstract class VariablesSearch(val node: ASTNode, private val filterForVariables: (KtProperty) -> Boolean) { +abstract class VariablesSearch(val node: ASTNode, + private val filterForVariables: (KtProperty) -> Boolean) { /** * to complete implementation of a search mechanism you need to specify what and how you will search in current scope * [this] - scope where to search the usages/assignments/e.t.c of the variable (can be of types KtBlockExpression/KtFile/KtClassBody) @@ -45,7 +50,7 @@ abstract class VariablesSearch(val node: ASTNode, private val filterForVariables "To collect all variables in a file you need to provide file root node" } return node - .findAllNodesWithSpecificType(ElementType.PROPERTY) + .findAllDescendantsWithSpecificType(ElementType.PROPERTY) .map { it.psi as KtProperty } .filter(filterForVariables) .associateWith { it.getSearchResults() } diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt index bc9ec08ce3..f0889812c5 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithAssignmentSearch.kt @@ -1,9 +1,16 @@ -@file:Suppress("MISSING_KDOC_TOP_LEVEL", "KDOC_NO_CONSTRUCTOR_PROPERTY", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", - "KDOC_WITHOUT_RETURN_TAG", "KDOC_NO_EMPTY_TAGS") +@file:Suppress( + "MISSING_KDOC_TOP_LEVEL", + "KDOC_NO_CONSTRUCTOR_PROPERTY", + "MISSING_KDOC_CLASS_ELEMENTS", + "MISSING_KDOC_ON_FUNCTION", + "KDOC_WITHOUT_PARAM_TAG", + "KDOC_WITHOUT_RETURN_TAG", + "KDOC_NO_EMPTY_TAGS" +) package org.cqfn.diktat.ruleset.utils.search -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.isGoingAfter import com.pinterest.ktlint.core.ast.ElementType @@ -22,7 +29,7 @@ class VariablesWithAssignmentSearch(fileNode: ASTNode, * @return */ override fun KtElement.getAllSearchResults(property: KtProperty) = this.node - .findAllNodesWithSpecificType(ElementType.BINARY_EXPRESSION) + .findAllDescendantsWithSpecificType(ElementType.BINARY_EXPRESSION) // filtering out all usages that are declared in the same context but are going before the variable declaration // AND checking that there is an assignment .filter { diff --git a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt index c18d7e115f..60b747d376 100644 --- a/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt +++ b/diktat-rules/src/main/kotlin/org/cqfn/diktat/ruleset/utils/search/VariablesWithUsagesSearch.kt @@ -1,9 +1,16 @@ -@file:Suppress("MISSING_KDOC_TOP_LEVEL", "KDOC_NO_CONSTRUCTOR_PROPERTY", "MISSING_KDOC_CLASS_ELEMENTS", "MISSING_KDOC_ON_FUNCTION", "KDOC_WITHOUT_PARAM_TAG", - "KDOC_WITHOUT_RETURN_TAG", "KDOC_NO_EMPTY_TAGS") +@file:Suppress( + "MISSING_KDOC_TOP_LEVEL", + "KDOC_NO_CONSTRUCTOR_PROPERTY", + "MISSING_KDOC_CLASS_ELEMENTS", + "MISSING_KDOC_ON_FUNCTION", + "KDOC_WITHOUT_PARAM_TAG", + "KDOC_WITHOUT_RETURN_TAG", + "KDOC_NO_EMPTY_TAGS" +) package org.cqfn.diktat.ruleset.utils.search -import org.cqfn.diktat.ruleset.utils.findAllNodesWithSpecificType +import org.cqfn.diktat.ruleset.utils.findAllDescendantsWithSpecificType import org.cqfn.diktat.ruleset.utils.isGoingAfter import com.pinterest.ktlint.core.ast.ElementType @@ -15,7 +22,7 @@ import org.jetbrains.kotlin.psi.KtProperty class VariablesWithUsagesSearch(fileNode: ASTNode, filterForVariables: (KtProperty) -> Boolean) : VariablesSearch(fileNode, filterForVariables) { override fun KtElement.getAllSearchResults(property: KtProperty) = this.node - .findAllNodesWithSpecificType(ElementType.REFERENCE_EXPRESSION) + .findAllDescendantsWithSpecificType(ElementType.REFERENCE_EXPRESSION) // filtering out all usages that are declared in the same context but are going before the variable declaration .filter { it.isGoingAfter(property.node) } .map { it.psi as KtNameReferenceExpression } diff --git a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml index d63ca15da0..fd01f2544f 100644 --- a/diktat-rules/src/main/resources/diktat-analysis-huawei.yml +++ b/diktat-rules/src/main/resources/diktat-analysis-huawei.yml @@ -8,6 +8,7 @@ disabledChapters: "" testDirs: test kotlinVersion: 1.4.30 + srcDirectories: "main" # Checks that the Class/Enum/Interface name does not match Pascal case - name: CLASS_NAME_INCORRECT enabled: true @@ -489,4 +490,7 @@ enabled: true # If file contains class, then it can't contain extension functions for the same class - name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT 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 c5afadd942..1bfce0581c 100644 --- a/diktat-rules/src/main/resources/diktat-analysis.yml +++ b/diktat-rules/src/main/resources/diktat-analysis.yml @@ -6,6 +6,7 @@ testDirs: test disabledChapters: "" kotlinVersion: 1.4 + srcDirectories: "main" # Checks that the Class/Enum/Interface name does not match Pascal case - name: CLASS_NAME_INCORRECT enabled: true @@ -485,4 +486,7 @@ enabled: true # If file contains class, then it can't contain extension functions for the same class - name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT enabled: true \ No newline at end of file diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt index ea4e5b813d..22ff9a0a90 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/IdentifierNamingWarnTest.kt @@ -122,7 +122,11 @@ class IdentifierNamingWarnTest : LintTestBase(::IdentifierNaming) { } @Test - @Tags(Tag(WarningNames.CLASS_NAME_INCORRECT), Tag(WarningNames.VARIABLE_NAME_INCORRECT_FORMAT), Tag(WarningNames.CONSTANT_UPPERCASE)) + @Tags( + Tag(WarningNames.CLASS_NAME_INCORRECT), + Tag(WarningNames.VARIABLE_NAME_INCORRECT_FORMAT), + Tag(WarningNames.CONSTANT_UPPERCASE) + ) fun `check identifiers case format (check - negative)`() { val code = """ diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt index 4e2b3043b1..1f700f71ce 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter1/PackageNamingWarnTest.kt @@ -25,6 +25,13 @@ class PackageNamingWarnTest : LintTestBase(::PackageNaming) { private val rulesConfigListEmptyDomainName: List = listOf( RulesConfig("DIKTAT_COMMON", true, mapOf("domainName" to "")) ) + private val rulesConfigSourceDirectories: List = listOf( + RulesConfig("DIKTAT_COMMON", true, mapOf( + "domainName" to "org.cqfn.diktat", + "srcDirectories" to "nativeMain, mobileMain", + "testDirs" to "nativeTest" + )) + ) @Test @Tag(WarningNames.PACKAGE_NAME_MISSING) @@ -148,6 +155,84 @@ class PackageNamingWarnTest : LintTestBase(::PackageNaming) { ) } + @Test + @Tag(WarningNames.PACKAGE_NAME_INCORRECT_SYMBOLS) + fun `test source config`() { + lintMethod( + + """ + package org.cqfn.diktat.domain + + import org.cqfn.diktat.a.b.c + + /** + * testComment + */ + class TestPackageName { } + + """.trimIndent(), + fileName = "~/diktat/diktat-rules/src/nativeMain/kotlin/org/cqfn/diktat/domain/BlaBla.kt", + rulesConfigList = rulesConfigSourceDirectories + ) + + lintMethod( + + """ + package org.cqfn.diktat.domain + + import org.cqfn.diktat.a.b.c + + /** + * testComment + */ + class TestPackageName { } + + """.trimIndent(), + LintError(1, 9, ruleId, "${PACKAGE_NAME_INCORRECT_PATH.warnText()} org.cqfn.diktat.main.kotlin.org.cqfn.diktat.domain", true), + fileName = "~/diktat/diktat-rules/src/main/kotlin/org/cqfn/diktat/domain/BlaBla.kt", + rulesConfigList = rulesConfigSourceDirectories + ) + } + + @Test + @Tag(WarningNames.PACKAGE_NAME_INCORRECT_SYMBOLS) + fun `test directories for test config`() { + lintMethod( + + """ + package org.cqfn.diktat.domain + + import org.cqfn.diktat.a.b.c + + /** + * testComment + */ + class TestPackageName { } + + """.trimIndent(), + fileName = "~/diktat/diktat-rules/src/nativeTest/kotlin/org/cqfn/diktat/domain/BlaBla.kt", + rulesConfigList = rulesConfigSourceDirectories + ) + + lintMethod( + + """ + package org.cqfn.diktat.domain + + import org.cqfn.diktat.a.b.c + + /** + * testComment + */ + class TestPackageName { } + + """.trimIndent(), + LintError(1, 9, ruleId, "${PACKAGE_NAME_INCORRECT_PATH.warnText()} org.cqfn.diktat.test.kotlin.org.cqfn.diktat.domain", true), + fileName = "~/diktat/diktat-rules/src/test/kotlin/org/cqfn/diktat/domain/BlaBla.kt", + rulesConfigList = rulesConfigSourceDirectories + ) + } + @Test @Tag(WarningNames.PACKAGE_NAME_INCORRECT_PATH) fun `regression - incorrect warning on file under test directory`() { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt index 43150abcfe..53d0f3a217 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/CommentsFormattingFixTest.kt @@ -19,7 +19,12 @@ class CommentsFormattingFixTest : FixTestBase("test/paragraph2/kdoc/", ::Comment } @Test - @Tags(Tag(WRONG_NEWLINES_AROUND_KDOC), Tag(COMMENT_WHITE_SPACE), Tag(IF_ELSE_COMMENTS), Tag(FIRST_COMMENT_NO_BLANK_LINE)) + @Tags( + Tag(WRONG_NEWLINES_AROUND_KDOC), + Tag(COMMENT_WHITE_SPACE), + Tag(IF_ELSE_COMMENTS), + Tag(FIRST_COMMENT_NO_BLANK_LINE) + ) fun `check lines and spaces in comments`() { fixAndCompare("KdocCodeBlocksFormattingExpected.kt", "KdocCodeBlocksFormattingTest.kt") } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingFixTest.kt index 4011181830..9c7e6a15f0 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocFormattingFixTest.kt @@ -50,4 +50,10 @@ class KdocFormattingFixTest : FixTestBase("test/paragraph2/kdoc/", ::KdocFormatt fun `KdocFormatting - all warnings`() { fixAndCompare("KdocFormattingFullExpected.kt", "KdocFormattingFullTest.kt") } + + @Test + @Tags(Tag(WarningNames.KDOC_NO_DEPRECATED_TAG), Tag(WarningNames.KDOC_NO_NEWLINES_BETWEEN_BASIC_TAGS)) + fun `KdocFormatting - sort order`() { + fixAndCompare("KdocFormattingOrderExpected.kt", "KdocFormattingOrderTest.kt") + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsFixTest.kt index 440f5cdfdb..ae8a5074c9 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsFixTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsFixTest.kt @@ -47,7 +47,11 @@ class KdocMethodsFixTest : FixTestBase("test/paragraph2/kdoc/package/src/main/ko } @Test - @Tags(Tag(WarningNames.KDOC_WITHOUT_PARAM_TAG), Tag(WarningNames.KDOC_WITHOUT_RETURN_TAG), Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG)) + @Tags( + Tag(WarningNames.KDOC_WITHOUT_PARAM_TAG), + Tag(WarningNames.KDOC_WITHOUT_RETURN_TAG), + Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG) + ) fun `KdocMethods rule should reformat code (full example)`() { fixAndCompare("KdocMethodsFullExpected.kt", "KdocMethodsFullTested.kt") } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsTest.kt index 0e3b8fa47d..9002b23029 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter2/KdocMethodsTest.kt @@ -27,7 +27,11 @@ class KdocMethodsTest : LintTestBase(::KdocMethods) { """.trimIndent() @Test - @Tags(Tag(WarningNames.KDOC_WITHOUT_PARAM_TAG), Tag(WarningNames.KDOC_WITHOUT_RETURN_TAG), Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG)) + @Tags( + Tag(WarningNames.KDOC_WITHOUT_PARAM_TAG), + Tag(WarningNames.KDOC_WITHOUT_RETURN_TAG), + Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG) + ) fun `Accessible methods with parameters, return type and throws should have proper KDoc (positive example)`() { val validCode = """ /** @@ -51,8 +55,12 @@ class KdocMethodsTest : LintTestBase(::KdocMethods) { } @Test - @Tags(Tag(WarningNames.KDOC_WITHOUT_PARAM_TAG), Tag(WarningNames.KDOC_WITHOUT_RETURN_TAG), Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG), - Tag(WarningNames.MISSING_KDOC_ON_FUNCTION)) + @Tags( + Tag(WarningNames.KDOC_WITHOUT_PARAM_TAG), + Tag(WarningNames.KDOC_WITHOUT_RETURN_TAG), + Tag(WarningNames.KDOC_WITHOUT_THROWS_TAG), + Tag(WarningNames.MISSING_KDOC_ON_FUNCTION) + ) fun `Warning should not be triggered for functions in tests`() { val validCode = "@Test $funCode" val complexAnnotationCode = "@Anno(test = [\"args\"]) $funCode" diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BlockStructureBracesWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BlockStructureBracesWarnTest.kt index 6fbcb92a84..b34828e1d4 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BlockStructureBracesWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/BlockStructureBracesWarnTest.kt @@ -85,6 +85,20 @@ class BlockStructureBracesWarnTest : LintTestBase(::BlockStructureBraces) { ) } + @Test + @Tag(WarningNames.BRACES_BLOCK_STRUCTURE_ERROR) + fun `check lambda with empty block`() { + lintMethod( + """ + |fun foo() { + | run { + | + | } + |} + """.trimMargin() + ) + } + @Test @Tag(WarningNames.BRACES_BLOCK_STRUCTURE_ERROR) fun `check empty block in else expression`() { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt index 656ac7af05..c2a87615f0 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/EmptyBlockWarnTest.kt @@ -8,6 +8,8 @@ import org.cqfn.diktat.util.LintTestBase import com.pinterest.ktlint.core.LintError import generated.WarningNames +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.junit.jupiter.api.Tag import org.junit.jupiter.api.Test @@ -96,39 +98,39 @@ class EmptyBlockWarnTest : LintTestBase(::EmptyBlock) { } @Test - fun `check if-else expression without block`() { + fun `empty lambda`() { lintMethod( """ |fun foo() { - | if (node.treeParent != null) return else println(true) + | run { } |} - """.trimMargin() + """.trimMargin(), + rulesConfigList = rulesConfigListEmptyBlockExist ) } @Test - fun `check for expresion and while without block`() { + fun `check if-else expression without block`() { lintMethod( """ |fun foo() { - | for(x in 0..10) println(x) - | val x = 10 - | while (x > 0) - | --x + | if (node.treeParent != null) return else println(true) |} """.trimMargin() ) } @Test - fun `check empty lambda`() { + fun `check for expresion and while without block`() { lintMethod( """ |fun foo() { - | val y = listOf().map {} + | for(x in 0..10) println(x) + | val x = 10 + | while (x > 0) + | --x |} - """.trimMargin(), - LintError(2, 30, ruleId, "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} empty blocks are forbidden unless it is function with override keyword", false) + """.trimMargin() ) } @@ -138,14 +140,28 @@ class EmptyBlockWarnTest : LintTestBase(::EmptyBlock) { lintMethod( """ |fun foo() { - | val y = listOf().map {} + | val y = listOf().map { + | + | } |} """.trimMargin(), - LintError(2, 30, ruleId, "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} different style for empty block", true), + LintError(2, 30, ruleId, "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} do not put newlines in empty lambda", true), rulesConfigList = rulesConfigListEmptyBlockExist ) } + @Test + fun `check empty lambda`() { + lintMethod( + """ + |fun foo() { + | val y = listOf().map { } + |} + """.trimMargin(), + LintError(2, 30, ruleId, "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} empty blocks are forbidden unless it is function with override keyword", false) + ) + } + @Test fun `should not trigger on anonymous SAM classes #1`() { lintMethod( @@ -190,4 +206,38 @@ class EmptyBlockWarnTest : LintTestBase(::EmptyBlock) { rulesConfigList = rulesConfigListEmptyBlockExist ) } + + @Test + @Tag(WarningNames.EMPTY_BLOCK_STRUCTURE_ERROR) + fun `should not trigger on empty lambdas as a functions`() { + lintMethod( + """ + |fun foo(bar: () -> Unit = {}) + | + |class Some { + | fun bar() { + | A({}) + | } + |} + """.trimMargin(), + rulesConfigList = rulesConfigListEmptyBlockExist + ) + } + + @Test + @Tag(WarningNames.EMPTY_BLOCK_STRUCTURE_ERROR) + fun `should not trigger on empty lambdas as a functions #2`() { + lintMethod( + """ + |fun some() { + | val project = KotlinCoreEnvironment.createForProduction( + | { }, + | compilerConfiguration, + | EnvironmentConfigFiles.JVM_CONFIG_FILES + | ).project + |} + """.trimMargin(), + rulesConfigList = rulesConfigListEmptyBlockExist + ) + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationRuleFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationRuleFixTest.kt new file mode 100644 index 0000000000..2705a85f33 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationRuleFixTest.kt @@ -0,0 +1,24 @@ +package org.cqfn.diktat.ruleset.chapter3 + +import org.cqfn.diktat.common.config.rules.RulesConfig +import org.cqfn.diktat.ruleset.constants.Warnings +import org.cqfn.diktat.ruleset.rules.chapter3.StringConcatenationRule +import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class StringConcatenationRuleFixTest : FixTestBase( + "test/paragraph3/string_concatenation", + ::StringConcatenationRule, + listOf( + RulesConfig(Warnings.STRING_CONCATENATION.name, true, emptyMap()) + ) +) { + @Test + @Tag(WarningNames.STRING_CONCATENATION) + fun `fixing string concatenation`() { + fixAndCompare("StringConcatenationExpected.kt", "StringConcatenationTest.kt") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt index b3c8d49a6d..90ff084190 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/StringConcatenationWarnTest.kt @@ -12,7 +12,7 @@ import org.junit.jupiter.api.Test class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { private val ruleId = "$DIKTAT_RULE_SET_ID:string-concatenation" - private val canBeAutoCorrected = false + private val canBeAutoCorrected = true @Test @Tag(WarningNames.STRING_CONCATENATION) @@ -48,7 +48,9 @@ class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { """ | val a = (1 + 2).toString() + "my string" + 3 | - """.trimMargin() + """.trimMargin(), + LintError(1, 10, ruleId, Warnings.STRING_CONCATENATION.warnText() + + " (1 + 2).toString() + \"my string\" + 3", canBeAutoCorrected) ) } @@ -60,7 +62,9 @@ class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { | val myObject = 12 | val a = (1 + 2).toString() + "my string" + 3 + "string" + myObject + myObject | - """.trimMargin() + """.trimMargin(), + LintError(2, 10, ruleId, Warnings.STRING_CONCATENATION.warnText() + + " (1 + 2).toString() + \"my string\" + 3 + \"string\" + myObject + myObject", canBeAutoCorrected) ) } @@ -73,7 +77,7 @@ class StringConcatenationWarnTest : LintTestBase(::StringConcatenationRule) { | val a = (1 + 2).toString() + "my string" + ("string" + myObject) + myObject | """.trimMargin(), - LintError(2, 46, ruleId, Warnings.STRING_CONCATENATION.warnText() + + LintError(2, 10, ruleId, Warnings.STRING_CONCATENATION.warnText() + " (1 + 2).toString() + \"my string\" + (\"string\" + myObject) + myObject", canBeAutoCorrected) ) } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/BlankLinesWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/BlankLinesWarnTest.kt index 02a2b2947b..ff4ddae24e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/BlankLinesWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/BlankLinesWarnTest.kt @@ -30,6 +30,20 @@ class BlankLinesWarnTest : LintTestBase(::BlankLinesRule) { ) } + @Test + @Tag(WarningNames.TOO_MANY_BLANK_LINES) + fun `check lambda with empty block`() { + lintMethod( + """ + |fun foo() { + | run { + | + | } + |} + """.trimMargin() + ) + } + @Test @Tag(WarningNames.TOO_MANY_BLANK_LINES) fun `should prohibit usage of two or more consecutive blank lines`() { 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 0cda5145d4..8af3ecad11 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 @@ -45,6 +45,18 @@ class NewlinesRuleFixTest : FixTestBase("test/paragraph3/newlines", ::NewlinesRu fixAndCompare("CommaExpected.kt", "CommaTest.kt") } + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `should fix wrong newlines before colon`() { + fixAndCompare("ColonExpected.kt", "ColonTest.kt") + } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `One line parameters list sheet must contain no more than 2 parameters`() { + fixAndCompare("sizeParameterListExpected.kt", "sizeParameterListTest.kt") + } + @Test @Tag(WarningNames.WRONG_NEWLINES) fun `should fix wrong newlines in lambdas`() { @@ -68,4 +80,10 @@ class NewlinesRuleFixTest : FixTestBase("test/paragraph3/newlines", ::NewlinesRu fun `should fix one line function with and without semicolon`() { fixAndCompare("OneLineFunctionExpected.kt", "OneLineFunctionTest.kt") } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `list argument in lambda`() { + fixAndCompare("ListArgumentLambdaExpected.kt", "ListArgumentLambdaTest.kt") + } } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt index 61dc547f73..a159f18af6 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/files/NewlinesRuleWarnTest.kt @@ -31,6 +31,7 @@ class NewlinesRuleWarnTest : LintTestBase(::NewlinesRule) { private val functionalStyleWarn = "${WRONG_NEWLINES.warnText()} should follow functional style at" private val lparWarn = "${WRONG_NEWLINES.warnText()} opening parentheses should not be separated from constructor or function name" private val commaWarn = "${WRONG_NEWLINES.warnText()} newline should be placed only after comma" + private val prevColonWarn = "${WRONG_NEWLINES.warnText()} newline shouldn't be placed before colon" private val lambdaWithArrowWarn = "${WRONG_NEWLINES.warnText()} in lambda with several lines in body newline should be placed after an arrow" private val lambdaWithoutArrowWarn = "${WRONG_NEWLINES.warnText()} in lambda with several lines in body newline should be placed after an opening brace" private val singleReturnWarn = "${WRONG_NEWLINES.warnText()} functions with single return statement should be simplified to expression body" @@ -313,6 +314,68 @@ class NewlinesRuleWarnTest : LintTestBase(::NewlinesRule) { ) } + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `newline shouldn't be placed before colon`() { + lintMethod( + """ + |fun foo(a + | : Int, + | b + | : Int) { + | bar(a, b) + |} + """.trimMargin(), + LintError(2, 9, ruleId, prevColonWarn, true), + LintError(4, 9, ruleId, prevColonWarn, true) + ) + } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `One line parameters list sheet must contain no more than 2 parameters`() { + lintMethod( + """ + |fun foo(a: Int, b: Int, c: Int) { + | bar(a, b) + |} + """.trimMargin(), + LintError(1, 8, ruleId, "${WRONG_NEWLINES.warnText()} first parameter should be placed on a separate line or all other parameters " + + "should be aligned with it in declaration of ", true), + LintError(1, 8, ruleId, "${WRONG_NEWLINES.warnText()} value parameters should be placed on different lines in declaration of ", true) + ) + } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `newline after colon`() { + lintMethod( + """ + |fun foo(a: + | Int, + | b: + | Int) { + | bar(a, b) + |} + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `list parameter should be placed on different lines`() { + lintMethod( + """ + |fun foo( + | a: Int, + | b: Int, + | c: Int + | ) { + |} + """.trimMargin(), + ) + } + @Test @Tag(WarningNames.WRONG_NEWLINES) fun `function name should not be separated from ( - positive example`() { @@ -614,7 +677,10 @@ class NewlinesRuleWarnTest : LintTestBase(::NewlinesRule) { LintError(3, 10, ruleId, "${WRONG_NEWLINES.warnText()} value parameters should be placed on different lines in declaration of ", true), LintError(4, 16, ruleId, "${WRONG_NEWLINES.warnText()} first parameter should be placed on a separate line or all other parameters " + "should be aligned with it in declaration of ", true), - LintError(4, 16, ruleId, "${WRONG_NEWLINES.warnText()} value parameters should be placed on different lines in declaration of ", true) + LintError(4, 16, ruleId, "${WRONG_NEWLINES.warnText()} value parameters should be placed on different lines in declaration of ", true), + LintError(4, 62, ruleId, "${WRONG_NEWLINES.warnText()} first value argument (arg1) should be placed on the new line or " + + "all other parameters should be aligned with it", true), + LintError(4, 62, ruleId, "${WRONG_NEWLINES.warnText()} value arguments should be placed on different lines", true) ) } @@ -643,6 +709,49 @@ class NewlinesRuleWarnTest : LintTestBase(::NewlinesRule) { ) } + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `should not raise warning on value arguments`() { + lintMethod( + """ + |class SomeRule(configRules: List) : Rule("id", + |configRules, + |listOf("foo", "baz")) { + | + |} + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `should not raise warning on list params`() { + lintMethod( + """ + |class SomeRule(configRules: List) : Rule("id", + |configRules, + |listOf("foo", "baz", "triple", "bar")) { + | + |} + """.trimMargin() + ) + } + + @Test + @Tag(WarningNames.WRONG_NEWLINES) + fun `should raise warning on value arguments`() { + lintMethod( + """ + |class SomeRule(configRules: List) : Rule("id", configRules, listOf("foo", "baz")) { + | + |} + """.trimMargin(), + LintError(1, 46, ruleId, "${WRONG_NEWLINES.warnText()} first value argument (\"id\") should be placed on the new line or " + + "all other parameters should be aligned with it", true), + LintError(1, 46, ruleId, "${WRONG_NEWLINES.warnText()} value arguments should be placed on different lines", true), + ) + } + @Test @Tag(WarningNames.WRONG_NEWLINES) fun `should suggest newlines in a long supertype list`() { diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt index 5d803c7965..d73159b3ca 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter3/spaces/IndentationRuleWarnTest.kt @@ -606,5 +606,30 @@ class IndentationRuleWarnTest : LintTestBase(::IndentationRule) { ) } + @Test + @Tag(WarningNames.WRONG_INDENTATION) + fun `should trigger on string templates starting with new line`() { + lintMethod( + """ + |fun foo(some: String) { + | fun bar() { + | val a = "${'$'}{ + | expression + | .foo() + | .bar() + | }" + | } + | + | val b = "${'$'}{ foo().bar() }" + |} + | + """.trimMargin(), + LintError(4, 1, ruleId, warnText(12, 8), true), + LintError(5, 1, ruleId, warnText(16, 12), true), + LintError(6, 1, ruleId, warnText(16, 12), true), + rulesConfigList = rulesConfigList + ) + } + private fun warnText(expected: Int, actual: Int) = "${WRONG_INDENTATION.warnText()} expected $expected but was $actual" } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/RunInScriptFixTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/RunInScriptFixTest.kt new file mode 100644 index 0000000000..5961acb89d --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/RunInScriptFixTest.kt @@ -0,0 +1,16 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import org.cqfn.diktat.ruleset.rules.chapter6.RunInScript +import org.cqfn.diktat.util.FixTestBase + +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class RunInScriptFixTest : FixTestBase("test/chapter6/script", ::RunInScript) { + @Test + @Tag(WarningNames.RUN_IN_SCRIPT) + fun `should wrap into run`() { + fixAndCompare("SimpleRunInScriptExpected.kts", "SimpleRunInScriptTest.kts") + } +} diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/RunInScriptWarnTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/RunInScriptWarnTest.kt new file mode 100644 index 0000000000..8deb1d9ac1 --- /dev/null +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/chapter6/RunInScriptWarnTest.kt @@ -0,0 +1,133 @@ +package org.cqfn.diktat.ruleset.chapter6 + +import org.cqfn.diktat.ruleset.constants.Warnings.RUN_IN_SCRIPT +import org.cqfn.diktat.ruleset.rules.DIKTAT_RULE_SET_ID +import org.cqfn.diktat.ruleset.rules.chapter6.RunInScript +import org.cqfn.diktat.util.LintTestBase + +import com.pinterest.ktlint.core.LintError +import generated.WarningNames +import org.junit.jupiter.api.Tag +import org.junit.jupiter.api.Test + +class RunInScriptWarnTest : LintTestBase(::RunInScript) { + private val ruleId: String = "$DIKTAT_RULE_SET_ID:run-script" + + @Test + @Tag(WarningNames.RUN_IN_SCRIPT) + fun `check simple example`() { + lintMethod( + """ + class A {} + + fun foo() { + } + + diktat {} + + diktat({}) + + foo/*df*/() + + foo( //dfdg + 10 + ) + println("hello") + + w.map { it -> it } + + tasks.register("a") { + dependsOn("b") + doFirst { + generateCodeStyle(file("rootDir/guide"), file("rootDir/../wp")) + } + } + + """.trimMargin(), + LintError(10, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} foo/*df*/()", true), + LintError(12, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} foo( //dfdg...", true), + LintError(15, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} println(\"hello\")", true), + LintError(17, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} w.map { it -> it }", true), + LintError(19, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} tasks.register(\"a\") {...", true), + fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts" + ) + } + + @Test + @Tag(WarningNames.RUN_IN_SCRIPT) + fun `check correct examples`() { + lintMethod( + """ + run { + println("hello") + } + + run{println("hello")} + + val task = tasks.register("a") { + } + + """.trimMargin(), + fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts" + ) + } + + @Test + @Tag(WarningNames.RUN_IN_SCRIPT) + fun `check correct with custom wrapper`() { + lintMethod( + """ + custom { + println("hello") + } + + oneMore{println("hello")} + + another { + println("hello") + } + """.trimMargin(), + fileName = "src/main/kotlin/org/cqfn/diktat/Example.kts" + ) + } + + @Test + @Tag(WarningNames.RUN_IN_SCRIPT) + fun `check gradle file`() { + lintMethod( + """ + class A {} + + fun foo() { + } + + if(true) { + goo() + } + + diktat {} + + diktat({}) + + foo/*df*/() + + foo( //dfdg + 10 + ) + println("hello") + + w.map { it -> it } + + (tasks.register("a") { + dependsOn("b") + doFirst { + generateCodeStyle(file("rootDir/guide"), file("rootDir/../wp")) + } + }) + + """.trimMargin(), + LintError(6, 17, ruleId, "${RUN_IN_SCRIPT.warnText()} if(true) {...", true), + fileName = "src/main/kotlin/org/cqfn/diktat/builds.gradle.kts" + ) + } +} 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 ba81e98b34..1bd5a84e29 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 @@ -8,6 +8,7 @@ import org.cqfn.diktat.ruleset.constants.Warnings.EMPTY_BLOCK_STRUCTURE_ERROR import org.cqfn.diktat.ruleset.constants.Warnings.FILE_NAME_MATCH_CLASS import org.cqfn.diktat.ruleset.constants.Warnings.HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_NO_EMPTY_TAGS +import org.cqfn.diktat.ruleset.constants.Warnings.KDOC_WITHOUT_PARAM_TAG import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_CLASS_ELEMENTS import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_ON_FUNCTION import org.cqfn.diktat.ruleset.constants.Warnings.MISSING_KDOC_TOP_LEVEL @@ -160,7 +161,11 @@ class DiktatSmokeTest : FixTestBase("test/smoke/src/main/kotlin", fun `smoke test #2`() { fixAndCompare("Example2Expected.kt", "Example2Test.kt") unfixedLintErrors.assertEquals( - LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} there are 2 declared classes and/or objects", false) + LintError(1, 1, "$DIKTAT_RULE_SET_ID:header-comment", "${HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE.warnText()} there are 2 declared classes and/or objects", false), + LintError(15, 23, "$DIKTAT_RULE_SET_ID:kdoc-methods", + "${KDOC_WITHOUT_PARAM_TAG.warnText()} createWithFile (containerName)", true), + LintError(31, 13, "$DIKTAT_RULE_SET_ID:empty-block-structure", + "${EMPTY_BLOCK_STRUCTURE_ERROR.warnText()} empty blocks are forbidden unless it is function with override keyword", false) ) } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt index 0f49f436c8..f5fa29987a 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AstNodeUtilsTest.kt @@ -1,4 +1,8 @@ -@file:Suppress("HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE", "LOCAL_VARIABLE_EARLY_DECLARATION", "AVOID_NULL_CHECKS") +@file:Suppress( + "HEADER_MISSING_IN_NON_SINGLE_CLASS_FILE", + "LOCAL_VARIABLE_EARLY_DECLARATION", + "AVOID_NULL_CHECKS" +) package org.cqfn.diktat.ruleset.utils @@ -144,7 +148,7 @@ class AstNodeUtilsTest { """.trimIndent() val list = listOf("Test", "foo", "a", "a", "Int", "Int", "a") applyToCode(code, 7) { node, counter -> - node.getAllIdentifierChildren().ifNotEmpty { + node.getAllChildrenWithType(IDENTIFIER).ifNotEmpty { this.forEach { Assertions.assertEquals(list[counter.get()], it.text) } counter.incrementAndGet() } @@ -553,7 +557,7 @@ class AstNodeUtilsTest { listResults.add(node) } } - val listTypes = firstNode?.findAllNodesWithSpecificType(IDENTIFIER) + val listTypes = firstNode?.findAllDescendantsWithSpecificType(IDENTIFIER) Assertions.assertEquals(listResults, listTypes) } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt index 8f03e57175..6ac304775e 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/AvailableRulesDocTest.kt @@ -28,7 +28,7 @@ class AvailableRulesDocTest { @Test fun `read rules from documentation`() { - val allRulesFromCode = Warnings.values() + val allRulesFromCode = Warnings.values().filterNot { it == Warnings.DUMMY_TEST_WARNING } val allRulesFromDoc = getAllRulesFromDoc() allRulesFromCode.forEach { warning -> diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt index ff1ab3e719..16f2bfc6eb 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/ruleset/utils/KotlinParserTest.kt @@ -27,7 +27,7 @@ class KotlinParserTest { val node = KotlinParser().createNode("val x: Int = 10") Assertions.assertEquals(PROPERTY, node.elementType) Assertions.assertEquals("val x: Int = 10", node.text) - Assertions.assertEquals(4, node.findAllNodesWithSpecificType(WHITE_SPACE).size) + Assertions.assertEquals(4, node.findAllDescendantsWithSpecificType(WHITE_SPACE).size) } @Test @@ -37,7 +37,7 @@ class KotlinParserTest { Assertions.assertEquals(FUN, node.elementType) Assertions.assertEquals("fun foo(text: String) = text.toUpperCase()", node.text) Assertions.assertEquals("foo", node.getIdentifierName()!!.text) - Assertions.assertEquals(4, node.findAllNodesWithSpecificType(WHITE_SPACE).size) + Assertions.assertEquals(4, node.findAllDescendantsWithSpecificType(WHITE_SPACE).size) } @Test @@ -96,7 +96,11 @@ class KotlinParserTest { } @Test - @Suppress("UnsafeCallOnNullableType", "TOO_LONG_FUNCTION", "AVOID_NULL_CHECKS") + @Suppress( + "UnsafeCallOnNullableType", + "TOO_LONG_FUNCTION", + "AVOID_NULL_CHECKS" + ) fun `test multiline class code compare with applyToCode`() { val emptyClass = """ |package org.cqfn.diktat.ruleset.utils diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt index 58ef54f4ee..5661b44b08 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/DiktatRuleSetProvider4Test.kt @@ -40,7 +40,7 @@ class DiktatRuleSetProviderTest { .filter { it.isFile } .map { it.nameWithoutExtension } .filterNot { it in ignoreFile } - val rulesName = DiktatRuleSetProvider().get().map { it::class.simpleName!! } + val rulesName = DiktatRuleSetProvider().get().map { it::class.simpleName!! }.filter { it != "DummyWarning" } Assertions.assertEquals(filesName.sorted().toList(), rulesName.sorted()) } diff --git a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt index daf3fa71f1..94ad54041d 100644 --- a/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt +++ b/diktat-rules/src/test/kotlin/org/cqfn/diktat/util/FixTestBase.kt @@ -22,7 +22,8 @@ open class FixTestBase(protected val resourceFilePath: String, ruleSupplier: (rulesConfigList: List) -> Rule, rulesConfigList: List? = null, cb: LintErrorCallback = defaultCallback) : this( - resourceFilePath, { overrideRulesConfigList -> DiktatRuleSetProvider4Test(ruleSupplier, overrideRulesConfigList) }, + resourceFilePath, + { overrideRulesConfigList -> DiktatRuleSetProvider4Test(ruleSupplier, overrideRulesConfigList) }, cb, rulesConfigList ) diff --git a/diktat-rules/src/test/resources/test/chapter6/script/SimpleRunInScriptExpected.kts b/diktat-rules/src/test/resources/test/chapter6/script/SimpleRunInScriptExpected.kts new file mode 100644 index 0000000000..7c0b81a2a1 --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/script/SimpleRunInScriptExpected.kts @@ -0,0 +1,18 @@ + +run { + println("hello world!") +} + +fun foo() { + println() +} + +val q = Config() + +run { + println("a") +} + +also { + println("a") +} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/chapter6/script/SimpleRunInScriptTest.kts b/diktat-rules/src/test/resources/test/chapter6/script/SimpleRunInScriptTest.kts new file mode 100644 index 0000000000..285d668e58 --- /dev/null +++ b/diktat-rules/src/test/resources/test/chapter6/script/SimpleRunInScriptTest.kts @@ -0,0 +1,16 @@ + +println("hello world!") + +fun foo() { + println() +} + +val q = Config() + +run { + println("a") +} + +also { + println("a") +} diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingOrderExpected.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingOrderExpected.kt new file mode 100644 index 0000000000..4c33868daf --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingOrderExpected.kt @@ -0,0 +1,17 @@ +package test.paragraph2.kdoc + +/** + * Creates a docker container with [file], prepared to execute it + * + * @param runConfiguration a [RunConfiguration] for the supplied binary + * @param file a file that will be included as an executable + * @param resources additional resources + * @return id of created container or null if it wasn't created + * @throws DockerException if docker daemon has returned an error + * @throws DockerException if docker daemon has returned an error + * @throws RuntimeException if an exception not specific to docker has occurred + */ +internal fun createWithFile(runConfiguration: RunConfiguration, + containerName: String, + file: File, + resources: Collection = emptySet()): String {} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingOrderTest.kt b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingOrderTest.kt new file mode 100644 index 0000000000..b1a515e31e --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph2/kdoc/KdocFormattingOrderTest.kt @@ -0,0 +1,17 @@ +package test.paragraph2.kdoc + +/** + * Creates a docker container with [file], prepared to execute it + * + * @param runConfiguration a [RunConfiguration] for the supplied binary + * @param file a file that will be included as an executable + * @param resources additional resources + * @throws DockerException if docker daemon has returned an error + * @throws DockerException if docker daemon has returned an error + * @throws RuntimeException if an exception not specific to docker has occurred + * @return id of created container or null if it wasn't created + */ +internal fun createWithFile(runConfiguration: RunConfiguration, + containerName: String, + file: File, + resources: Collection = emptySet()): String {} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockExpected.kt index a47f33cf2b..debdaa86fe 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockExpected.kt @@ -10,8 +10,7 @@ fun foo () { fun goo () { var x = 10 if (x == 10) return else println(10) - val y = listOf().map { -} + val y = listOf().map { } for(x in 0..10) println(x) while (x > 0) --x diff --git a/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockTest.kt b/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockTest.kt index 7316e61550..fb5faa35ff 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockTest.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/empty_block/EmptyBlockTest.kt @@ -9,7 +9,9 @@ fun foo () { fun goo () { var x = 10 if (x == 10) return else println(10) - val y = listOf().map {} + val y = listOf().map { + + } for(x in 0..10) println(x) while (x > 0) --x diff --git a/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Expected.kt b/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Expected.kt index 53f92028ba..890053ba1f 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Expected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Expected.kt @@ -32,5 +32,37 @@ data class Example(val field1: Type1, else foobaz() } + + fun some() { + val a = "${ + foo().bar() + }" + + val b = "${baz().foo()}" + + val c = "${ + expression + .foo() + .bar() + }" + } + + val dockerFileAsText = + """ + FROM $baseImage + COPY resources $resourcesPath + RUN /bin/bash + """.trimIndent() + + val some = + """ + some $foo test + $start another value + """.trimIndent() + + val teeest = + """ + some text $foo $bar another text + """.trimIndent() } diff --git a/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Test.kt b/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Test.kt index 0174a58413..03ca6762d8 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Test.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/indentation/IndentationFull1Test.kt @@ -32,4 +32,36 @@ fun foo( else foobaz() } + + fun some() { + val a = "${ + foo().bar() + }" + + val b = "${baz().foo()}" + + val c = "${ + expression + .foo() + .bar() + }" + } + + val dockerFileAsText = + """ + FROM $baseImage + COPY resources $resourcesPath + RUN /bin/bash + """.trimIndent() + + val some = + """ + some $foo test + $start another value + """.trimIndent() + + val teeest = + """ + some text $foo $bar another text + """.trimIndent() } diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/ColonExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/ColonExpected.kt new file mode 100644 index 0000000000..81edec3196 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/ColonExpected.kt @@ -0,0 +1,4 @@ +package test.paragraph3.newlines + +fun foo(a: Int, + b: Int) { } \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/ColonTest.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/ColonTest.kt new file mode 100644 index 0000000000..842f0c3dcc --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/ColonTest.kt @@ -0,0 +1,6 @@ +package test.paragraph3.newlines + +fun foo(a + : Int, + b + : Int) { } diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/ListArgumentLambdaExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/ListArgumentLambdaExpected.kt new file mode 100644 index 0000000000..ca09bcd93f --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/ListArgumentLambdaExpected.kt @@ -0,0 +1,15 @@ +package test.paragraph3.newlines + +class Example { + + fun foo() { + foldIndexed("#") { index: Int, acc: String, pathPart: String -> + } + } + + fun bar() { + foldIndexed("#") { index, acc, pathPart -> + } + } + +} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/ListArgumentLambdaTest.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/ListArgumentLambdaTest.kt new file mode 100644 index 0000000000..ca09bcd93f --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/ListArgumentLambdaTest.kt @@ -0,0 +1,15 @@ +package test.paragraph3.newlines + +class Example { + + fun foo() { + foldIndexed("#") { index: Int, acc: String, pathPart: String -> + } + } + + fun bar() { + foldIndexed("#") { index, acc, pathPart -> + } + } + +} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/ParameterListExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/ParameterListExpected.kt index c18ac4bf33..cdcff7da86 100644 --- a/diktat-rules/src/test/resources/test/paragraph3/newlines/ParameterListExpected.kt +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/ParameterListExpected.kt @@ -18,7 +18,10 @@ val arg1: Int, constructor( arg1: Int, arg2: String, - arg3: String) : this(arg1, 0, 0) { } + arg3: String) : this( +arg1, + 0, + 0) { } } class Foo(val arg1: Int, diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/sizeParameterListExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/sizeParameterListExpected.kt new file mode 100644 index 0000000000..a6238bcb0a --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/sizeParameterListExpected.kt @@ -0,0 +1,8 @@ +package test.paragraph3.newlines + +fun foo( +a: Int, + b: Int, + c: Int) { + bar(a, b, c) +} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/newlines/sizeParameterListTest.kt b/diktat-rules/src/test/resources/test/paragraph3/newlines/sizeParameterListTest.kt new file mode 100644 index 0000000000..f37d687c44 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/newlines/sizeParameterListTest.kt @@ -0,0 +1,5 @@ +package test.paragraph3.newlines + +fun foo(a: Int, b: Int, c: Int) { + bar(a, b, c) +} \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationExpected.kt b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationExpected.kt new file mode 100644 index 0000000000..87fc861338 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationExpected.kt @@ -0,0 +1,34 @@ +package test.chapter3.strings + +val valueStr = "my str" +val x = 13 + +fun foo(): String { + return "string" +} + +val myTest1 = "my string string $valueStr other value" +val myTest2 = "my string 1$valueStr other value" +val myTest3 = "my string $valueStr string other value" +val myTest4 = "my string one two" +val myTest5 = "trying to sum with multiline" +val myTest6 = "trying to sum with " + """ + multiline""".trimIndent() +val myTest7 = "multiline string" +val myTest8 = "string ${valueStr.replace("my", "")}" +val myTest9 = "string ${"valueStr".replace("my", "")}" +val myTest10 = "string ${(1 + 5)}" +val myTest11 = "sum other string 3 str2 5 other string 2 2 str3 4" +val myTest12 = "my string 123" +val myTest13 = "${(1 + 2)} my string 3 string $valueStr$valueStr" +val myTest14 = "my string ${(1 + 2 + 3)} other string 3${(1 + 2 + 3)}" +val myTest15 = 1 + 2 + ("13").toInt() +val myTest16 = 1.0 + 2.0 + ("13.0").toFloat() +val myTest17 = "sum ${(1 + 2 + 3) * 4}" +val myTest18 = "my string ${(1 + 2 + 3) * 4} other string 3${(1 + (2 + 3))} third string str 5" +val myTest19 = 1 + 2 + 3 + ("65").toInt() +val myTest20 = "${x}string" +val myTest21 = "${x} string" +val myTest22 = "string${foo()}" +val myTest23 = x.toString() + foo() +val myTest24 = foo() + "string" \ No newline at end of file diff --git a/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationTest.kt b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationTest.kt new file mode 100644 index 0000000000..b14425bfa1 --- /dev/null +++ b/diktat-rules/src/test/resources/test/paragraph3/string_concatenation/StringConcatenationTest.kt @@ -0,0 +1,34 @@ +package test.chapter3.strings + +val valueStr = "my str" +val x = 13 + +fun foo(): String { + return "string" +} + +val myTest1 = "my string " + "string " + valueStr + " other value" +val myTest2 = "my string " + 1 + valueStr + " other value" +val myTest3 = "my string " + valueStr + " string " + "other value" +val myTest4 = "my string" + (" one " + "two") +val myTest5 = "trying to sum with " + """multiline""" +val myTest6 = "trying to sum with " + """ + multiline""".trimIndent() +val myTest7 = """multiline""" + " string" +val myTest8 = "string " + valueStr.replace("my", "") +val myTest9 = "string " + "valueStr".replace("my", "") +val myTest10 = "string " + (1 + 5) +val myTest11 = "sum " + ("other string " + 3 + " str2 " + 5 + (" other string 2 " + 2 + " str3 " + 4)) +val myTest12 = "my string " + 1 + 2 + 3 +val myTest13 = (1 + 2).toString() + " my string " + 3 + " string " + valueStr + valueStr +val myTest14 = "my string " + (1 + 2 + 3) + (" other string " + 3) + (1 + 2 + 3) +val myTest15 = 1 + 2 + ("1" + 3).toInt() +val myTest16 = 1.0 + 2.0 + ("1" + 3.0).toFloat() +val myTest17 = "sum " + (1 + 2 + 3) * 4 +val myTest18 = "my string " + (1 + 2 + 3) * 4 + (" other string " + 3) + (1 + (2 + 3)) + (" third string " + ("str " + 5)) +val myTest19 = 1 + 2 + 3 + ("6" + 5).toInt() +val myTest20 = x.toString() + "string" +val myTest21 = x.toString() + " string" +val myTest22 = "string" + foo() +val myTest23 = x.toString() + foo() +val myTest24 = foo() + "string" diff --git a/diktat-rules/src/test/resources/test/smoke/build.gradle_.kts b/diktat-rules/src/test/resources/test/smoke/build.gradle_.kts index 79f8084ff8..041d745d05 100644 --- a/diktat-rules/src/test/resources/test/smoke/build.gradle_.kts +++ b/diktat-rules/src/test/resources/test/smoke/build.gradle_.kts @@ -11,14 +11,14 @@ repositories { maven { url = uri("https://example.com") } } -tasks.register("generateAvailableRules") { +val generateAvailableRules by tasks.register("generateAvailableRules") { dependsOn("generateRulesMapping") doFirst { generateAvailableRules(rootDir, file("$rootDir/../wp")) } } -tasks.register("updateDocumentation") { +val updateDocumentation = tasks.register("updateDocumentation") { dependsOn( "generateRulesMapping", "generateAvailableRules", 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 2dc345d30b..ad5f9be991 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 @@ -15,6 +15,23 @@ private class TestException : Exception() /* this class is unused */ // private class Test : RuntimeException() +/** + * Creates a docker container with [file], prepared to execute it + * + * @param runConfiguration a [RunConfiguration] for the supplied binary + * @param file a file that will be included as an executable + * @param resources additional resources + * @param containerName + * @return id of created container or null if it wasn't created + * @throws DockerException if docker daemon has returned an error + * @throws DockerException if docker daemon has returned an error + * @throws RuntimeException if an exception not specific to docker has occurred + */ +internal fun createWithFile(runConfiguration: RunConfiguration, + containerName: String, + file: File, + resources: Collection = emptySet()) {} + private fun foo(node: ASTNode) { when (node.elementType) { CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node) 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 680550b06a..b6fe5ccc3e 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 @@ -11,6 +11,22 @@ private class Test : Exception() /* this class is unused */ //private class Test : RuntimeException() +/** + * Creates a docker container with [file], prepared to execute it + * + * @param runConfiguration a [RunConfiguration] for the supplied binary + * @param file a file that will be included as an executable + * @param resources additional resources + * @throws DockerException if docker daemon has returned an error + * @throws DockerException if docker daemon has returned an error + * @throws RuntimeException if an exception not specific to docker has occurred + * @return id of created container or null if it wasn't created + */ +internal fun createWithFile(runConfiguration: RunConfiguration, + containerName: String, + file: File, + resources: Collection = emptySet()) {} + private fun foo (node: ASTNode) { when (node.elementType) { CLASS, FUN, PRIMARY_CONSTRUCTOR, SECONDARY_CONSTRUCTOR -> checkAnnotation(node) @@ -24,4 +40,4 @@ private fun foo (node: ASTNode) { foo() setOf(IOException(), Properties(), LoggerFactory()) -} \ No newline at end of file +} diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Expected.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Expected.kt index 7d19f7fefc..165882843a 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Expected.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Expected.kt @@ -18,3 +18,10 @@ val text = x """ +val dockerFileAsText = + """ + FROM $baseImage someTest + COPY resources $resourcesPath + RUN /bin/bash + """.trimIndent() // RUN command shouldn't matter because it will be replaced on container creation + diff --git a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Test.kt b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Test.kt index 402d258c7d..86cd15e232 100644 --- a/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Test.kt +++ b/diktat-rules/src/test/resources/test/smoke/src/main/kotlin/Example6Test.kt @@ -18,3 +18,9 @@ val text = x """ +val dockerFileAsText = + """ + FROM $baseImage someTest + COPY resources $resourcesPath + RUN /bin/bash + """.trimIndent() // RUN command shouldn't matter because it will be replaced on container creation diff --git a/diktat-ruleset/pom.xml b/diktat-ruleset/pom.xml index 8371dab3e9..9753f6a91d 100644 --- a/diktat-ruleset/pom.xml +++ b/diktat-ruleset/pom.xml @@ -8,7 +8,7 @@ org.cqfn.diktat diktat-parent - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT diff --git a/diktat-test-framework/pom.xml b/diktat-test-framework/pom.xml index db08a1688c..51912b50a9 100644 --- a/diktat-test-framework/pom.xml +++ b/diktat-test-framework/pom.xml @@ -9,7 +9,7 @@ org.cqfn.diktat diktat-parent - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT @@ -39,8 +39,8 @@ slf4j-log4j12 - io.github.java-diff-utils - java-diff-utils + io.github.petertrr + kotlin-multiplatform-diff-jvm org.junit.jupiter diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt index b9664181bb..57c6f779ff 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/FileComparator.kt @@ -1,8 +1,8 @@ package org.cqfn.diktat.test.framework.processing -import com.github.difflib.DiffUtils -import com.github.difflib.patch.ChangeDelta -import com.github.difflib.text.DiffRowGenerator +import io.github.petertrr.diffutils.diff +import io.github.petertrr.diffutils.patch.ChangeDelta +import io.github.petertrr.diffutils.text.DiffRowGenerator import org.slf4j.LoggerFactory import java.io.File @@ -17,7 +17,7 @@ import java.util.stream.Collectors */ class FileComparator { private val expectedResultFile: File - private val actualResultList: List + private val actualResultList: List private val diffGenerator = DiffRowGenerator.create() .showInlineDiffs(true) .mergeOriginalRevised(true) @@ -26,7 +26,7 @@ class FileComparator { .newTag { start -> if (start) "<" else ">" } .build() - constructor(expectedResultFile: File, actualResultList: List) { + constructor(expectedResultFile: File, actualResultList: List) { this.expectedResultFile = expectedResultFile this.actualResultList = actualResultList } @@ -47,7 +47,7 @@ class FileComparator { if (expect.isEmpty()) { return false } - val patch = DiffUtils.diff(expect, actualResultList) + val patch = diff(expect, actualResultList) if (patch.deltas.isEmpty()) { return true } diff --git a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt index 392808ba7c..ef5c25eaaa 100644 --- a/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt +++ b/diktat-test-framework/src/main/kotlin/org/cqfn/diktat/test/framework/processing/TestCheckWarn.kt @@ -9,7 +9,7 @@ import org.slf4j.LoggerFactory */ class TestCheckWarn : TestCompare() { @Suppress("MISSING_KDOC_CLASS_ELEMENTS") override val log: Logger = LoggerFactory.getLogger(TestCheckWarn::class.java) - private var testConfig: TestConfig? = null + @Suppress("UnusedPrivateMember") private var testConfig: TestConfig? = null /** * Get tests execution result diff --git a/examples/gradle-groovy-dsl/build.gradle b/examples/gradle-groovy-dsl/build.gradle index 21aac07a0b..cee0ef24fd 100644 --- a/examples/gradle-groovy-dsl/build.gradle +++ b/examples/gradle-groovy-dsl/build.gradle @@ -1,5 +1,5 @@ plugins { - id "org.cqfn.diktat.diktat-gradle-plugin" version "0.4.0" + id "org.cqfn.diktat.diktat-gradle-plugin" version "0.4.2" } repositories { diff --git a/examples/gradle-groovy-dsl/diktat-analysis.yml b/examples/gradle-groovy-dsl/diktat-analysis.yml index 674e425011..ad1ed749f5 100644 --- a/examples/gradle-groovy-dsl/diktat-analysis.yml +++ b/examples/gradle-groovy-dsl/diktat-analysis.yml @@ -5,7 +5,8 @@ domainName: your.name.here testDirs: test disabledChapters: "" - kotlinVersion: "1.4.21" + kotlinVersion: 1.4 + srcDirectories: "main" # Checks that the Class/Enum/Interface name does not match Pascal case - name: CLASS_NAME_INCORRECT enabled: true @@ -181,6 +182,9 @@ # Checks that properties with comments are separated by a blank line - name: BLANK_LINE_BETWEEN_PROPERTIES enabled: true +# Checks top level order +- name: TOP_LEVEL_ORDER + enabled: true # Checks that non-empty code blocks with braces follow the K&R style (1TBS or OTBS style) - name: BRACES_BLOCK_STRUCTURE_ERROR enabled: true @@ -227,7 +231,27 @@ # If the number of parameters on one line is more than this threshold, all parameters will be placed on separate lines. maxParametersInOneLine: 2 # 3 by default. - # maxCallsInOneLine: 3 + maxCallsInOneLine: 3 +# Checks trailing comma +- name: TRAILING_COMMA + enabled: true + configuration: + # VALUE_ARGUMENT + valueArgument: false + # VALUE_PARAMETER + valueParameter: false + # REFERENCE_EXPRESSION + indices: false + # WHEN_CONDITION_WITH_EXPRESSION + whenConditions: false + # STRING_TEMPLATE + collectionLiteral: false + # TYPE_PROJECTION + typeArgument: false + # TYPE_PARAMETER + typeParameter: false + # DESTRUCTURING_DECLARATION_ENTRY + destructuringDeclaration: false # Checks that there are not too many consecutive spaces in line - name: TOO_MANY_CONSECUTIVE_SPACES enabled: true @@ -440,4 +464,7 @@ enabled: true # If file contains class, then it can't contain extension functions for the same class - name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT enabled: true \ No newline at end of file diff --git a/examples/gradle-kotlin-dsl/build.gradle.kts b/examples/gradle-kotlin-dsl/build.gradle.kts index cc06a19bdc..84145a14c0 100644 --- a/examples/gradle-kotlin-dsl/build.gradle.kts +++ b/examples/gradle-kotlin-dsl/build.gradle.kts @@ -1,5 +1,5 @@ plugins { - id("org.cqfn.diktat.diktat-gradle-plugin") version "0.4.0" + id("org.cqfn.diktat.diktat-gradle-plugin") version "0.4.2" } repositories { diff --git a/examples/gradle-kotlin-dsl/diktat-analysis.yml b/examples/gradle-kotlin-dsl/diktat-analysis.yml index 674e425011..ad1ed749f5 100644 --- a/examples/gradle-kotlin-dsl/diktat-analysis.yml +++ b/examples/gradle-kotlin-dsl/diktat-analysis.yml @@ -5,7 +5,8 @@ domainName: your.name.here testDirs: test disabledChapters: "" - kotlinVersion: "1.4.21" + kotlinVersion: 1.4 + srcDirectories: "main" # Checks that the Class/Enum/Interface name does not match Pascal case - name: CLASS_NAME_INCORRECT enabled: true @@ -181,6 +182,9 @@ # Checks that properties with comments are separated by a blank line - name: BLANK_LINE_BETWEEN_PROPERTIES enabled: true +# Checks top level order +- name: TOP_LEVEL_ORDER + enabled: true # Checks that non-empty code blocks with braces follow the K&R style (1TBS or OTBS style) - name: BRACES_BLOCK_STRUCTURE_ERROR enabled: true @@ -227,7 +231,27 @@ # If the number of parameters on one line is more than this threshold, all parameters will be placed on separate lines. maxParametersInOneLine: 2 # 3 by default. - # maxCallsInOneLine: 3 + maxCallsInOneLine: 3 +# Checks trailing comma +- name: TRAILING_COMMA + enabled: true + configuration: + # VALUE_ARGUMENT + valueArgument: false + # VALUE_PARAMETER + valueParameter: false + # REFERENCE_EXPRESSION + indices: false + # WHEN_CONDITION_WITH_EXPRESSION + whenConditions: false + # STRING_TEMPLATE + collectionLiteral: false + # TYPE_PROJECTION + typeArgument: false + # TYPE_PARAMETER + typeParameter: false + # DESTRUCTURING_DECLARATION_ENTRY + destructuringDeclaration: false # Checks that there are not too many consecutive spaces in line - name: TOO_MANY_CONSECUTIVE_SPACES enabled: true @@ -440,4 +464,7 @@ enabled: true # If file contains class, then it can't contain extension functions for the same class - name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT enabled: true \ No newline at end of file diff --git a/examples/maven/diktat-analysis.yml b/examples/maven/diktat-analysis.yml index 674e425011..ad1ed749f5 100644 --- a/examples/maven/diktat-analysis.yml +++ b/examples/maven/diktat-analysis.yml @@ -5,7 +5,8 @@ domainName: your.name.here testDirs: test disabledChapters: "" - kotlinVersion: "1.4.21" + kotlinVersion: 1.4 + srcDirectories: "main" # Checks that the Class/Enum/Interface name does not match Pascal case - name: CLASS_NAME_INCORRECT enabled: true @@ -181,6 +182,9 @@ # Checks that properties with comments are separated by a blank line - name: BLANK_LINE_BETWEEN_PROPERTIES enabled: true +# Checks top level order +- name: TOP_LEVEL_ORDER + enabled: true # Checks that non-empty code blocks with braces follow the K&R style (1TBS or OTBS style) - name: BRACES_BLOCK_STRUCTURE_ERROR enabled: true @@ -227,7 +231,27 @@ # If the number of parameters on one line is more than this threshold, all parameters will be placed on separate lines. maxParametersInOneLine: 2 # 3 by default. - # maxCallsInOneLine: 3 + maxCallsInOneLine: 3 +# Checks trailing comma +- name: TRAILING_COMMA + enabled: true + configuration: + # VALUE_ARGUMENT + valueArgument: false + # VALUE_PARAMETER + valueParameter: false + # REFERENCE_EXPRESSION + indices: false + # WHEN_CONDITION_WITH_EXPRESSION + whenConditions: false + # STRING_TEMPLATE + collectionLiteral: false + # TYPE_PROJECTION + typeArgument: false + # TYPE_PARAMETER + typeParameter: false + # DESTRUCTURING_DECLARATION_ENTRY + destructuringDeclaration: false # Checks that there are not too many consecutive spaces in line - name: TOO_MANY_CONSECUTIVE_SPACES enabled: true @@ -440,4 +464,7 @@ enabled: true # If file contains class, then it can't contain extension functions for the same class - name: EXTENSION_FUNCTION_WITH_CLASS + enabled: true +# Check if kts script contains other functions except run code +- name: RUN_IN_SCRIPT enabled: true \ No newline at end of file diff --git a/examples/maven/pom.xml b/examples/maven/pom.xml index 0d11061f0a..ffaa28dc55 100644 --- a/examples/maven/pom.xml +++ b/examples/maven/pom.xml @@ -8,7 +8,7 @@ 1.0.0-SNAPSHOT - 0.4.0 + 0.4.2 diff --git a/info/available-rules.md b/info/available-rules.md index 87eb9c3938..f730c1b2d6 100644 --- a/info/available-rules.md +++ b/info/available-rules.md @@ -118,4 +118,5 @@ | 6 | 6.2.2 | EXTENSION_FUNCTION_SAME_SIGNATURE | Checks if extension function has the same signature as another extension function and their classes are related | no | no | + | | 6 | 6.4.1 | AVOID_USING_UTILITY_CLASS | Checks if there is class/object that can be replace with extension function | no | no | - | | 6 | 6.4.2 | OBJECT_IS_PREFERRED | Checks: if class is stateless it is preferred to use `object` | yes | no | + | -| 6 | 6.2.3 | EXTENSION_FUNCTION_WITH_CLASS | Check: if file contains class, then it can not have extension functions for the same class | no | no | - | \ No newline at end of file +| 6 | 6.2.3 | EXTENSION_FUNCTION_WITH_CLASS | Check: if file contains class, then it can not have extension functions for the same class | no | no | - | +| 6 | 6.5.1 | RUN_IN_SCRIPT | Checks : if kts script contains other functions except run code | yes | no | - | \ No newline at end of file diff --git a/info/buildSrc/gradle.properties b/info/buildSrc/gradle.properties index 1fc893775d..0ef5b9495e 100644 --- a/info/buildSrc/gradle.properties +++ b/info/buildSrc/gradle.properties @@ -1 +1 @@ -version=0.4.1-SNAPSHOT +version=0.4.3-SNAPSHOT diff --git a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt index b29209c058..4327cb36eb 100644 --- a/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt +++ b/info/buildSrc/src/main/kotlin/org/cqfn/diktat/generation/docs/WarningsTableGenerator.kt @@ -15,7 +15,8 @@ const val DIKTAT_GUIDE: String = "guide/diktat-coding-convention.md#" */ @Suppress("MagicNumber") fun generateRulesMapping() { - val allWarnings = Warnings.values() + // excluding dummy warning + val allWarnings = Warnings.values().filterNot { it == Warnings.DUMMY_TEST_WARNING } as MutableList allWarnings.sortBy { warn -> val numbers = warn.ruleId.split(".") val chapter = numbers[0].toInt() diff --git a/info/gradle/wrapper/gradle-wrapper.jar b/info/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e708b1c023 Binary files /dev/null and b/info/gradle/wrapper/gradle-wrapper.jar differ diff --git a/info/gradle/wrapper/gradle-wrapper.properties b/info/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..442d9132ea --- /dev/null +++ b/info/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/info/gradlew b/info/gradlew new file mode 100644 index 0000000000..4f906e0c81 --- /dev/null +++ b/info/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/info/gradlew.bat b/info/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/info/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/info/guide/diktat-coding-convention.md b/info/guide/diktat-coding-convention.md index 124b14ff2e..d0208c183b 100644 --- a/info/guide/diktat-coding-convention.md +++ b/info/guide/diktat-coding-convention.md @@ -38,6 +38,7 @@ I [Preface](#c0) * [3.1.2 Code blocks in the source file should be separated by one blank line](#r3.1.2) * [3.1.3 Import statements order](#r3.1.3) * [3.1.4 Order of declaration parts of class-like code structures](#r3.1.4) + * [3.1.5 Order of declaration of top-level code structures](#r3.1.5) * [3.2 Braces](#c3.2) * [3.2.1 Using braces in conditional statements and loop blocks](#r3.2.1) * [3.2.2 Opening braces are placed at the end of the line in *non-empty* blocks and block structures](#r3.2.2) @@ -87,8 +88,10 @@ I [Preface](#c0) * [5.2 Function arguments](#c5.2) * [5.2.1 The lambda parameter of the function should be placed at the end of the argument list](#r5.2.1) * [5.2.2 Number of function parameters should be limited to five](#r5.2.2) - * [5.2.3 Use default values for function arguments instead of overloading them](#r5.2.3) - * [5.2.5 Long lambdas should have explicit parameters](#r5.2.4) + * [5.2.3 Use default values for function arguments instead of overloading them](#r5.2.3) + * [5.2.4 Synchronizing code inside asynchronous code](#r5.2.4) + * [5.2.5 Long lambdas should have explicit parameters](#r5.2.5) + * [5.2.6 Avoid using unnecessary, custom label](#r5.2.6) [6. Classes, interfaces, and extension functions](#c6) * [6.1 Classes](#c6.1) @@ -102,10 +105,12 @@ I [Preface](#c0) * [6.1.8 Avoid using custom getters and setters](#r6.1.8) * [6.1.9 Never use the name of a variable in the custom getter or setter (possible_bug)](#r6.1.9) * [6.1.10 No trivial getters and setters are allowed in the code](#r6.1.10) - * [6.1.11 Use 'apply' for grouping object initialization](#r6.1.11) -* [6.2 Classes](#c6.2) + * [6.1.11 Use 'apply' for grouping object initialization](#r6.1.11) + * [6.1.12 Prefer Inline classes when a class has a single property](#r6.1.12) +* [6.2 Extension functions](#c6.2) * [6.2.1 Use extension functions for making logic of classes less coupled](#r6.2.1) - * [6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)](#r6.2.2) + * [6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)](#r6.2.2) + * [6.2.3 Don't use extension functions for the class in the same file](#r6.2.3) * [6.3 Interfaces](#c6.3) * [6.4 Objects](#c6.4) * [6.4.1 Instead of using utility classes/objects, use extensions](#r6.4.1) @@ -803,6 +808,7 @@ d) **Recommendation**: One `.kt` source file should contain only one class decla e) Avoid empty files that do not contain the code or contain only imports/comments/package name +f) Unused imports should be removed #### 3.1.3 Import statements order From top to bottom, the order is the following: @@ -853,6 +859,53 @@ If the classes are meant to be used externally, and are not referenced inside th **Exception:** All variants of a `(private) val` logger should be placed at the beginning of the class (`(private) val log`, `LOG`, `logger`, etc.). +#### 3.1.5 Order of declaration of top-level code structures +Kotlin allows several top-level declaration types: classes, objects, interfaces, properties and functions. +When declaring more than one class or zero classes (e.g. only functions), as per rule [2.2.1](#r2.2.1), you should document the whole file in the header KDoc. +When declaring top-level structures, keep the following order: +1. Top-level constants and properties (following same order as properties inside a class: `const val`,`val`, `lateinit var`, `var`) +2. Interfaces, classes and objects (grouped by their visibility modifiers) +3. Extension functions +4. Other functions + +**Note**: +Extension functions shouldn't have receivers declared in the same file according to [rule 6.2.3](#r6.2.3) + +Valid example: +```kotlin +package org.cqfn.diktat.example + +const val CONSTANT = 42 + +val topLevelProperty = "String constant" + +interface IExample + +class Example : IExample + +private class Internal + +fun Other.asExample(): Example { /* ... */ } + +private fun Other.asInternal(): Internal { /* ... */ } + +fun doStuff() { /* ... */ } +``` + +**Note**: +kotlin scripts (.kts) allow arbitrary code to be placed on the top level. When writing kotlin scripts, you should first declare all properties, classes +and functions. Only then you should execute functions on top level. It is still recommended wrapping logic inside functions and avoid using top-level statements +for function calls or wrapping blocks of code in top-level scope functions like `run`. + +Example: +```kotlin +/* class declarations */ +/* function declarations */ +run { + // call functions here +} +``` + ### 3.2 Braces This section describes the general rules of using braces in your code. @@ -1232,6 +1285,23 @@ fun foo( } ``` +same should be done for function calls/constructor arguments/e.t.c + +Kotlin supports trailing commas in the following cases: + +Enumerations +Value arguments +Class properties and parameters +Function value parameters +Parameters with optional type (including setters) +Indexing suffix +Lambda parameters +when entry +Collection literals (in annotations) +Type arguments +Type parameters +Destructuring declarations + 8) If the supertype list has more than two elements, they should be separated by newlines. **Valid example:** @@ -1839,8 +1909,8 @@ val anotherVal = myVar?.also { // example 4 myVar?.let { - println("null") -} ?: run { println("not null") } + println("not null") +} ?: run { println("null") } ``` **Exceptions:** @@ -2011,6 +2081,32 @@ GlobalScope.async { #### 5.2.5 Long lambdas should have explicit parameters The lambda without parameters shouldn't be too long. If a lambda is too long, it can confuse the user. Lambda without parameters should consist of 10 lines (non-empty and non-comment) in total. + +#### 5.2.6 Avoid using unnecessary, custom label +Expressions with unnecessary, custom labels generally increase complexity and worsen the maintainability of the code. + +**Invalid example**: +```kotlin +run lab@ { + list.forEach { + return@lab + } +} +``` + +**Valid example**: +```kotlin +list.forEachIndexed { index, i -> + return@forEachIndexed +} + +lab@ for(i: Int in q) { + for (j: Int in q) { + println(i) + break@lab + } +} +``` # 6. Classes, interfaces, and extension functions ### 6.1 Classes @@ -2370,6 +2466,23 @@ fun main() { } ``` +### 6.1.12 Prefer Inline classes when a class has a single property +If a class has only one immutable property, then it can be converted to the inline class. + +Sometimes it is necessary for business logic to create a wrapper around some type. However, it introduces runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers don't get any special treatment. + +**Invalid example**: +```kotlin +class Password { + val value: String +} +``` + +**Valid example**: +```kotlin +inline class Password(val value: String) +``` + ### 6.2 Extension functions This section describes the rules of using extension functions in your code. @@ -2402,6 +2515,20 @@ fun printClassName(s: A) { println(s.foo()) } fun main() { printClassName(B()) } ``` +#### 6.2.3 Don't use extension functions for the class in the same file +You should not use extension functions for the class in the same file, where it is defined. + +**Invalid example**: +```kotlin +class SomeClass { + +} + +fun SomeClass.deleteAllSpaces() { + +} +``` + ### 6.3 Interfaces An `Interface` in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. @@ -2448,5 +2575,20 @@ interface I { object O: I { override fun foo() {} +} +``` + +#### 6.5.1 kts files should wrap logic into top-level scope +It is still recommended wrapping logic inside functions and avoid using top-level statements for function calls or wrapping blocks of code +in top-level scope functions like `run`. + +**Valid example**: +``` +run { + // some code +} + +fun foo() { + } ``` \ No newline at end of file diff --git a/info/guide/guide-chapter-3.md b/info/guide/guide-chapter-3.md index fd8d69543a..fa885c77f0 100644 --- a/info/guide/guide-chapter-3.md +++ b/info/guide/guide-chapter-3.md @@ -305,7 +305,7 @@ class A Avoid empty blocks, and ensure braces start on a new line. An empty code block can be closed immediately on the same line and the next line. However, a newline is recommended between opening and closing braces `{}` (see the examples below.) Generally, empty code blocks are prohibited; using them is considered a bad practice (especially for catch block). -They are only appropriate for overridden functions when the base class's functionality is not needed in the class-inheritor. +They are appropriate for overridden functions, when the base class's functionality is not needed in the class-inheritor, for lambdas used as a function and for empty function in implementation of functional interface. ```kotlin override fun foo() { } @@ -318,6 +318,8 @@ fun doNothing() {} fun doNothingElse() { } + +fun foo(bar: () -> Unit = {}) ``` **Invalid examples:** diff --git a/info/guide/guide-chapter-4.md b/info/guide/guide-chapter-4.md index a11465ac05..11378b79a0 100644 --- a/info/guide/guide-chapter-4.md +++ b/info/guide/guide-chapter-4.md @@ -260,8 +260,8 @@ val anotherVal = myVar?.also { // example 4 myVar?.let { - println("null") -} ?: run { println("not null") } + println("not null") +} ?: run { println("null") } ``` **Exceptions:** diff --git a/info/guide/guide-chapter-6.md b/info/guide/guide-chapter-6.md index aaa6d0a36e..397cc506ae 100644 --- a/info/guide/guide-chapter-6.md +++ b/info/guide/guide-chapter-6.md @@ -468,3 +468,18 @@ object O: I { override fun foo() {} } ``` + +#### 6.5.1 kts files should wrap logic into top-level scope +It is still recommended wrapping logic inside functions and avoid using top-level statements for function calls or wrapping blocks of code +in top-level scope functions like `run`. + +**Valid example**: +``` +run { + // some code +} + +fun foo() { + +} +``` diff --git a/pom.xml b/pom.xml index e053dfbbd4..86ef0ee257 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.cqfn.diktat diktat-parent - 0.4.1-SNAPSHOT + 0.4.3-SNAPSHOT pom diktat @@ -42,18 +42,18 @@ 1.8 1.8 UTF-8 - 1.4.30 + 1.4.31 true - 1.0.1 + 1.1.0 0.39.0 5.7.1 - 30.0-jre + 30.1.1-jre 1.7.30 1.4 - 0.4.0 + 0.4.2 1.7.1 - 1.15.0 - 1.4.20 + 1.16.0 + 1.4.30 0.8.6 @@ -127,9 +127,9 @@ ${slf4j.version} - io.github.java-diff-utils - java-diff-utils - 4.8 + io.github.petertrr + kotlin-multiplatform-diff-jvm + 0.1.0 org.junit.jupiter @@ -229,6 +229,9 @@ src/main/kotlin src/main/resources + + -Xuse-ir + true diff --git a/wp/sections/appendix.tex b/wp/sections/appendix.tex index 70a6b17afb..8bf08f22f9 100644 --- a/wp/sections/appendix.tex +++ b/wp/sections/appendix.tex @@ -209,6 +209,8 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:3.1.4]{ 3.1.4 Order of declaration parts of class-like code structures} +\hspace{1.0cm}\hyperref[sec:3.1.5]{ 3.1.5 Order of declaration of top-level code structures} + \hspace{0.5cm}\hyperref[sec:3.2]{ 3.2 Braces} \hspace{1.0cm}\hyperref[sec:3.2.1]{ 3.2.1 Using braces in conditional statements and loop blocks} @@ -309,8 +311,12 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:5.2.3]{ 5.2.3 Use default values for function arguments instead of overloading them} +\hspace{1.0cm}\hyperref[sec:5.2.4]{ 5.2.4 Synchronizing code inside asynchronous code} + \hspace{1.0cm}\hyperref[sec:5.2.5]{ 5.2.5 Long lambdas should have explicit parameters} +\hspace{1.0cm}\hyperref[sec:5.2.6]{ 5.2.6 Avoid using unnecessary, custom label} + \hspace{0.0cm}\hyperref[sec:]{} \hspace{0.0cm}\hyperref[sec:6.]{6. Classes, interfaces, and extension functions} @@ -339,12 +345,16 @@ \section*{Table of contents} \hspace{1.0cm}\hyperref[sec:6.1.11]{ 6.1.11 Use 'apply' for grouping object initialization} -\hspace{0.5cm}\hyperref[sec:6.2]{ 6.2 Classes} +\hspace{1.0cm}\hyperref[sec:6.1.12]{ 6.1.12 Prefer Inline classes when a class has a single property} + +\hspace{0.5cm}\hyperref[sec:6.2]{ 6.2 Extension functions} \hspace{1.0cm}\hyperref[sec:6.2.1]{ 6.2.1 Use extension functions for making logic of classes less coupled} \hspace{1.0cm}\hyperref[sec:6.2.2]{ 6.2.2 No extension functions with the same name and signature if they extend base and inheritor classes (possible_bug)} +\hspace{1.0cm}\hyperref[sec:6.2.3]{ 6.2.3 Don't use extension functions for the class in the same file} + \hspace{0.5cm}\hyperref[sec:6.3]{ 6.3 Interfaces} \hspace{0.5cm}\hyperref[sec:6.4]{ 6.4 Objects} @@ -542,7 +552,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \begin{center} -\begin{tabular}{ |p{5,0cm}|p{5,0cm}|p{5,0cm}| } +\begin{tabular}{ |p{5.0cm}|p{5.0cm}|p{5.0cm}| } \hline @@ -588,7 +598,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \begin{center} -\begin{tabular}{ |p{5,0cm}|p{5,0cm}|p{5,0cm}| } +\begin{tabular}{ |p{5.0cm}|p{5.0cm}|p{5.0cm}| } \hline @@ -632,7 +642,7 @@ \subsubsection*{\textbf{1.1.1 Identifiers naming conventions}} \begin{center} -\begin{tabular}{ |p{7,5cm}|p{7,5cm}| } +\begin{tabular}{ |p{7.5cm}|p{7.5cm}| } \hline @@ -1583,6 +1593,8 @@ \subsubsection*{\textbf{3.1.2 Code blocks in the source file should be separated +f) Unused imports should be removed + \subsubsection*{\textbf{3.1.3 Import statements order}} \leavevmode\newline @@ -1672,6 +1684,77 @@ \subsubsection*{\textbf{3.1.4 Order of declaration parts of class-like code stru +\subsubsection*{\textbf{3.1.5 Order of declaration of top-level code structures}} +\leavevmode\newline + +\label{sec:3.1.5} + +Kotlin allows several top-level declaration types: classes, objects, interfaces, properties and functions. + +When declaring more than one class or zero classes (e.g. only functions), as per rule [2.2.1], you should document the whole file in the header KDoc. + +When declaring top-level structures, keep the following order: + +1. Top-level constants and properties (following same order as properties inside a class: \textbf{const val},\textbf{val}, \textbf{lateinit var}, \textbf{var}) + +2. Interfaces, classes and objects (grouped by their visibility modifiers) + +3. Extension functions + +4. Other functions + + + +\textbf{Note}: + +Extension functions shouldn't have receivers declared in the same file according to [rule 6.2.3] + + + +Valid example: + +\begin{lstlisting}[language=Kotlin] +package org.cqfn.diktat.example + +const val CONSTANT = 42 + +val topLevelProperty = "String constant" + +interface IExample + +class Example : IExample + +private class Internal + +fun Other.asExample(): Example { /* ... */ } + +private fun Other.asInternal(): Internal { /* ... */ } + +fun doStuff() { /* ... */ } +\end{lstlisting} + + +\textbf{Note}: + +kotlin scripts (.kts) allow arbitrary code to be placed on the top level. When writing kotlin scripts, you should first declare all properties, classes + +and functions. Only then you should execute functions on top level. It is still recommended wrapping logic inside functions and avoid using top-level statements + +for function calls or wrapping blocks of code in top-level scope functions like \textbf{run}. + + + +Example: + +\begin{lstlisting}[language=Kotlin] +/* class declarations */ +/* function declarations */ +run { + // call functions here +} +\end{lstlisting} + + \subsection*{\textbf{3.2 Braces}} \label{sec:3.2} @@ -2243,6 +2326,40 @@ \subsubsection*{\textbf{3.6.2 Rules for line-breaking}} \end{lstlisting} +same should be done for function calls/constructor arguments/e.t.c + + + +Kotlin supports trailing commas in the following cases: + + + +Enumerations + +Value arguments + +Class properties and parameters + +Function value parameters + +Parameters with optional type (including setters) + +Indexing suffix + +Lambda parameters + +when entry + +Collection literals (in annotations) + +Type arguments + +Type parameters + +Destructuring declarations + + + 8) If the supertype list has more than two elements, they should be separated by newlines. @@ -3211,8 +3328,8 @@ \subsubsection*{\textbf{4.3.3 Null-safety}} // example 4 myVar?.let { - println("null") -} ?: run { println("not null") } + println("not null") +} ?: run { println("null") } \end{lstlisting} @@ -3513,6 +3630,42 @@ \subsubsection*{\textbf{5.2.5 Long lambdas should have explicit parameters}} If a lambda is too long, it can confuse the user. Lambda without parameters should consist of 10 lines (non-empty and non-comment) in total. + + +\subsubsection*{\textbf{5.2.6 Avoid using unnecessary}} +\leavevmode\newline + +\label{sec:5.2.6} + +Expressions with unnecessary, custom labels generally increase complexity and worsen the maintainability of the code. + + + +\textbf{Invalid example}: + +\begin{lstlisting}[language=Kotlin] +run lab@ { + list.forEach { + return@lab + } +} +\end{lstlisting} + + +\textbf{Valid example}: + +\begin{lstlisting}[language=Kotlin] +list.forEachIndexed { index, i -> + return@forEachIndexed +} + +lab@ for(i: Int in q) { + for (j: Int in q) { + println(i) + break@lab + } +} +\end{lstlisting} \section*{\textbf{6. Classes}} \label{sec:6.} @@ -4025,6 +4178,35 @@ \subsubsection*{\textbf{6.1.11 Use 'apply' for grouping object initialization}} \end{lstlisting} +\subsubsection*{\textbf{6.1.12 Prefer Inline classes when a class has a single property}} +\leavevmode\newline + +\label{sec:6.1.12} + +If a class has only one immutable property, then it can be converted to the inline class. + + + +Sometimes it is necessary for business logic to create a wrapper around some type. However, it introduces runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers don't get any special treatment. + + + +\textbf{Invalid example}: + +\begin{lstlisting}[language=Kotlin] +class Password { + val value: String +} +\end{lstlisting} + + +\textbf{Valid example}: + +\begin{lstlisting}[language=Kotlin] +inline class Password(val value: String) +\end{lstlisting} + + \subsection*{\textbf{6.2 Extension functions}} \label{sec:6.2} @@ -4081,6 +4263,28 @@ \subsubsection*{\textbf{6.2.2 No extension functions with the same name and sign \end{lstlisting} +\subsubsection*{\textbf{6.2.3 Don't use extension functions for the class in the same file}} +\leavevmode\newline + +\label{sec:6.2.3} + +You should not use extension functions for the class in the same file, where it is defined. + + + +\textbf{Invalid example}: + +\begin{lstlisting}[language=Kotlin] +class SomeClass { + +} + +fun SomeClass.deleteAllSpaces() { + +} +\end{lstlisting} + + \subsection*{\textbf{6.3 Interfaces}} \label{sec:6.3} @@ -4157,5 +4361,29 @@ \subsubsection*{\textbf{6.4.2 Objects should be used for Stateless Interfaces}} object O: I { override fun foo() {} +} +\end{lstlisting} + + +\subsubsection*{\textbf{6.5.1 kts files should wrap logic into top-level scope}} +\leavevmode\newline + +\label{sec:6.5.1} + +It is still recommended wrapping logic inside functions and avoid using top-level statements for function calls or wrapping blocks of code + +in top-level scope functions like \textbf{run}. + + + +\textbf{Valid example}: + +\begin{lstlisting}[language=Kotlin] +run { + // some code +} + +fun foo() { + } \end{lstlisting} \ No newline at end of file