├── .gitignore ├── .idea ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── ls │ │ └── lint │ │ └── MainActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ └── activity_main.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── buildSrc ├── .gitignore ├── build.gradle ├── libs │ └── lint.jar ├── proguard-rules.pro └── src │ └── main │ ├── groovy │ └── com │ │ └── ls │ │ └── lintplugin │ │ ├── LintPlugin.groovy │ │ ├── LintToolClient.groovy │ │ └── LintTxtReporter.groovy │ └── resources │ └── META-INF │ └── gradle-plugins │ └── lintplugin.properties ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lint-check-result-all.xml ├── lint-check-result.txt ├── lintaar ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── res │ └── values │ └── strings.xml ├── lintlib ├── .gitignore ├── build.gradle └── src │ └── main │ └── java │ └── com │ └── ls │ └── lintlib │ ├── LintIssueRegistry.java │ └── detector │ ├── ClassCommentDetector.java │ ├── MessageDetector.java │ └── SimpleDetector.java ├── post-commit └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AwesomeLint 2 | ## 使用说明 3 | 4 | AwesomeLint可以分为Lint代码静态检查以及Lint代码增量检查。其中Lint代码静态检查以aar的方式引入到项目中。Lint代码增量检查工具以插件的方式引入到项目中。 5 | 6 | 注意:目前aar以及插件都是在本地实现的,引用是按照本地引用方法即可。 7 | 8 | ### 代码静态检查功能的使用 9 | 10 | Lint自定义规则是封装在aar中的,直接添加依赖即可 11 | 12 | > 如果Lint规则没有生效,可以尝试重启Android Studio 13 | 14 | ### git增量检查的使用 15 | 16 | 在项目中引用插件 apply plugin: 'lintplugin' 17 | 18 | LintPlugin的配置,与apply plugin 都可以发在顶级build.gradle文件中 19 | 20 | ```gradle 21 | lintConfig { 22 | //Lint检查文件的类型,默认是.java和.xml。可以自定义其他类型的文件 23 | lintCheckFileType = ".java,.xml" 24 | //默认是false。为true的时候会扫描git commit时候所有的代码并且输出扫描 25 | lintReportAll = false 26 | } 27 | ``` 28 | 29 | > 每次git commit都会通过git hooks触发Lint检查。检查结果会以TXT格式输出到项目根目录下,如果有问题,则会触发 git reset命令回滚提交。 30 | 31 | ### 环境准备 32 | 33 | 使用git提交增量检查时需要配置ANDROID_HOME环境变量(需要以ANDROID_HOME命名并加入到path中,因为在Lint框架中执行Lint检查时需要获取Android环境变量) 34 | 35 | ```txt 36 | Windows环境:在电脑->属性->环境变量中编辑即可 37 | ``` 38 | 39 | ```sh 40 | Linux环境:编辑 ~/.bashrc即可 41 | vi ~./bashrc 42 | export ANDROID_HOME=$HOME/{Android SDK 路径} 43 | export PATH=$PATH:$ANDROID_HOME/tools 44 | ``` 45 | 46 | ### 关于git hooks脚本 47 | 48 | Windows系统与Linux系统对应不同的git hooks脚本,触发git增量检查功能需要将git hooks脚本 **修改名称(修改为post-commit)** 并且复制到项目根目录的.git/hooks(此目录是隐藏目录)目录下。 49 | 50 | Windows系统下的git hooks脚本 51 | 52 | 头部路径需要修改:#!C:/Program\ Files/Git/bin/sh.exe 改为 #!{git安装路径}/bin/sh.exe 53 | 54 | ```shell 55 | #!C:/Program\ Files/Git/bin/sh.exe 56 | ./gradlew lintCheck 57 | exit 0 58 | ``` 59 | 60 | 61 | 62 | Linux系统下git hooks脚本 63 | 64 | ```shell 65 | #!/bin/sh 66 | ./gradlew lintCheck 67 | exit 0 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.ls.lint" 7 | minSdkVersion 15 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:28.0.0' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | 29 | implementation project(":lintaar") 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ls/lint/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.ls.lint; 2 | 3 | import android.os.Message; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.os.Bundle; 6 | import android.util.Log; 7 | 8 | public class MainActivity extends AppCompatActivity { 9 | 10 | @Override 11 | protected void onCreate(Bundle savedInstanceState) { 12 | super.onCreate(savedInstanceState); 13 | setContentView(R.layout.activity_main); 14 | 15 | Log.d("", "'"); 16 | Log.d("", "'"); 17 | System.out.print("ddd"); 18 | 19 | Message msg = new Message(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | My Application 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.3.2' 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | 29 | apply plugin: "lintplugin" 30 | 31 | lintConfig { 32 | //配置Lint检查文件的类型 33 | lintCheckFileType = ".java,.xml" 34 | //是否将检查文件的所有扫描结果都输出 35 | lintReportAll = true 36 | } -------------------------------------------------------------------------------- /buildSrc/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /buildSrc/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | 6 | implementation 'com.android.tools.lint:lint-api:26.3.2' 7 | implementation 'com.android.tools.lint:lint-checks:26.3.2' 8 | implementation 'com.android.tools.lint:lint:26.3.2' 9 | 10 | /*implementation 'com.android.tools.build:gradle:3.3.2' 11 | implementation 'com.android.tools.build:gradle-api:3.3.2'*/ 12 | //implementation 'com.android.tools.build:gradle-core:3.3.2' 13 | 14 | implementation gradleApi() 15 | implementation localGroovy() 16 | } 17 | 18 | repositories { 19 | google() 20 | jcenter() 21 | } 22 | -------------------------------------------------------------------------------- /buildSrc/libs/lint.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/buildSrc/libs/lint.jar -------------------------------------------------------------------------------- /buildSrc/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/com/ls/lintplugin/LintPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.ls.lintplugin 2 | 3 | import com.android.tools.lint.XmlReporter 4 | import com.ls.lintlib.LintIssueRegistry 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | 8 | class LintPlugin implements Plugin { 9 | 10 | def fileNameFix = [".java", ".xml"] as String[] 11 | 12 | @Override 13 | void apply(Project project) { 14 | project.extensions.create("lintConfig", LintConfig.class) 15 | project.task("lintCheck") << { 16 | println("=========== Lint check start ==============") 17 | 18 | String[] filenamePostfix 19 | if (project.lintConfig != null) { 20 | String fileType = project.lintConfig.lintCheckFileType 21 | if (fileType != null) { 22 | filenamePostfix = fileType.split(",") 23 | } 24 | } 25 | if (filenamePostfix == null || filenamePostfix.length <= 0) { 26 | filenamePostfix = fileNameFix 27 | } 28 | 29 | List list = getCommitChange(project) 30 | List files = new ArrayList<>() 31 | File file 32 | List startIndex = new ArrayList<>() 33 | List endIndex = new ArrayList<>() 34 | 35 | for (String s : list) { 36 | println("file path: " + s) 37 | if (isMatchFile(filenamePostfix, s)) { 38 | file = new File(s) 39 | files.add(file) 40 | getFileChangeStatus(s, project, startIndex, endIndex) 41 | } 42 | } 43 | 44 | println("need checked files size:" + files.size()) 45 | println(System.getenv("ANDROID_HOME")) 46 | 47 | def cl = new LintToolClient() 48 | def flag = cl.flags // LintCliFlags 用于设置Lint检查的一些标志 49 | flag.setExitCode = true 50 | /* 51 | * HtmlReport 52 | * 输出HTML格式的报告 53 | * 输出路径:/{$rootDir}/lint-all-result.html 54 | */ 55 | //Reporter reporter = new HtmlReporter(cl, new File("lint-result.html"), flag) 56 | //flag.reporters.add(reporter) 57 | 58 | //是否输出全部的扫描结果 59 | if (project.lintConfig != null && project.lintConfig.lintReportAll) { 60 | File outputResult = new File("lint-check-result-all.xml") 61 | def xmlReporter = new XmlReporter(cl, outputResult) 62 | flag.reporters.add(xmlReporter) 63 | } 64 | 65 | /* 66 | * 输出TXT格式的报告 67 | */ 68 | File lintResult = new File("lint-check-result.txt") 69 | Writer writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(lintResult), "UTF-8")) 70 | def txtReporter = new LintTxtReporter(cl, lintResult, writer, startIndex, endIndex) 71 | flag.reporters.add(txtReporter) 72 | 73 | /* 74 | * 执行run方法开始lint检查 75 | * 76 | * LintIssueRegistry()-> 自定义Lint检查规则 77 | * files->需要检查的文件文件 78 | * result 检查结果 设置flag.setExitCode = true时, 有错误的时候返回1 反之返回0 79 | */ 80 | cl.run(new LintIssueRegistry(), files) 81 | 82 | println("issue number: " + txtReporter.issueNumber) 83 | 84 | //根据报告中存在的问题进行判断是否需要回退 85 | if (txtReporter.issueNumber > 0) { 86 | //回退commit 87 | "git reset HEAD~1".execute(null, project.getRootDir()) 88 | } 89 | 90 | println("============ Lint check end ===============") 91 | } 92 | 93 | /* 94 | * gradle task: 将git hooks 脚本复制到.git/hooks文件夹下 95 | * 根据不同的系统类型复制不同的git hooks脚本(现支持Windows、Linux两种) 96 | */ 97 | project.task("installGitHooks").doLast { 98 | println("OS Type:" + System.getProperty("os.name")) 99 | File postCommit 100 | String OSType = System.getProperty("os.name") 101 | if (OSType.contains("Windows")) { 102 | postCommit = new File(project.rootDir, "post-commit-windows") 103 | } else { 104 | postCommit = new File(project.rootDir, "post-commit") 105 | } 106 | 107 | project.copy { 108 | from (postCommit) { 109 | rename { 110 | String filename -> 111 | "post-commit" 112 | } 113 | } 114 | into new File(project.rootDir, ".git/hooks/") 115 | } 116 | } 117 | } 118 | 119 | /** 120 | * 通过Git命令获取需要检查的文件 121 | * 122 | * @param project gradle.Project 123 | * @return 文件名 124 | */ 125 | static List getCommitChange(Project project) { 126 | ArrayList filterList = new ArrayList<>() 127 | try { 128 | //此命令获取本次提交的文件 在git commit之后执行 129 | String command = "git diff --name-only --diff-filter=ACMRTUXB HEAD~1 HEAD~0" 130 | String changeInfo = command.execute(null, project.getRootDir()).text.trim() 131 | if (changeInfo == null || changeInfo.empty) { 132 | return filterList 133 | } 134 | 135 | String[] lines = changeInfo.split("\\n") 136 | return lines.toList() 137 | } catch (Exception e) { 138 | e.printStackTrace() 139 | return filterList 140 | } 141 | } 142 | 143 | /** 144 | * 通过git diff获取已提交文件的修改,包括文件的添加行的行号、删除行的行号、修改行的行号 145 | * 146 | * @param filePath 文件路径 147 | * @param project Project对象 148 | * @param startIndex 修改开始的下表数组 149 | * @param endIndex 修改结束的下表数组 150 | */ 151 | void getFileChangeStatus(String filePath, Project project, List startIndex, List endIndex) { 152 | try { 153 | String command = "git diff --unified=0 --ignore-blank-lines --ignore-all-space HEAD~1 HEAD " + filePath 154 | String changeInfo = command.execute(null, project.getRootDir()).text.trim() 155 | String[] changeLogs = changeInfo.split("@@") 156 | String[] indexArray 157 | 158 | for (int i = 1; i < changeLogs.size(); i += 2) { 159 | indexArray = changeLogs[i].trim().split(" ") 160 | try { 161 | int start, end 162 | String[] startArray = null 163 | if (indexArray.length > 1) { 164 | startArray = indexArray[1].split(",") 165 | } 166 | 167 | if (startArray != null && startArray.length > 1) { 168 | start = Integer.parseInt(startArray[0]) 169 | end = Integer.parseInt(startArray[0]) + Integer.parseInt(startArray[1]) 170 | } else { 171 | start = Integer.parseInt(startArray[0]) 172 | end = start + 1 173 | } 174 | startIndex.add(start) 175 | endIndex.add(end) 176 | } catch (NumberFormatException e) { 177 | e.printStackTrace() 178 | startIndex.add(0) 179 | endIndex.add(0) 180 | } 181 | 182 | } 183 | } catch (Exception e) { 184 | e.printStackTrace() 185 | } 186 | } 187 | 188 | /** 189 | * 检查特定后缀的文件 190 | * 比如: .java .xml等 191 | * 192 | * @param fileName 文件名 193 | * @return 匹配 返回true 否则 返回 false 194 | */ 195 | boolean isMatchFile(String[] fileNamePostfix, String fileName) { 196 | for (String fix : fileNamePostfix) { 197 | if (fileName.endsWith(fix)) { 198 | return true 199 | } 200 | } 201 | return false 202 | } 203 | } 204 | 205 | class LintConfig { 206 | def lintCheckFileType = "" 207 | def lintReportAll = false 208 | } -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/com/ls/lintplugin/LintToolClient.groovy: -------------------------------------------------------------------------------- 1 | package com.ls.lintplugin 2 | 3 | import com.android.tools.lint.LintCliClient 4 | import com.android.tools.lint.Warning 5 | import com.android.tools.lint.client.api.LintRequest 6 | import com.android.tools.lint.detector.api.Project 7 | 8 | /** 9 | * 重写LintClient 10 | * 用来创建LintRequest以便Lint扫描时使用,这里在LintRequest中加入了git提交的增量文件 11 | * LintCliClient run()方法是Lint扫描的入口 12 | * 13 | * @author liushuanggo@gmail.com 14 | * Time: 2019-4-16 15 | */ 16 | class LintToolClient extends LintCliClient { 17 | 18 | @Override 19 | protected LintRequest createLintRequest(List files) { 20 | LintRequest request = super.createLintRequest(files) 21 | for (Project project : request.getProjects()) { 22 | for (File file : files) { 23 | project.addFile(file) 24 | } 25 | } 26 | return new LintRequest(this, files) 27 | } 28 | 29 | /** 30 | * 获取扫描文件得到的结果 31 | * Warning类包含了文件路径、问题描述、问题所在文件的行号等信息 32 | * 33 | * @return Warnings 34 | */ 35 | List getIssueWarnings() { 36 | return warnings 37 | } 38 | } -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/com/ls/lintplugin/LintTxtReporter.groovy: -------------------------------------------------------------------------------- 1 | package com.ls.lintplugin 2 | 3 | import com.android.annotations.NonNull 4 | import com.android.annotations.Nullable 5 | import com.android.tools.lint.LintCliClient 6 | import com.android.tools.lint.LintStats 7 | import com.android.tools.lint.Main 8 | import com.android.tools.lint.Reporter 9 | import com.android.tools.lint.Warning 10 | import com.android.tools.lint.client.api.IssueRegistry 11 | import com.android.tools.lint.detector.api.Issue 12 | import com.android.tools.lint.detector.api.Location 13 | import com.android.tools.lint.detector.api.Position 14 | import com.android.tools.lint.detector.api.Severity 15 | import com.android.tools.lint.detector.api.TextFormat 16 | import com.android.utils.SdkUtils 17 | import com.google.common.base.Splitter 18 | 19 | /** 20 | * 重写Reporter,以TXT格式输出Lint扫描结果 21 | * 通过LintTxtReporter来将Lint扫描的结果精确到每一行的修改 22 | * 23 | * @author liushuanggo@gmail.com 24 | */ 25 | class LintTxtReporter extends Reporter { 26 | 27 | private Writer writer 28 | 29 | private List startLines 30 | private List endLines 31 | 32 | public int issueNumber = 0 33 | 34 | protected LintTxtReporter(@NonNull LintCliClient client, File output, Writer writer, List start, List end) { 35 | super(client, output) 36 | this.writer = writer 37 | this.startLines = start 38 | this.endLines = end 39 | } 40 | 41 | @Override 42 | void write(LintStats stats, List issues) throws IOException { 43 | issueNumber = 0 44 | StringBuilder output = new StringBuilder(issues.size() * 200) 45 | output.append(outputBanner()) 46 | output.append("\n") 47 | output.append("Lint检查日期: " + new Date().toString()) 48 | output.append("\n\n") 49 | if (issues.isEmpty()) { 50 | if (isDisplayEmpty()) { 51 | output.append("没有扫描结果") 52 | } 53 | } else { 54 | Issue lastIssue = null 55 | boolean isBetweenNewLines 56 | int lineNo 57 | for (Warning warning : issues) { 58 | isBetweenNewLines = false 59 | 60 | //输出的行号与文件中对应的行号相差1,所以这里进行加1操作 61 | lineNo = warning.line + 1 62 | 63 | /* 64 | * 1.找出扫描结果的行号是否在修改代码之间 65 | */ 66 | for (int i = 0; i < startLines.size(); i++) { 67 | if (lineNo >= startLines.get(i) && lineNo < endLines.get(i)) { 68 | //println("w line " + lineNo + " " + startLines.get(i) + " " + endLines.get(i)) 69 | isBetweenNewLines = true 70 | break 71 | } 72 | } 73 | 74 | /* 75 | * 2.如果Lint扫描到的Issue不在修改的范围之内,结束这次循环 76 | */ 77 | if (!isBetweenNewLines) { 78 | continue 79 | } 80 | 81 | /* 82 | * 3.Lint扫描的Issue在修改的范围内,将扫描结果写入文件 83 | */ 84 | if (warning.issue != lastIssue) { 85 | explainIssue(output, lastIssue) 86 | lastIssue = warning.issue 87 | } 88 | 89 | //记录Issue的数量 90 | issueNumber++ 91 | 92 | String p = warning.path 93 | if (p != null) { 94 | output.append("(").append(issueNumber).append(")") 95 | output.append("文件名: ") 96 | appendPath(output, p) 97 | output.append('\n') 98 | output.append("问题行号: ") 99 | 100 | if (warning.line >= 0) { 101 | output.append(Integer.toString(lineNo)) 102 | output.append('\n') 103 | } 104 | } 105 | 106 | Severity severity = warning.severity 107 | if (severity == Severity.FATAL) { 108 | severity = Severity.ERROR 109 | } 110 | 111 | output.append(severity.getDescription()) 112 | output.append(": ") 113 | output.append(TextFormat.RAW.convertTo(warning.message, TextFormat.TEXT)) 114 | 115 | if (warning.issue != null) { 116 | output.append(" [") 117 | output.append(warning.issue.getId()) 118 | output.append(']') 119 | } 120 | output.append('\n') 121 | 122 | if (warning.errorLine != null && !warning.errorLine.isEmpty()) { 123 | output.append("问题代码: ") 124 | output.append(warning.errorLine) 125 | } 126 | //output.append('\n') 127 | 128 | if (warning.location != null && warning.location.getSecondary() != null) { 129 | Location location = warning.location.getSecondary() 130 | boolean omitted = false 131 | while (location != null) { 132 | if (location.getMessage() != null && !location.getMessage().isEmpty()) { 133 | output.append(" ") 134 | String path = client.getDisplayPath(warning.project, location.getFile()) 135 | appendPath(output, path) 136 | 137 | Position start = location.getStart() 138 | if (start != null) { 139 | int line = start.getLine() 140 | if (line >= 0) { 141 | output.append(':') 142 | output.append(Integer.toString(line + 1)) 143 | } 144 | } 145 | 146 | if (location.getMessage() != null && !location.getMessage().isEmpty()) { 147 | output.append(": ") 148 | output.append(TextFormat.RAW.convertTo(location.message, TextFormat.TEXT)) 149 | } 150 | 151 | output.append('\n') 152 | } else { 153 | omitted = true 154 | } 155 | 156 | location = location.getSecondary() 157 | } 158 | 159 | if (omitted) { 160 | location = warning.location.getSecondary() 161 | StringBuilder sb = new StringBuilder(100) 162 | sb.append("Also affects: ") 163 | int begin = sb.length() 164 | while (location != null) { 165 | if (location.getMessage() == null 166 | || location.getMessage().isEmpty()) { 167 | if (sb.length() > begin) { 168 | sb.append(", ") 169 | } 170 | 171 | String path = client.getDisplayPath(warning.project, location.getFile()) 172 | appendPath(sb, path) 173 | 174 | Position start = location.getStart() 175 | if (start != null) { 176 | int line = start.getLine() 177 | if (line >= 0) { 178 | sb.append(':') 179 | sb.append(Integer.toString(line + 1)) 180 | } 181 | } 182 | } 183 | 184 | location = location.getSecondary() 185 | } 186 | String wrapped = Main.wrap(sb.toString(), Main.MAX_LINE_WIDTH, " ") 187 | output.append(wrapped) 188 | } 189 | } 190 | 191 | if (warning.isVariantSpecific()) { 192 | List names 193 | if (warning.includesMoreThanExcludes()) { 194 | output.append("Applies to variants: ") 195 | names = warning.getIncludedVariantNames() 196 | } else { 197 | output.append("Does not apply to variants: ") 198 | names = warning.getExcludedVariantNames() 199 | } 200 | output.append(Joiner.on(", ").join(names)) 201 | output.append('\n') 202 | } 203 | } 204 | 205 | if (issueNumber == 0) { 206 | output.append("没有扫描结果") 207 | output.append('\n') 208 | } 209 | explainIssue(output, lastIssue) 210 | output.append('\n\n') 211 | output.append("====================================================================================") 212 | .append("\n") 213 | .append("+++++++++++++++++++ " + "共发现" + issueNumber + "个Issue,请根据Issue说明的提示信息修改."+ "++++++++++++++++++++++++") 214 | .append("\n") 215 | .append("====================================================================================") 216 | .append("\n") 217 | writer.write(output.toString()) 218 | writer.write('\n') 219 | writer.flush() 220 | } 221 | } 222 | 223 | private void appendPath(@NonNull StringBuilder sb, @NonNull String path) { 224 | sb.append(path) 225 | } 226 | 227 | /** 228 | * 对出现的Issue进行说明 229 | * 230 | * @param output 输出 231 | * @param issue Lint Issue 232 | */ 233 | private void explainIssue(@NonNull StringBuilder output, @Nullable Issue issue) { 234 | if (issue == null || issue == IssueRegistry.LINT_ERROR || issue == IssueRegistry.BASELINE) { 235 | return 236 | } 237 | 238 | output.append("\n请根据以下提示修改.") 239 | output.append("\n===================================Issue说明========================================\n") 240 | 241 | String explanation = issue.getExplanation(TextFormat.TEXT) 242 | if (explanation.trim().isEmpty()) { 243 | return 244 | } 245 | 246 | String indent = " " 247 | String formatted = SdkUtils.wrap(explanation, Main.MAX_LINE_WIDTH - indent.length(), null) 248 | output.append('\n') 249 | output.append(indent) 250 | output.append("关于 Lint Issue \"").append(issue.getId()).append("\"的说明\n") 251 | for (String line : Splitter.on('\n').split(formatted)) { 252 | if (!line.isEmpty()) { 253 | output.append(indent) 254 | output.append(line) 255 | } 256 | output.append('\n') 257 | } 258 | output.append("==================================== end ==========================================\n\n\n") 259 | List moreInfo = issue.getMoreInfo() 260 | if (!moreInfo.isEmpty()) { 261 | for (String url : moreInfo) { 262 | if (formatted.contains(url)) { 263 | continue 264 | } 265 | output.append(indent) 266 | output.append(url) 267 | output.append('\n') 268 | } 269 | output.append('\n') 270 | } 271 | } 272 | 273 | private String outputBanner() { 274 | StringBuilder builder = new StringBuilder() 275 | builder.append("====================================================================================") 276 | .append("\n") 277 | .append("+++++++++++++++++++++++++++++++++++ Lint扫描结果 ++++++++++++++++++++++++++++++++++++") 278 | .append("\n") 279 | .append("====================================================================================") 280 | .append("\n") 281 | return builder.toString() 282 | } 283 | } -------------------------------------------------------------------------------- /buildSrc/src/main/resources/META-INF/gradle-plugins/lintplugin.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.ls.lintplugin.LintPlugin -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | 15 | 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Apr 13 14:13:24 CST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /lint-check-result-all.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/lint-check-result-all.xml -------------------------------------------------------------------------------- /lint-check-result.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lsc1993/AwesomeLint/f3975d6f8ff46811fb0044de26611012cbf0896a/lint-check-result.txt -------------------------------------------------------------------------------- /lintaar/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lintaar/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 28 5 | 6 | defaultConfig { 7 | minSdkVersion 15 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.0" 11 | } 12 | 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | 20 | } 21 | 22 | configurations { 23 | lintChecks 24 | } 25 | 26 | dependencies { 27 | //设置lint依赖 -> lint.jar 28 | lintChecks project(path: ":lintlib", configuration: "lintChecks") 29 | } 30 | 31 | task copyLintJar(type: Copy) { 32 | from (configurations.lintChecks) { 33 | rename { 34 | String filename -> 35 | "lint.jar" 36 | } 37 | } 38 | into "$buildDir/intermediates/lint/" 39 | } 40 | 41 | project.afterEvaluate { 42 | def compileLintTask = project.tasks.find { 43 | //println("task name:$it.name") 44 | it.name == "prepareLintJar" 45 | } 46 | compileLintTask.dependsOn(copyLintJar) 47 | } 48 | -------------------------------------------------------------------------------- /lintaar/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /lintaar/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /lintaar/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | lintaar 3 | 4 | -------------------------------------------------------------------------------- /lintlib/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lintlib/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java-library' 2 | 3 | dependencies { 4 | implementation fileTree(dir: 'libs', include: ['*.jar']) 5 | 6 | implementation 'com.android.tools.lint:lint:26.3.2' 7 | implementation 'com.android.tools.lint:lint-api:26.3.2' 8 | implementation 'com.android.tools.lint:lint-checks:26.3.2' 9 | } 10 | 11 | jar { 12 | manifest { 13 | attributes("Lint-Registry-v2": "com.ls.lintlib.LintIssueRegistry") 14 | } 15 | } 16 | 17 | configurations { 18 | lintChecks 19 | } 20 | 21 | dependencies { 22 | lintChecks files(jar) 23 | } 24 | 25 | tasks.withType(JavaCompile) { 26 | options.encoding = "UTF-8" 27 | } 28 | -------------------------------------------------------------------------------- /lintlib/src/main/java/com/ls/lintlib/LintIssueRegistry.java: -------------------------------------------------------------------------------- 1 | package com.ls.lintlib; 2 | 3 | import com.android.tools.lint.client.api.IssueRegistry; 4 | import com.android.tools.lint.detector.api.Issue; 5 | import com.ls.lintlib.detector.ClassCommentDetector; 6 | import com.ls.lintlib.detector.MessageDetector; 7 | import com.ls.lintlib.detector.SimpleDetector; 8 | 9 | import org.jetbrains.annotations.NotNull; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | /** 15 | * 用于注册自定义的Lint规则 16 | * 17 | * @author liushuanggo@gmail.com 18 | * Time: 2019/4/13 19 | */ 20 | public class LintIssueRegistry extends IssueRegistry { 21 | @NotNull 22 | @Override 23 | public List getIssues() { 24 | return Arrays.asList( 25 | SimpleDetector.ISSUE, 26 | ClassCommentDetector.ISSUE, 27 | MessageDetector.ISSUE 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /lintlib/src/main/java/com/ls/lintlib/detector/ClassCommentDetector.java: -------------------------------------------------------------------------------- 1 | package com.ls.lintlib.detector; 2 | 3 | 4 | import com.android.tools.lint.client.api.UElementHandler; 5 | import com.android.tools.lint.detector.api.Category; 6 | import com.android.tools.lint.detector.api.Detector; 7 | import com.android.tools.lint.detector.api.Implementation; 8 | import com.android.tools.lint.detector.api.Issue; 9 | import com.android.tools.lint.detector.api.JavaContext; 10 | import com.android.tools.lint.detector.api.Scope; 11 | import com.android.tools.lint.detector.api.Severity; 12 | 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | import org.jetbrains.uast.UClass; 16 | import org.jetbrains.uast.UComment; 17 | import org.jetbrains.uast.UElement; 18 | import org.jetbrains.uast.java.JavaUClass; 19 | import org.jetbrains.uast.kotlin.KotlinUClass; 20 | 21 | import java.util.Collections; 22 | import java.util.List; 23 | 24 | /** 25 | * 检测类注释 26 | * 这个Issue的作用就是检查类是否有符合模板的注释 27 | * 28 | * @author liushuanggo@gmail.com 29 | * date 2019/5/21. 30 | */ 31 | 32 | public class ClassCommentDetector extends Detector implements Detector.UastScanner { 33 | 34 | public static final String ID = "ClassComment"; 35 | public static final String DESCRIPTION = "类注释"; 36 | public static final String EXPLANATION = "每个类都需要注释,其内容包含描述、作者、日期,可以在 AS 中加入你喜欢的模板\n" + 37 | "/**\n" + 38 | " * Author : \n" + 39 | " * Time :\n" + 40 | " * Desc :\n" + 41 | " */\n"; 42 | public static final Category CATEGORY = Category.CORRECTNESS; 43 | public static final int PRIORITY = 6; 44 | public static final Severity SEVERITY = Severity.ERROR; 45 | public static final Implementation IMPLEMENTATION = new Implementation(ClassCommentDetector.class, Scope.JAVA_FILE_SCOPE); 46 | 47 | private static final String EXPLANATION_SIMPLE = "请添加正确的类注释"; 48 | private static final String AUTHOR = "author"; 49 | private static final String DATE = "date"; 50 | private static final String TIME = "time"; 51 | private static final String DESC = "desc"; 52 | 53 | public static final Issue ISSUE = Issue.create( 54 | ID, 55 | DESCRIPTION, 56 | EXPLANATION, 57 | CATEGORY, 58 | PRIORITY, 59 | SEVERITY, 60 | IMPLEMENTATION 61 | ); 62 | 63 | @Nullable 64 | @Override 65 | public List> getApplicableUastTypes() { 66 | return Collections.singletonList(UClass.class); 67 | } 68 | 69 | @Nullable 70 | @Override 71 | public UElementHandler createUastHandler(@NotNull JavaContext context) { 72 | return new UElementHandler(){ 73 | @Override 74 | public void visitClass(@NotNull UClass node) { 75 | // 必须是Java或kotlin类以及非内部类才会接受检测 76 | boolean isTarget = (node instanceof JavaUClass || node instanceof KotlinUClass) && node.getContainingClass() == null; 77 | if(!isTarget){ 78 | return ; 79 | } 80 | List uComments = node.getComments(); 81 | // 获取类的第一个注释,即类注释 uComments不可能为null 82 | if(uComments.size() > 0){ 83 | String comment = uComments.get(0).asSourceString().toLowerCase(); 84 | // 类描述必须有 85 | if(comment.contains(AUTHOR) && comment.contains(DESC)){ 86 | return ; 87 | } 88 | } 89 | context.report(ISSUE, context.getNameLocation(node), EXPLANATION_SIMPLE); 90 | } 91 | }; 92 | } 93 | } 94 | 95 | -------------------------------------------------------------------------------- /lintlib/src/main/java/com/ls/lintlib/detector/MessageDetector.java: -------------------------------------------------------------------------------- 1 | package com.ls.lintlib.detector; 2 | 3 | import com.android.tools.lint.detector.api.Category; 4 | import com.android.tools.lint.detector.api.Detector; 5 | import com.android.tools.lint.detector.api.Implementation; 6 | import com.android.tools.lint.detector.api.Issue; 7 | import com.android.tools.lint.detector.api.JavaContext; 8 | import com.android.tools.lint.detector.api.Scope; 9 | import com.android.tools.lint.detector.api.Severity; 10 | import com.intellij.psi.PsiMethod; 11 | 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | import org.jetbrains.uast.UCallExpression; 15 | 16 | import java.util.Arrays; 17 | import java.util.List; 18 | 19 | /** 20 | * 检查代码中直接使用 new Message() 的情况 21 | * 建议使用 handler.obtainMessage() 或者 Message.obtain() 方法" 22 | * 23 | * @author liushuanggo@gmail.com 24 | * date 2019/5/21 25 | */ 26 | public class MessageDetector extends Detector implements Detector.UastScanner { 27 | 28 | public static final Issue ISSUE = Issue.create( 29 | "Message_Obtain_Tip", 30 | "建议使用handler.obtainMessage()或者Message.obtain()方法", 31 | "为了减少内存开销,不应该直接使用{new Message()},应该使用 {handler.obtainMessage()方法} 或者 {Message.obtain()方法},这样的话从整个Message池中返回一个新的Message实例,从而能够避免重复Message创建对象,减少内存开销.", 32 | Category.PERFORMANCE, 33 | 5, 34 | Severity.WARNING, 35 | new Implementation(MessageDetector.class, Scope.JAVA_FILE_SCOPE) 36 | ); 37 | 38 | 39 | @Nullable 40 | @Override 41 | public List getApplicableConstructorTypes() { 42 | return Arrays.asList("android.os.Message"); 43 | } 44 | 45 | @Override 46 | public void visitConstructor(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod constructor) { 47 | context.report(ISSUE, node, context.getLocation(node), "避免直接使用new Message()"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lintlib/src/main/java/com/ls/lintlib/detector/SimpleDetector.java: -------------------------------------------------------------------------------- 1 | package com.ls.lintlib.detector; 2 | 3 | import com.android.tools.lint.detector.api.Category; 4 | import com.android.tools.lint.detector.api.Detector; 5 | import com.android.tools.lint.detector.api.Implementation; 6 | import com.android.tools.lint.detector.api.Issue; 7 | import com.android.tools.lint.detector.api.JavaContext; 8 | import com.android.tools.lint.detector.api.Scope; 9 | import com.android.tools.lint.detector.api.Severity; 10 | import com.intellij.psi.JavaElementVisitor; 11 | import com.intellij.psi.PsiMethod; 12 | import com.intellij.psi.PsiMethodCallExpression; 13 | 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | import org.jetbrains.uast.UCallExpression; 17 | 18 | import java.util.Arrays; 19 | import java.util.List; 20 | 21 | /** 22 | * 一个自定义Lint规则的简单示例 23 | * Detector: 根据定义的lint规则进行扫描 24 | * Issue: 描述一个自定义Lint规则 25 | * 26 | * @author liushuanggo@gmail.com 27 | * Time: 2019-4-13 28 | */ 29 | public class SimpleDetector extends Detector implements Detector.UastScanner { 30 | 31 | /** 32 | * 一个Lint规则 33 | * 这里是关于使用android.util.Log以及system.out的提示 34 | * 参数说明: 35 | * id: 唯一值,简短描述当前问题 36 | * briefDescription: 简短总结 37 | * explanation: 完整的描述问题以及提出修改建议 38 | * category: 问题类别 39 | * priority: 优先级 10最重要 40 | * severity: 严重级别 41 | * Implementation: 提供Issue和 Detector提供映射关系 42 | */ 43 | public static final Issue ISSUE = Issue.create( 44 | "Log", 45 | "请使用项目中提供的Log工具", 46 | "避免在项目中直接使用android.log以及system.out", 47 | Category.PERFORMANCE, 48 | 5, 49 | Severity.ERROR, 50 | new Implementation(SimpleDetector.class, Scope.JAVA_FILE_SCOPE) 51 | ); 52 | 53 | /** 54 | * 需要被检查的方法名 55 | * 56 | * @return 方法名 57 | */ 58 | @Override 59 | public List getApplicableMethodNames() { 60 | return Arrays.asList("v", "d", "i", "w", "e", "println", "print"); 61 | } 62 | 63 | /*@Override 64 | public void visitMethod(@NotNull JavaContext context, @Nullable JavaElementVisitor visitor, @NotNull PsiMethodCallExpression call, @NotNull PsiMethod method) { 65 | if (context.getEvaluator().isMemberInClass(method, "android.util.Log") 66 | || context.getEvaluator().isMemberInClass(method, "java.io.PrintStream")) { 67 | //向 Android Studio 报告问题 68 | context.report( 69 | ISSUE, 70 | call.getContext(), 71 | context.getLocation(call.getOriginalElement()), 72 | "建议使用封装好的Log工具类打印日志"); 73 | } 74 | } 75 | */ 76 | @Override 77 | public void visitMethod(@NotNull JavaContext context, @NotNull UCallExpression node, @NotNull PsiMethod method) { 78 | if (context.getEvaluator().isMemberInClass(method, "android.util.Log") || context.getEvaluator().isMemberInClass(method, "java.io.PrintStream")) { 79 | context.report(ISSUE, node, context.getLocation(node), "应该使用项目中的Log工具!"); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /post-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | $HOME/usr/project_git/AwesomeLint/./gradlew lintCheck 3 | exit 0 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lintlib', ':lintaar', ':buildSrc' 2 | --------------------------------------------------------------------------------