{"id":22066164,"url":"https://github.com/serchinastico/lin","last_synced_at":"2025-05-13T01:55:16.272Z","repository":{"id":71604447,"uuid":"158760415","full_name":"Serchinastico/Lin","owner":"Serchinastico","description":"Lin is an Android Lint tool made simple","archived":false,"fork":false,"pushed_at":"2019-03-22T19:49:40.000Z","size":330,"stargazers_count":233,"open_issues_count":10,"forks_count":7,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-05-13T01:55:07.365Z","etag":null,"topics":["android","dsl","java","kotlin","lint"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Serchinastico.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-22T23:56:01.000Z","updated_at":"2024-07-11T08:25:33.000Z","dependencies_parsed_at":"2023-02-23T18:16:09.347Z","dependency_job_id":null,"html_url":"https://github.com/Serchinastico/Lin","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Serchinastico%2FLin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Serchinastico%2FLin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Serchinastico%2FLin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Serchinastico%2FLin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Serchinastico","download_url":"https://codeload.github.com/Serchinastico/Lin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253856640,"owners_count":21974577,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","dsl","java","kotlin","lint"],"created_at":"2024-11-30T19:26:19.823Z","updated_at":"2025-05-13T01:55:16.265Z","avatar_url":"https://github.com/Serchinastico.png","language":"Kotlin","readme":"\u003cp align=\"center\"\u003e\u003cimg src =\"./readme/logo.png\" /\u003e\u003c/p\u003e\n\n---------------\n\n[![Build Status](https://travis-ci.org/Serchinastico/Lin.svg?branch=master)](https://travis-ci.org/Serchinastico/Lin)\n[![codecov](https://codecov.io/gh/Serchinastico/Lin/branch/master/graph/badge.svg)](https://codecov.io/gh/Serchinastico/Lin)\n[![jitpack](https://jitpack.io/v/Serchinastico/Lin.svg)](https://jitpack.io/#Serchinastico/Lin)\n[![Lint tool: Lin](https://img.shields.io/badge/Lint_tool-lin-2e99e9.svg?style=flat)](https://github.com/Serchinastico/Lin)\n\nLin is an Android Lint tool made simpler. It has two different goals:\n\n1. To create a set of highly opinionated detectors to apply to your Android projects.\n2. To offer a Kotlin DSL to write your own detectors in a much easier way.\n\n## How to use\n\nAdd the JitPack repository to your build file:\n\n```groovy\nallprojects {\n    repositories {\n        ...\n        maven { url 'https://jitpack.io' }\n    }\n}\n```\n\n### Lin - Detectors\n\nAdd the `detectors` module dependencies to your project and the `dsl` module as part of the lint classpath:\n\n```groovy\ndependencies {\n    lintChecks 'com.github.serchinastico.lin:detectors:0.0.6'\n}\n```\n\n### Lin - DSL (Domain Specific Language)\n\nIf you want to write your own detectors with Lin just add the `dsl`, `annotations` and `processor` modules to your linting project:\n\n```groovy\ndependencies {\n    compileOnly 'com.github.serchinastico.lin:dsl:0.0.6'\n    compileOnly 'com.github.serchinastico.lin:annotations:0.0.6'\n    kapt 'com.github.serchinastico.lin:processor:0.0.6'\n}\n```\n\nYou 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:\n\n```groovy\njar {\n  manifest {\n    attributes(\"Lint-Registry-v2\": \"com.your.project.IssueRegistry\")\n  }\n\n  from {\n    configurations.compileOnly.filter {\n      it.absolutePath.contains(\"com.github.serchinastico.lin/dsl\")\n    }.collect {\n      it.isDirectory() ? it : zipTree(it)\n    }\n  }\n}\n```\n\n## How to write your own detectors\n\nLin 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:\n\n```kotlin\n@Detector\nfun noElseInSwitchWithEnumOrSealed() = detector(\n    // Define the issue:\n    issue(\n        // 1. What files should the detector check\n        Scope.JAVA_FILE_SCOPE,\n        // 2. A brief description of the issue\n        \"There should not be else/default branches on a switch statement checking for enum/sealed class values\",\n        // 3. A more in-detail explanation of why are we detecting the issue\n        \"Adding an else/default branch breaks extensibility because it won't let you know if there is a missing \" +\n                \"implementation when adding new types to the enum/sealed class\",\n        // The category this issue falls into\n        Category.CORRECTNESS\n    )\n) {\n    /* The rule definition using the DSL. Define the\n     * AST node you want to look for and include a\n     * suchThat definition returning true when you want \n     * your rule to report an issue.\n     * The best way to see what nodes you have\n     * available is by using your IDE autocomplete\n     * function.\n    */\n    switch {\n        suchThat { node -\u003e\n            val classReferenceType = node.expression?.getExpressionType() ?: (return@suchThat false)\n\n            if (!classReferenceType.isEnum \u0026\u0026 !classReferenceType.isSealed) {\n                return@suchThat false\n            }\n\n            node.clauses.any { clause -\u003e clause.isElseBranch }\n        }\n    }\n}\n```\n\n### Quantifiers\n\nYou 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.\n\n```kotlin\n@Detector\nfun noMoreThanOneGsonInstance() = detector(\n    issue(\n        Scope.JAVA_FILE_SCOPE,\n        \"Gson should only be initialized only once\",\n        \"\"\"Creating multiple instances of Gson may hurt performance and it's a common mistake to instantiate it for\n            | simple serialization/deserialization. Use a single instance, be it with a classic singleton pattern or\n            | other mechanism your dependency injector framework provides. This way you can also share the common\n            | type adapters.\n        \"\"\".trimMargin(),\n        Category.PERFORMANCE\n    ),\n    // We can use anyOf to report if any of the rules\n    // included is found.\n    anyOf(\n        // This rule will only report if more than one\n        // file has any call expression matching the \n        // suchThat predicate.\n        file(moreThan(1)) { callExpression { suchThat { it.isGsonConstructor } } },\n        // On the other hand, this rule will only \n        // report if there is any file with more than\n        // one call expression matching the suchThat\n        // predicate.\n        file { callExpression(moreThan(1)) { suchThat { it.isGsonConstructor } } }\n    )\n)\n```\n\nThe list of available quantifiers is:\n\n```kotlin\nval any                  // The default quantifier, if a rule matches any number of times then it's reported\nval all                  // It should appear in every single appearance of the node\nfun times(times: Int)    // Match the rule an exact number of \"times\"\nfun atMost(times: Int)   // matches \u003c= \"times\"\nfun atLeast(times: Int)  // matches \u003e= \"times\"\nfun lessThan(times: Int) // matches \u003c \"times\"\nfun moreThan(times: Int) // matches \u003e \"times\"\nval none                 // No matches\n```\n\n### Storage\n\nLin 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.\n\nBecause 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.\n\n```kotlin\n{\n    import {\n        suchThat { node -\u003e\n            val importedString = node.importReference?.asRenderString() ?: return@suchThat false\n            val importedLayout = KOTLINX_SYNTHETIC_VIEW_IMPORT\n                .matchEntire(importedString)\n                ?.groups\n                ?.get(1)\n                ?.value ?: return@suchThat false\n            // Here we have access to the LinContext object that holds\n            // a reference to a map of values where you can store\n            // string values.\n            params[\"Imported Layout\"] = importedLayout\n            it.isSyntheticViewImport\n        }\n    }\n\n    expression {\n        suchThat { node -\u003e\n            // We retrieve the information we stored previously\n            // It's the same value we stored when the rule returned\n            // true so we are sure it's the one we need.\n            val importedLayout = params[\"Imported Layout\"] ?: return@suchThat false\n            val usedLayout = LAYOUT_EXPRESSION.matchEntire(node.asRenderString())\n                ?.groups\n                ?.get(1)\n                ?.value ?: return@suchThat false\n            return usedLayout != importedLayout\n        }\n    }\n}\n```\n\nThe `storage` property is just a `MutableMap\u003cString, String\u003e`. 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.**\n\nIt'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:\n\n```kotlin\n{\n    expression {\n        suchThat {\n            storage[\"node\"] = it.asRenderString() \n            true\n        }\n    }\n\n    expression {\n        suchThat { \"MyExpression\" == storage[\"node\"] }\n    }\n}\n```\n\nEven 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.\n\n### Lin - Testing\n\nInternally, 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:\n\n```groovy\ndependencies {\n    testImplementation 'com.github.serchinastico.lin:test:0.0.6'\n    // You might still need to load the official Android Lint dependencies for tests\n    testImplementation 'com.android.tools.lint:lint:26.3.0'\n    testImplementation 'com.android.tools.lint:lint-tests:26.3.0'\n    testImplementation 'com.android.tools:testutils:26.3.0'\n}\n```\n\nCreating a test with the `test` module is pretty easy, just look at an example:\n\n```kotlin\nclass SomeDetectorTest : LintTest {\n    // Specify the issue we are covering, in this case an issue created with Lin\n    override val issue = SomeDetector.issue\n\n    @Test\n    fun inJavaClass_whenSomethingHappens_detectsNoErrors() {\n        // `expect` can load multiple files to the test project\n        expect(\n            someSharedFile,\n            \"\"\"\n                |package foo;\n                |\n                |import java.util.Date;\n                |\n                |class TestClass {\n                |   public void main(String[] args) {}\n                |}\n            \"\"\".inJava    // Specify the language in which the file is written e.g. `inJava` or `inKotlin`\n        ) toHave NoErrors /* Three possible values here:\n                           *   \u003e `NoErrors`               No expected reports\n                           *   \u003e `SomeWarning(fileName)`  Expect at least one warning in the specified file\n                           *   \u003e `SomeError(fileName)`    Expect at least one error in the specified file\n                           */\n    }\n}\n```\n\nLin tests are used with this very same DSL so you can take a look to the `detectors` module tests to see many more examples.\n\n### Badge\n\nShow the world you're using Lin.\n\n[![Lint tool: Lin](https://img.shields.io/badge/Lint_tool-lin-2e99e9.svg?style=flat)](https://github.com/Serchinastico/Lin)\n\n```md\n[![Lint tool: Lin](https://img.shields.io/badge/Lint_tool-lin-2e99e9.svg?style=flat)](https://github.com/Serchinastico/Lin)\n```\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserchinastico%2Flin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fserchinastico%2Flin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fserchinastico%2Flin/lists"}