All Projects → Serchinastico → Lin

Serchinastico / Lin

Licence: apache-2.0
Lin is an Android Lint tool made simple

Programming Languages

java
68154 projects - #9 most used programming language
kotlin
9241 projects
dsl
153 projects

Projects that are alternatives of or similar to Lin

Cflint
Static code analysis for CFML (a linter)
Stars: ✭ 156 (-33.62%)
Mutual labels:  lint
Woke
✊ Detect non-inclusive language in your source code.
Stars: ✭ 190 (-19.15%)
Mutual labels:  lint
Ember Template Lint
Linter for Ember or Handlebars templates
Stars: ✭ 214 (-8.94%)
Mutual labels:  lint
Textlint
The pluggable natural language linter for text and markdown.
Stars: ✭ 2,158 (+818.3%)
Mutual labels:  lint
Ue4 Style Guide
An attempt to make Unreal Engine 4 projects more consistent
Stars: ✭ 2,656 (+1030.21%)
Mutual labels:  lint
Add And Commit
Add & commit files from a path directly from GitHub Actions
Stars: ✭ 198 (-15.74%)
Mutual labels:  lint
Misspell Fixer
Simple tool for fixing common misspellings, typos in source code
Stars: ✭ 154 (-34.47%)
Mutual labels:  lint
D Scanner
Swiss-army knife for D source code
Stars: ✭ 221 (-5.96%)
Mutual labels:  lint
Parcel Plugin Typescript
🚨 Enhanced TypeScript support for Parcel
Stars: ✭ 176 (-25.11%)
Mutual labels:  lint
Sql Language Server
SQL Language Server
Stars: ✭ 210 (-10.64%)
Mutual labels:  lint
Stylelint Config Primer
Sharable stylelint config used by GitHub's CSS
Stars: ✭ 165 (-29.79%)
Mutual labels:  lint
Openapi Cli
⚒️ OpenAPI 3 CLI toolbox with rich validation and bundling features.
Stars: ✭ 169 (-28.09%)
Mutual labels:  lint
Grunt Recess
[DEPRECATED] Lint and minify CSS and LESS
Stars: ✭ 205 (-12.77%)
Mutual labels:  lint
Clippy Check
📎 GitHub Action for PR annotations with clippy warnings
Stars: ✭ 159 (-32.34%)
Mutual labels:  lint
Markdownlint
A Node.js style checker and lint tool for Markdown/CommonMark files.
Stars: ✭ 2,828 (+1103.4%)
Mutual labels:  lint
Ember Cli Template Lint
Ember CLI integration for ember-template-lint
Stars: ✭ 156 (-33.62%)
Mutual labels:  lint
Nbqa
Run any standard Python code quality tool on a Jupyter Notebook
Stars: ✭ 193 (-17.87%)
Mutual labels:  lint
Fsharplint
Lint tool for F#
Stars: ✭ 224 (-4.68%)
Mutual labels:  lint
Protoc Gen Lint
A plug-in for Google's Protocol Buffers (protobufs) compiler to lint .proto files for style violations.
Stars: ✭ 221 (-5.96%)
Mutual labels:  lint
Whispers
Identify hardcoded secrets and dangerous behaviours
Stars: ✭ 66 (-71.91%)
Mutual labels:  lint


Build Status codecov jitpack Lint tool: Lin

Lin is an Android Lint tool made simpler. It has two different goals:

  1. To create a set of highly opinionated detectors to apply to your Android projects.
  2. To offer a Kotlin DSL to write your own detectors in a much easier way.

How to use

Add the JitPack repository to your build file:

allprojects {
    repositories {
        ...
        maven { url 'https://jitpack.io' }
    }
}

Lin - Detectors

Add the detectors module dependencies to your project and the dsl module as part of the lint classpath:

dependencies {
    lintChecks 'com.github.serchinastico.lin:detectors:0.0.6'
}

Lin - DSL (Domain Specific Language)

If you want to write your own detectors with Lin just add the dsl, annotations and processor modules to your linting project:

dependencies {
    compileOnly 'com.github.serchinastico.lin:dsl:0.0.6'
    compileOnly 'com.github.serchinastico.lin:annotations:0.0.6'
    kapt 'com.github.serchinastico.lin:processor:0.0.6'
}

You will also need to export classes defined in the dsl dependency into your linting module. To do so, pack the dsl files inside the jar along with the definition of your Lint-Registry-v2 file:

jar {
  manifest {
    attributes("Lint-Registry-v2": "com.your.project.IssueRegistry")
  }

  from {
    configurations.compileOnly.filter {
      it.absolutePath.contains("com.github.serchinastico.lin/dsl")
    }.collect {
      it.isDirectory() ? it : zipTree(it)
    }
  }
}

How to write your own detectors

Lin offers a DSL (Domain Specific Language) to write your own detectors easily. The API is focused on representing your rules as concisely as possible. Let's bisect an example of a detector to understand how it works:

@Detector
fun noElseInSwitchWithEnumOrSealed() = detector(
    // Define the issue:
    issue(
        // 1. What files should the detector check
        Scope.JAVA_FILE_SCOPE,
        // 2. A brief description of the issue
        "There should not be else/default branches on a switch statement checking for enum/sealed class values",
        // 3. A more in-detail explanation of why are we detecting the issue
        "Adding an else/default branch breaks extensibility because it won't let you know if there is a missing " +
                "implementation when adding new types to the enum/sealed class",
        // The category this issue falls into
        Category.CORRECTNESS
    )
) {
    /* The rule definition using the DSL. Define the
     * AST node you want to look for and include a
     * suchThat definition returning true when you want 
     * your rule to report an issue.
     * The best way to see what nodes you have
     * available is by using your IDE autocomplete
     * function.
    */
    switch {
        suchThat { node ->
            val classReferenceType = node.expression?.getExpressionType() ?: (return@suchThat false)

            if (!classReferenceType.isEnum && !classReferenceType.isSealed) {
                return@suchThat false
            }

            node.clauses.any { clause -> clause.isElseBranch }
        }
    }
}

Quantifiers

You can specify your rules using quantifiers, that is, numeric restrictions to how many times you are expecting a specific rule to appear in order to be reported.

@Detector
fun noMoreThanOneGsonInstance() = detector(
    issue(
        Scope.JAVA_FILE_SCOPE,
        "Gson should only be initialized only once",
        """Creating multiple instances of Gson may hurt performance and it's a common mistake to instantiate it for
            | simple serialization/deserialization. Use a single instance, be it with a classic singleton pattern or
            | other mechanism your dependency injector framework provides. This way you can also share the common
            | type adapters.
        """.trimMargin(),
        Category.PERFORMANCE
    ),
    // We can use anyOf to report if any of the rules
    // included is found.
    anyOf(
        // This rule will only report if more than one
        // file has any call expression matching the 
        // suchThat predicate.
        file(moreThan(1)) { callExpression { suchThat { it.isGsonConstructor } } },
        // On the other hand, this rule will only 
        // report if there is any file with more than
        // one call expression matching the suchThat
        // predicate.
        file { callExpression(moreThan(1)) { suchThat { it.isGsonConstructor } } }
    )
)

The list of available quantifiers is:

val any                  // The default quantifier, if a rule matches any number of times then it's reported
val all                  // It should appear in every single appearance of the node
fun times(times: Int)    // Match the rule an exact number of "times"
fun atMost(times: Int)   // matches <= "times"
fun atLeast(times: Int)  // matches >= "times"
fun lessThan(times: Int) // matches < "times"
fun moreThan(times: Int) // matches > "times"
val none                 // No matches

Storage

Lin detectors can store and retrieve information from a provided map. This is really useful if you have dependant rules where one of them might depend on the value of another, e.g. an activity class name having the same name as the layout it renders.

Because Lin uses backtracking on the process of finding the best match for rules it's highly discouraged to store information by yourself, intead you should use the storage property provided in the suchThat block.

{
    import {
        suchThat { node ->
            val importedString = node.importReference?.asRenderString() ?: return@suchThat false
            val importedLayout = KOTLINX_SYNTHETIC_VIEW_IMPORT
                .matchEntire(importedString)
                ?.groups
                ?.get(1)
                ?.value ?: return@suchThat false
            // Here we have access to the LinContext object that holds
            // a reference to a map of values where you can store
            // string values.
            params["Imported Layout"] = importedLayout
            it.isSyntheticViewImport
        }
    }

    expression {
        suchThat { node ->
            // We retrieve the information we stored previously
            // It's the same value we stored when the rule returned
            // true so we are sure it's the one we need.
            val importedLayout = params["Imported Layout"] ?: return@suchThat false
            val usedLayout = LAYOUT_EXPRESSION.matchEntire(node.asRenderString())
                ?.groups
                ?.get(1)
                ?.value ?: return@suchThat false
            return usedLayout != importedLayout
        }
    }
}

The storage property is just a MutableMap<String, String>. The matching algorithm takes care of keeping the map in a coherent state while doing the search so that you won't find values stored in failing rules. All siblings and child nodes will see stored values.

It's also important to keep in mind that Lin will try to match rules in any order. The most important implication is that even if you define a rule in a specific order Lin might find matches in the opposite:

{
    expression {
        suchThat {
            storage["node"] = it.asRenderString() 
            true
        }
    }

    expression {
        suchThat { "MyExpression" == storage["node"] }
    }
}

Even if the expression storing things in the storage is defined before, that order is not honored when looking for the best match of rules, so it might happen that storage["node"] is null.

Lin - Testing

Internally, Lin uses a DSL for tests that makes a bit easier the simplest scenarios. You can use it by adding the dependency to your project:

dependencies {
    testImplementation 'com.github.serchinastico.lin:test:0.0.6'
    // You might still need to load the official Android Lint dependencies for tests
    testImplementation 'com.android.tools.lint:lint:26.3.0'
    testImplementation 'com.android.tools.lint:lint-tests:26.3.0'
    testImplementation 'com.android.tools:testutils:26.3.0'
}

Creating a test with the test module is pretty easy, just look at an example:

class SomeDetectorTest : LintTest {
    // Specify the issue we are covering, in this case an issue created with Lin
    override val issue = SomeDetector.issue

    @Test
    fun inJavaClass_whenSomethingHappens_detectsNoErrors() {
        // `expect` can load multiple files to the test project
        expect(
            someSharedFile,
            """
                |package foo;
                |
                |import java.util.Date;
                |
                |class TestClass {
                |   public void main(String[] args) {}
                |}
            """.inJava    // Specify the language in which the file is written e.g. `inJava` or `inKotlin`
        ) toHave NoErrors /* Three possible values here:
                           *   > `NoErrors`               No expected reports
                           *   > `SomeWarning(fileName)`  Expect at least one warning in the specified file
                           *   > `SomeError(fileName)`    Expect at least one error in the specified file
                           */
    }
}

Lin tests are used with this very same DSL so you can take a look to the detectors module tests to see many more examples.

Badge

Show the world you're using Lin.

Lint tool: Lin

[![Lint tool: Lin](https://img.shields.io/badge/Lint_tool-lin-2e99e9.svg?style=flat)](https://github.com/Serchinastico/Lin)
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].