├── .gitignore ├── LICENSE ├── README.md ├── aar ├── README.md ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── meituan │ │ │ └── android │ │ │ └── lint │ │ │ └── demo │ │ │ └── MainActivity.java │ │ └── res │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lint │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ └── res │ │ └── values │ │ └── strings.xml ├── lintCoreLibrary │ ├── build.gradle │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── meituan │ │ │ └── android │ │ │ └── lint │ │ │ └── core │ │ │ ├── HashMapForJDK7Detector.java │ │ │ ├── LogDetector.java │ │ │ ├── MTIssueRegistry.java │ │ │ └── ToastDetector.java │ │ └── test │ │ ├── java │ │ └── com │ │ │ └── meituan │ │ │ └── android │ │ │ └── lint │ │ │ └── core │ │ │ ├── HashMapForJDK7DetectorTest.java │ │ │ ├── LogDetectorTest.java │ │ │ ├── MTIssueRegistryTest.java │ │ │ └── util │ │ │ └── MTLintDetectorTest.java │ │ └── resources │ │ ├── hashmap │ │ ├── HashMapCase1.java │ │ ├── HashMapCase2.java │ │ ├── HashMapCase3.java │ │ ├── HashMapCase4_1.java │ │ ├── HashMapCase4_2.java │ │ └── HashMapCase5.java │ │ └── log │ │ ├── LogCase1.java │ │ ├── LogCase2.java │ │ └── fake │ │ ├── Log.java │ │ ├── PrintStream.java │ │ └── System.java └── settings.gradle ├── check_custom_lint.py └── plugin ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src └── main ├── groovy └── com │ └── meituan │ └── android │ └── lint │ └── MTLintPlugin.groovy ├── java └── com │ └── meituan │ └── android │ └── lint │ ├── IOUtils.java │ └── XMLMergeUtil.java └── resources ├── META-INF └── gradle-plugins │ └── MTLintPlugin.properties └── config ├── lint.xml └── retrolambda_lint.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | lint-report/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # IntelliJ 36 | .idea/ 37 | *.iml 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MeituanCustomLintDemo 2 | 美团自定义Lint示例 3 | 4 | ## 代码使用说明 5 | 6 | 本库主要分为aar、plugin、python脚本三部分 7 | 8 | - aar提供了集成自定义lint规则的aar(包括基本的编码规范以及公司要求的安全规范),团队可以集成后结合本身的lint配置进行使用。详见aar文件夹中的README。 9 | 10 | 11 | - plugin提供集成自定义lint aar、lintOptions、lint.xml的插件(配置使用统一标准),方便工程内一键部署使用,不用担心lint规则配置麻烦的问题。详见plugin文件夹中的README。 12 | 13 | 14 | - check_custom_lint.py:集成plugin,同时针对遇到的retrolambda ast问题进行了自动处理,并运行lint查找代码问题,查找完成执行git reset,减少对后续检查的影响。可真正实现一键运行效果,方便CI部署。 15 | 16 | ``` 17 | ./check_custom_lint.py -s aar/app 18 | ``` 19 | 20 | ​ 21 | 22 | ## 使用场景 23 | 24 | 一般配合CI使用,如Jenkins等。 25 | 26 | 本地使用场景有两个缺点: 27 | 28 | - lint检查耗时 29 | - 本地检查容易被忽略或者修改,无法保证质量。 30 | 31 | ## 声明 32 | 33 | 本仓库仅提供美团自定义Lint的框架,具体lint检查实现仅提供了两个示例,并不是可直接部署使用的工具。开发者可根据公司代码规范自行增加定制开发。 34 | 35 | ## License 36 | 37 | ``` 38 | Copyright 2016 GavinCT 39 | 40 | Licensed under the Apache License, Version 2.0 (the "License"); 41 | you may not use this file except in compliance with the License. 42 | You may obtain a copy of the License at 43 | 44 | http://www.apache.org/licenses/LICENSE-2.0 45 | 46 | Unless required by applicable law or agreed to in writing, software 47 | distributed under the License is distributed on an "AS IS" BASIS, 48 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 49 | See the License for the specific language governing permissions and 50 | limitations under the License. 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /aar/README.md: -------------------------------------------------------------------------------- 1 | # 接入方式 2 | 3 | `lintOptions`配置好后,使用 4 | 5 | ``` groovy 6 | compile 'com.czt.mtlint:lint:latest.integration' 7 | ``` 8 | 9 | lint.xml等配置和以前lint用法相同 10 | 11 | # 使用注意 12 | 13 | ## 不可使用1.2.x系列的gradle plugin 14 | 15 | 1.2.x gradle plugin遇到的两个问题 16 | 17 | - [Issue 174808:custom lint in AAR doesn’t work](https://code.google.com/p/android/issues/detail?id=174808) 18 | - [Issue 178699:lint.jar in AAR doesn’t work sometimes](https://code.google.com/p/android/issues/detail?id=178699) 19 | 20 | ## 项目中使用了retrolambda 21 | 22 | 如果项目中使用了gradle plugin 1.5 和 retrolambda,请注意配置替换原有的抽象语法树(ast),避免lint因不识别lambda表达式而报错 23 | 24 | ``` groovy 25 | buildscript { 26 | repositories { 27 | jcenter() 28 | } 29 | 30 | dependencies { 31 | classpath 'com.android.tools.build:gradle:' 32 | classpath "me.tatarka:gradle-retrolambda:" 33 | classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' 34 | } 35 | 36 | // Exclude the version that the android plugin depends on. 37 | configurations.classpath.exclude group: 'com.android.tools.external.lombok' 38 | } 39 | ``` -------------------------------------------------------------------------------- /aar/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.meituan.android.lint.demo" 9 | minSdkVersion 9 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | lintOptions { 21 | lintConfig file("lint.xml") 22 | warningsAsErrors true 23 | abortOnError true 24 | htmlReport true 25 | htmlOutput file("lint-report/lint-report.html") 26 | xmlReport false 27 | } 28 | } 29 | 30 | dependencies { 31 | compile fileTree(dir: 'libs', include: ['*.jar']) 32 | compile 'com.android.support:appcompat-v7:25.2.0' 33 | compile project(':lint') 34 | } 35 | -------------------------------------------------------------------------------- /aar/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chentong/android-sdk-mac_x86/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /aar/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /aar/app/src/main/java/com/meituan/android/lint/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.util.Log; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | public class MainActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_main); 16 | testHashMap(); 17 | testLog(); 18 | } 19 | 20 | private void testLog() { 21 | System.out.println("test"); 22 | Log.i("test", "test"); 23 | Log.d("test", "test"); 24 | } 25 | 26 | public static void testHashMap() { 27 | HashMap map1 = new HashMap(); 28 | map1.put(1, "name"); 29 | HashMap map2 = new HashMap<>(); 30 | map2.put(1, "name"); 31 | Map map3 = new HashMap<>(); 32 | map3.put(1, "name"); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aar/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /aar/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinCT/MeituanLintDemo/fa80c6dc38657f1ab4e492d18d749fa6fcad51e0/aar/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /aar/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinCT/MeituanLintDemo/fa80c6dc38657f1ab4e492d18d749fa6fcad51e0/aar/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /aar/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinCT/MeituanLintDemo/fa80c6dc38657f1ab4e492d18d749fa6fcad51e0/aar/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /aar/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinCT/MeituanLintDemo/fa80c6dc38657f1ab4e492d18d749fa6fcad51e0/aar/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /aar/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /aar/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /aar/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CustomLint 3 | 4 | Hello world! 5 | 6 | -------------------------------------------------------------------------------- /aar/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /aar/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 | jcenter() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:2.3.0' 10 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 11 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' 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 | jcenter() 20 | mavenCentral() 21 | } 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } 27 | -------------------------------------------------------------------------------- /aar/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /aar/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinCT/MeituanLintDemo/fa80c6dc38657f1ab4e492d18d749fa6fcad51e0/aar/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /aar/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Mar 07 23:30:44 CST 2017 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-3.3-all.zip 7 | -------------------------------------------------------------------------------- /aar/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /aar/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /aar/lint/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | } 24 | 25 | /* 26 | * rules for including "lint.jar" in aar 27 | */ 28 | configurations { 29 | lintJarImport 30 | } 31 | 32 | dependencies { 33 | lintJarImport project(path: ":lintCoreLibrary", configuration: "lintJarOutput") 34 | } 35 | 36 | task copyLintJar(type: Copy) { 37 | from (configurations.lintJarImport) { 38 | rename { 39 | String fileName -> 40 | 'lint.jar' 41 | } 42 | } 43 | into 'build/intermediates/lint/' 44 | } 45 | 46 | project.afterEvaluate { 47 | def compileLintTask = project.tasks.find { it.name == 'compileLint' } 48 | compileLintTask.dependsOn(copyLintJar) 49 | } 50 | 51 | 52 | apply plugin: 'com.github.dcendents.android-maven' 53 | apply plugin: 'com.jfrog.bintray' 54 | version = "1.0.0" 55 | group = "com.czt.mtlint" // Maven Group ID for the artifact,一般填你唯一的包名 56 | def siteUrl = 'https://github.com/GavinCT/MeituanCustomLintDemo' // 项目的主页 57 | def gitUrl = 'https://github.com/GavinCT/MeituanCustomLintDemo.git' // Git仓库的url 58 | 59 | install { 60 | repositories.mavenInstaller { 61 | // This generates POM.xml with proper parameters 62 | pom { 63 | project { 64 | packaging 'aar' 65 | // Add your description here 66 | name 'MeituanCustomLintDemoLibrary' //项目描述 67 | url siteUrl 68 | // Set your license 69 | licenses { 70 | license { 71 | name 'The Apache Software License, Version 2.0' 72 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 73 | } 74 | } 75 | developers { 76 | developer { 77 | id 'GavinCT' //填写的一些基本信息 78 | name 'chentong' 79 | email 'chentong.think@gmail.com' 80 | } 81 | } 82 | scm { 83 | connection gitUrl 84 | developerConnection gitUrl 85 | url siteUrl 86 | } 87 | } 88 | } 89 | } 90 | } 91 | task sourcesJar(type: Jar) { 92 | from android.sourceSets.main.java.srcDirs 93 | classifier = 'sources' 94 | } 95 | task javadoc(type: Javadoc) { 96 | source = android.sourceSets.main.java.srcDirs 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | options.encoding = "UTF-8" 99 | } 100 | task javadocJar(type: Jar, dependsOn: javadoc) { 101 | classifier = 'javadoc' 102 | from javadoc.destinationDir 103 | } 104 | artifacts { 105 | archives javadocJar 106 | archives sourcesJar 107 | } 108 | Properties properties = new Properties() 109 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 110 | bintray { 111 | user = properties.getProperty("bintray.user") 112 | key = properties.getProperty("bintray.apikey") 113 | configurations = ['archives'] 114 | pkg { 115 | repo = "maven" 116 | name = "MeituanCustomLintDemoLibrary" //发布到JCenter上的项目名字 117 | websiteUrl = siteUrl 118 | vcsUrl = gitUrl 119 | licenses = ["Apache-2.0"] 120 | publish = true 121 | } 122 | } -------------------------------------------------------------------------------- /aar/lint/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/chentong/android-sdk-mac_x86/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /aar/lint/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /aar/lint/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | lint 3 | 4 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'java' 2 | 3 | def lint_version = "25.1.0" 4 | dependencies { 5 | compile fileTree(dir: 'libs', include: ['*.jar']) 6 | compile 'com.android.tools.lint:lint-api:' + lint_version 7 | compile 'com.android.tools.lint:lint-checks:' + lint_version 8 | 9 | testCompile 'junit:junit:4.12' 10 | testCompile 'org.assertj:assertj-core:3.0.0' 11 | testCompile 'org.mockito:mockito-core:1.9.5' 12 | testCompile 'com.android.tools.lint:lint:' + lint_version 13 | testCompile 'com.android.tools.lint:lint-tests:' + lint_version 14 | testCompile 'com.android.tools:testutils:' + lint_version 15 | } 16 | 17 | jar { 18 | manifest { 19 | attributes("Lint-Registry": "com.meituan.android.lint.core.MTIssueRegistry") 20 | } 21 | } 22 | 23 | defaultTasks 'assemble' 24 | 25 | /* 26 | * rules for providing "lintCoreLibrary.jar" 27 | */ 28 | configurations { 29 | lintJarOutput 30 | } 31 | 32 | dependencies { 33 | lintJarOutput files(jar) 34 | } 35 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/main/java/com/meituan/android/lint/core/HashMapForJDK7Detector.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.SdkConstants; 4 | import com.android.annotations.NonNull; 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 java.util.Collections; 14 | import java.util.List; 15 | import java.util.regex.Matcher; 16 | import java.util.regex.Pattern; 17 | 18 | import lombok.ast.AstVisitor; 19 | import lombok.ast.BinaryExpression; 20 | import lombok.ast.ConstructorInvocation; 21 | import lombok.ast.Expression; 22 | import lombok.ast.ExpressionStatement; 23 | import lombok.ast.ForwardingAstVisitor; 24 | import lombok.ast.Node; 25 | import lombok.ast.StrictListAccessor; 26 | import lombok.ast.TypeReference; 27 | import lombok.ast.VariableDefinition; 28 | 29 | /** 30 | * 针对 JDK7 新语法的加强检测 31 | * http://docs.oracle.com/javase/7/docs/technotes/guides/language/type-inference-generic-instance-creation.html 32 | * 33 | * Created by chentong on 26/10/2015. 34 | */ 35 | public class HashMapForJDK7Detector extends Detector implements Detector.JavaScanner { 36 | /** Using HashMaps where SparseArray would be better */ 37 | public static final Issue ISSUE = Issue.create( 38 | "UseSparseArrays_JDK7", //$NON-NLS-1$ 39 | "HashMap can be replaced with SparseArray", 40 | 41 | "For maps where the keys are of type integer, it's typically more efficient to " + 42 | "use the Android `SparseArray` API. This check identifies scenarios where you might " + 43 | "want to consider using `SparseArray` instead of `HashMap` for better performance.\n" + 44 | "\n" + 45 | "This is *particularly* useful when the value types are primitives like ints, " + 46 | "where you can use `SparseIntArray` and avoid auto-boxing the values from `int` to " + 47 | "`Integer`.\n" + 48 | "\n" + 49 | "If you need to construct a `HashMap` because you need to call an API outside of " + 50 | "your control which requires a `Map`, you can suppress this warning using for " + 51 | "example the `@SuppressLint` annotation.", 52 | 53 | Category.PERFORMANCE, 54 | 4, 55 | Severity.WARNING, 56 | new Implementation( 57 | HashMapForJDK7Detector.class, 58 | Scope.JAVA_FILE_SCOPE) 59 | ); 60 | 61 | @Override 62 | public List> getApplicableNodeTypes() { 63 | return Collections.>singletonList(ConstructorInvocation.class); 64 | } 65 | 66 | private static final String INTEGER = "Integer"; //$NON-NLS-1$ 67 | private static final String BOOLEAN = "Boolean"; //$NON-NLS-1$ 68 | private static final String BYTE = "Byte"; //$NON-NLS-1$ 69 | private static final String LONG = "Long"; //$NON-NLS-1$ 70 | private static final String HASH_MAP = "HashMap"; //$NON-NLS-1$ 71 | 72 | @Override 73 | public AstVisitor createJavaVisitor(final @NonNull JavaContext context) { 74 | return new ForwardingAstVisitor() { 75 | 76 | @Override 77 | public boolean visitConstructorInvocation(ConstructorInvocation node) { 78 | TypeReference reference = node.astTypeReference(); 79 | String typeName = reference.astParts().last().astIdentifier().astValue(); 80 | // TODO: Should we handle factory method constructions of HashMaps as well, 81 | // e.g. via Guava? This is a bit trickier since we need to infer the type 82 | // arguments from the calling context. 83 | if (typeName.equals(HASH_MAP)) { 84 | checkHashMap(context, node, reference); 85 | } 86 | return super.visitConstructorInvocation(node); 87 | } 88 | }; 89 | } 90 | 91 | /** 92 | * Checks whether the given constructor call and type reference refers 93 | * to a HashMap constructor call that is eligible for replacement by a 94 | * SparseArray call instead 95 | */ 96 | private void checkHashMap(JavaContext context, ConstructorInvocation node, TypeReference reference) { 97 | StrictListAccessor types = reference.getTypeArguments(); 98 | if (types == null || types.size() != 2) { 99 | /* 100 | JDK7 新写法 101 | HashMap map2 = new HashMap<>(); 102 | map2.put(1, "name"); 103 | Map map3 = new HashMap<>(); 104 | map3.put(1, "name"); 105 | */ 106 | 107 | Node result = node.getParent().getParent(); 108 | if (result instanceof VariableDefinition) { 109 | TypeReference typeReference = ((VariableDefinition) result).astTypeReference(); 110 | checkCore(context, result, typeReference); 111 | return; 112 | } 113 | 114 | if (result instanceof ExpressionStatement) { 115 | Expression expression = ((ExpressionStatement) result).astExpression(); 116 | if (expression instanceof BinaryExpression) { 117 | Expression left = ((BinaryExpression) expression).astLeft(); 118 | String fullTypeName = context.getType(left).getName(); 119 | checkCore2(context, result, fullTypeName); 120 | } 121 | } 122 | } 123 | // else --> lint本身已经检测 124 | } 125 | 126 | private void checkCore2(JavaContext context, Node node, String fullTypeName) { 127 | final Pattern p = Pattern.compile(".*<(.*),(.*)>"); 128 | Matcher m = p.matcher(fullTypeName); 129 | if (m.find()) { 130 | String typeName = m.group(1).trim(); 131 | String valueType = m.group(2).trim(); 132 | int minSdk = context.getMainProject().getMinSdk(); 133 | if (typeName.equals(INTEGER) || typeName.equals(BYTE)) { 134 | if (valueType.equals(INTEGER)) { 135 | context.report(ISSUE, node, context.getLocation(node), 136 | "Use new SparseIntArray(...) instead for better performance"); 137 | } else if (valueType.equals(LONG) && minSdk >= 18) { 138 | context.report(ISSUE, node, context.getLocation(node), 139 | "Use new SparseLongArray(...) instead for better performance"); 140 | } else if (valueType.equals(BOOLEAN)) { 141 | context.report(ISSUE, node, context.getLocation(node), 142 | "Use new SparseBooleanArray(...) instead for better performance"); 143 | } else { 144 | context.report(ISSUE, node, context.getLocation(node), 145 | String.format( 146 | "Use new SparseArray<%1$s>(...) instead for better performance", 147 | valueType)); 148 | } 149 | } else if (typeName.equals(LONG) && (minSdk >= 16 || 150 | Boolean.TRUE == context.getMainProject().dependsOn( 151 | SdkConstants.SUPPORT_LIB_ARTIFACT))) { 152 | boolean useBuiltin = minSdk >= 16; 153 | String message = useBuiltin ? 154 | "Use new LongSparseArray(...) instead for better performance" : 155 | "Use new android.support.v4.util.LongSparseArray(...) instead for better performance"; 156 | context.report(ISSUE, node, context.getLocation(node), 157 | message); 158 | } 159 | } 160 | } 161 | 162 | /** 163 | * copy from lint source code 164 | */ 165 | private void checkCore(JavaContext context, Node node, TypeReference reference) { 166 | // reference.hasTypeArguments returns false where it should not 167 | StrictListAccessor types = reference.getTypeArguments(); 168 | if (types != null && types.size() == 2) { 169 | TypeReference first = types.first(); 170 | String typeName = first.getTypeName(); 171 | int minSdk = context.getMainProject().getMinSdk(); 172 | if (typeName.equals(INTEGER) || typeName.equals(BYTE)) { 173 | String valueType = types.last().getTypeName(); 174 | if (valueType.equals(INTEGER)) { 175 | context.report(ISSUE, node, context.getLocation(node), 176 | "Use new SparseIntArray(...) instead for better performance"); 177 | } else if (valueType.equals(LONG) && minSdk >= 18) { 178 | context.report(ISSUE, node, context.getLocation(node), 179 | "Use new SparseLongArray(...) instead for better performance"); 180 | } else if (valueType.equals(BOOLEAN)) { 181 | context.report(ISSUE, node, context.getLocation(node), 182 | "Use new SparseBooleanArray(...) instead for better performance"); 183 | } else { 184 | context.report(ISSUE, node, context.getLocation(node), 185 | String.format( 186 | "Use new SparseArray<%1$s>(...) instead for better performance", 187 | valueType)); 188 | } 189 | } else if (typeName.equals(LONG) && (minSdk >= 16 || 190 | Boolean.TRUE == context.getMainProject().dependsOn( 191 | SdkConstants.SUPPORT_LIB_ARTIFACT))) { 192 | boolean useBuiltin = minSdk >= 16; 193 | String message = useBuiltin ? 194 | "Use new LongSparseArray(...) instead for better performance" : 195 | "Use new android.support.v4.util.LongSparseArray(...) instead for better performance"; 196 | context.report(ISSUE, node, context.getLocation(node), 197 | message); 198 | } 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/main/java/com/meituan/android/lint/core/LogDetector.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.tools.lint.client.api.JavaParser; 4 | import com.android.tools.lint.detector.api.Category; 5 | import com.android.tools.lint.detector.api.Detector; 6 | import com.android.tools.lint.detector.api.Implementation; 7 | import com.android.tools.lint.detector.api.Issue; 8 | import com.android.tools.lint.detector.api.JavaContext; 9 | import com.android.tools.lint.detector.api.Scope; 10 | import com.android.tools.lint.detector.api.Severity; 11 | 12 | import java.util.Collections; 13 | import java.util.List; 14 | 15 | import lombok.ast.AstVisitor; 16 | import lombok.ast.ForwardingAstVisitor; 17 | import lombok.ast.MethodInvocation; 18 | import lombok.ast.Node; 19 | 20 | /** 21 | * 避免使用Log / System.out.println ,提醒使用Ln 22 | * Created by chentong on 18/9/15. 23 | */ 24 | public class LogDetector extends Detector implements Detector.JavaScanner{ 25 | 26 | public static final Issue ISSUE = Issue.create( 27 | "LogUse", 28 | "避免使用Log/System.out.println", 29 | "使用Ln,防止在正式包打印log", 30 | Category.SECURITY, 5, Severity.ERROR, 31 | new Implementation(LogDetector.class, Scope.JAVA_FILE_SCOPE)); 32 | 33 | @Override 34 | public List> getApplicableNodeTypes() { 35 | return Collections.>singletonList(MethodInvocation.class); 36 | } 37 | 38 | @Override 39 | public AstVisitor createJavaVisitor(final JavaContext context) { 40 | return new ForwardingAstVisitor() { 41 | @Override 42 | public boolean visitMethodInvocation(MethodInvocation node) { 43 | JavaParser.ResolvedNode resolve = context.resolve(node); 44 | if (resolve instanceof JavaParser.ResolvedMethod) { 45 | JavaParser.ResolvedMethod method = (JavaParser.ResolvedMethod) resolve; 46 | // 方法所在的类校验 47 | JavaParser.ResolvedClass containingClass = method.getContainingClass(); 48 | if (containingClass.matches("android.util.Log")) { 49 | context.report(ISSUE, node, context.getLocation(node), 50 | "请使用Ln,避免使用Log"); 51 | return true; 52 | } 53 | if (node.toString().startsWith("System.out.println")) { 54 | context.report(ISSUE, node, context.getLocation(node), 55 | "请使用Ln,避免使用System.out.println"); 56 | return true; 57 | } 58 | } 59 | return super.visitMethodInvocation(node); 60 | } 61 | }; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/main/java/com/meituan/android/lint/core/MTIssueRegistry.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.tools.lint.checks.BuiltinIssueRegistry; 4 | import com.android.tools.lint.checks.ToastDetector; 5 | import com.android.tools.lint.client.api.IssueRegistry; 6 | import com.android.tools.lint.detector.api.Issue; 7 | 8 | import java.lang.reflect.Field; 9 | import java.lang.reflect.Modifier; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class MTIssueRegistry extends IssueRegistry { 15 | static { 16 | // 替换一些有问题的系统的检查 变成我们的改进版 17 | List systemIssues = new BuiltinIssueRegistry().getIssues(); 18 | List systemIssuesNew = new ArrayList<>(systemIssues.size()); 19 | systemIssuesNew.addAll(systemIssues); 20 | systemIssuesNew.remove(ToastDetector.ISSUE); 21 | systemIssuesNew.add(com.meituan.android.lint.core.ToastDetector.ISSUE); 22 | try { 23 | Field field = BuiltinIssueRegistry.class.getDeclaredField("sIssues"); 24 | field.setAccessible(true); 25 | Field modifiersField = Field.class.getDeclaredField("modifiers"); 26 | modifiersField.setAccessible(true); 27 | modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); 28 | field.set(null, systemIssuesNew); 29 | } catch (NoSuchFieldException e) { 30 | e.printStackTrace(); 31 | } catch (IllegalAccessException e) { 32 | e.printStackTrace(); 33 | } 34 | } 35 | @Override 36 | public synchronized List getIssues() { 37 | System.out.println("==== MT lint start ===="); 38 | return Arrays.asList( 39 | LogDetector.ISSUE, 40 | HashMapForJDK7Detector.ISSUE 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/main/java/com/meituan/android/lint/core/ToastDetector.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.annotations.NonNull; 4 | import com.android.annotations.Nullable; 5 | import com.android.tools.lint.detector.api.Category; 6 | import com.android.tools.lint.detector.api.Context; 7 | import com.android.tools.lint.detector.api.Detector; 8 | import com.android.tools.lint.detector.api.Implementation; 9 | import com.android.tools.lint.detector.api.Issue; 10 | import com.android.tools.lint.detector.api.JavaContext; 11 | import com.android.tools.lint.detector.api.Scope; 12 | import com.android.tools.lint.detector.api.Severity; 13 | 14 | import java.io.File; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | import lombok.ast.AstVisitor; 19 | import lombok.ast.ConstructorDeclaration; 20 | import lombok.ast.Expression; 21 | import lombok.ast.ForwardingAstVisitor; 22 | import lombok.ast.IntegralLiteral; 23 | import lombok.ast.MethodDeclaration; 24 | import lombok.ast.MethodInvocation; 25 | import lombok.ast.Node; 26 | import lombok.ast.Return; 27 | import lombok.ast.StrictListAccessor; 28 | /* 29 | 拷贝自 lint源码的 Toast检查 30 | 源码通过 JavaContext.findSurroundingMethod来查找外围方法,无法识别lambda,导致在lambda表达式中show会误报 31 | 这里改进一下。 32 | 33 | 改进部分: 34 | findSurroundingMethod部分 新增: 35 | type != LambdaExpression.class 36 | 37 | */ 38 | /** Detector looking for Toast.makeText() without a corresponding show() call */ 39 | public class ToastDetector extends Detector implements Detector.JavaScanner { 40 | /** The main issue discovered by this detector */ 41 | public static final Issue ISSUE = Issue.create( 42 | "ShowToast", //$NON-NLS-1$ 43 | "Toast created but not shown", 44 | 45 | "`Toast.makeText()` creates a `Toast` but does *not* show it. You must call " + 46 | "`show()` on the resulting object to actually make the `Toast` appear.", 47 | 48 | Category.CORRECTNESS, 49 | 6, 50 | Severity.WARNING, 51 | new Implementation( 52 | ToastDetector.class, 53 | Scope.JAVA_FILE_SCOPE)); 54 | 55 | 56 | /** Constructs a new {@link ToastDetector} check */ 57 | public ToastDetector() { 58 | } 59 | 60 | @Override 61 | public boolean appliesTo(@NonNull Context context, @NonNull File file) { 62 | return true; 63 | } 64 | 65 | 66 | // ---- Implements JavaScanner ---- 67 | 68 | @Override 69 | public List getApplicableMethodNames() { 70 | return Collections.singletonList("makeText"); //$NON-NLS-1$ 71 | } 72 | 73 | @Override 74 | public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor, 75 | @NonNull MethodInvocation node) { 76 | assert node.astName().astValue().equals("makeText"); 77 | if (node.astOperand() == null) { 78 | // "makeText()" in the code with no operand 79 | return; 80 | } 81 | 82 | String operand = node.astOperand().toString(); 83 | if (!(operand.equals("Toast") || operand.endsWith(".Toast"))) { 84 | return; 85 | } 86 | 87 | // Make sure you pass the right kind of duration: it's not a delay, it's 88 | // LENGTH_SHORT or LENGTH_LONG 89 | // (see http://code.google.com/p/android/issues/detail?id=3655) 90 | StrictListAccessor args = node.astArguments(); 91 | if (args.size() == 3) { 92 | Expression duration = args.last(); 93 | if (duration instanceof IntegralLiteral) { 94 | context.report(ISSUE, duration, context.getLocation(duration), 95 | "Expected duration `Toast.LENGTH_SHORT` or `Toast.LENGTH_LONG`, a custom " + 96 | "duration value is not supported"); 97 | } 98 | } 99 | 100 | Node method = findSurroundingMethod(node.getParent()); 101 | if (method == null) { 102 | return; 103 | } 104 | 105 | ShowFinder finder = new ShowFinder(node); 106 | method.accept(finder); 107 | if (!finder.isShowCalled()) { 108 | context.report(ISSUE, node, context.getLocation(node), 109 | "Toast created but not shown: did you forget to call `show()` ?"); 110 | } 111 | } 112 | 113 | private static class ShowFinder extends ForwardingAstVisitor { 114 | /** The target makeText call */ 115 | private final MethodInvocation mTarget; 116 | /** Whether we've found the show method */ 117 | private boolean mFound; 118 | /** Whether we've seen the target makeText node yet */ 119 | private boolean mSeenTarget; 120 | 121 | private ShowFinder(MethodInvocation target) { 122 | mTarget = target; 123 | } 124 | 125 | @Override 126 | public boolean visitMethodInvocation(MethodInvocation node) { 127 | if (node == mTarget) { 128 | mSeenTarget = true; 129 | } else if ((mSeenTarget || node.astOperand() == mTarget) 130 | && "show".equals(node.astName().astValue())) { //$NON-NLS-1$ 131 | // TODO: Do more flow analysis to see whether we're really calling show 132 | // on the right type of object? 133 | mFound = true; 134 | } 135 | 136 | return true; 137 | } 138 | 139 | @Override 140 | public boolean visitReturn(Return node) { 141 | if (node.astValue() == mTarget) { 142 | // If you just do "return Toast.makeText(...) don't warn 143 | mFound = true; 144 | } 145 | return super.visitReturn(node); 146 | } 147 | 148 | boolean isShowCalled() { 149 | return mFound; 150 | } 151 | } 152 | 153 | public static Node findSurroundingMethod(Node scope) { 154 | while(true) { 155 | if(scope != null) { 156 | Class type = scope.getClass(); 157 | if(type != MethodDeclaration.class && type != ConstructorDeclaration.class && !isLambdaExpression(type)) { 158 | scope = scope.getParent(); 159 | continue; 160 | } 161 | 162 | return scope; 163 | } 164 | 165 | return null; 166 | } 167 | } 168 | private static boolean isLambdaExpression(Class type) { 169 | return "lombok.ast.LambdaExpression".equals(type.getName()); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/java/com/meituan/android/lint/core/HashMapForJDK7DetectorTest.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.tools.lint.detector.api.Detector; 4 | import com.meituan.android.lint.core.util.MTLintDetectorTest; 5 | 6 | public class HashMapForJDK7DetectorTest extends MTLintDetectorTest { 7 | @Override 8 | protected Detector getDetector() { 9 | return new HashMapForJDK7Detector(); 10 | } 11 | 12 | public void testCase1() throws Exception { 13 | String file = "hashmap/HashMapCase1.java"; 14 | assertResultWarning(lintFiles(file), 4); 15 | } 16 | public void testCase2() throws Exception { 17 | String file = "hashmap/HashMapCase2.java"; 18 | assertResultWarning(lintFiles(file), 4); 19 | } 20 | public void testCase3() throws Exception { 21 | String file = "hashmap/HashMapCase3.java"; 22 | assertResultWarning(lintFiles(file), 2); 23 | } 24 | 25 | public void testCase4() throws Exception { 26 | String file4_1 = "hashmap/HashMapCase4_1.java"; 27 | String file4_2 = "hashmap/HashMapCase4_2.java"; 28 | assertResultWarning(lintFiles(file4_1, file4_2), 4); 29 | } 30 | public void testCase5() throws Exception { 31 | String file = "hashmap/HashMapCase5.java"; 32 | assertResultWarning(lintFiles(file), 1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/java/com/meituan/android/lint/core/LogDetectorTest.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.tools.lint.detector.api.Detector; 4 | import com.meituan.android.lint.core.util.MTLintDetectorTest; 5 | 6 | public class LogDetectorTest extends MTLintDetectorTest { 7 | @Override 8 | protected Detector getDetector() { 9 | return new LogDetector(); 10 | } 11 | 12 | public void testCase1() throws Exception { 13 | String file1 = "log/fake/Log.java"; 14 | String file2 = "log/LogCase1.java"; 15 | assertResultError(lintFiles(file1, file2), 1); 16 | } 17 | public void testCase2() throws Exception { 18 | String file1 = "log/fake/System.java"; 19 | String file2 = "log/fake/PrintStream.java"; 20 | String file3 = "log/LogCase2.java"; 21 | assertResultError(lintFiles(file1, file2, file3), 1); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/java/com/meituan/android/lint/core/MTIssueRegistryTest.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core; 2 | 3 | import com.android.tools.lint.detector.api.Issue; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | import java.util.List; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | 12 | public class MTIssueRegistryTest { 13 | 14 | private MTIssueRegistry mtIssueRegistry; 15 | 16 | /** 17 | * Setup for the other test methods 18 | */ 19 | @Before 20 | public void setUp() throws Exception { 21 | mtIssueRegistry = new MTIssueRegistry(); 22 | } 23 | 24 | /** 25 | * Test that the Issue Registry contains the correct number of Issues 26 | */ 27 | @Test 28 | public void testNumberOfIssues() throws Exception { 29 | int size = mtIssueRegistry.getIssues().size(); 30 | assertThat(size).isEqualTo(2); 31 | } 32 | 33 | /** 34 | * Test that the Issue Registry contains the correct Issues 35 | */ 36 | @Test 37 | public void testGetIssues() throws Exception { 38 | List actual = mtIssueRegistry.getIssues(); 39 | assertThat(actual).contains(LogDetector.ISSUE); 40 | assertThat(actual).contains(HashMapForJDK7Detector.ISSUE); 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/java/com/meituan/android/lint/core/util/MTLintDetectorTest.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.core.util; 2 | 3 | import com.android.tools.lint.checks.infrastructure.LintDetectorTest; 4 | import com.android.tools.lint.detector.api.Detector; 5 | import com.android.tools.lint.detector.api.Issue; 6 | import com.android.utils.SdkUtils; 7 | import com.meituan.android.lint.core.MTIssueRegistry; 8 | 9 | import java.io.BufferedInputStream; 10 | import java.io.File; 11 | import java.io.FileInputStream; 12 | import java.io.FileNotFoundException; 13 | import java.io.InputStream; 14 | import java.net.MalformedURLException; 15 | import java.net.URL; 16 | import java.security.CodeSource; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | import static org.assertj.core.api.Assertions.assertThat; 21 | 22 | /** 23 | * Created by chentong on 12/8/16. 24 | */ 25 | public abstract class MTLintDetectorTest extends LintDetectorTest{ 26 | 27 | private static final String PATH_TEST_RESOURCES = "/src/test/resources/"; 28 | 29 | @Override 30 | protected List getIssues() { 31 | List issues = new ArrayList(); 32 | Class detectorClass = getDetectorInstance().getClass(); 33 | // Get the list of issues from the registry and filter out others, to make sure 34 | // issues are properly registered 35 | List candidates = new MTIssueRegistry().getIssues(); 36 | for (Issue issue : candidates) { 37 | if (issue.getImplementation().getDetectorClass() == detectorClass) { 38 | issues.add(issue); 39 | } 40 | } 41 | return issues; 42 | } 43 | 44 | @Override 45 | protected InputStream getTestResource(String relativePath, boolean expectExists) { 46 | String path = (PATH_TEST_RESOURCES + relativePath).replace('/', File.separatorChar); 47 | File file = new File(getTestDataRootDir(), path); 48 | if (file.exists()) { 49 | try { 50 | return new BufferedInputStream(new FileInputStream(file)); 51 | } catch (FileNotFoundException e) { 52 | if (expectExists) { 53 | fail("Could not find file " + relativePath); 54 | } 55 | } 56 | } 57 | return null; 58 | } 59 | 60 | private File getTestDataRootDir() { 61 | CodeSource source = getClass().getProtectionDomain().getCodeSource(); 62 | if (source != null) { 63 | URL location = source.getLocation(); 64 | try { 65 | File classesDir = SdkUtils.urlToFile(location); 66 | return classesDir.getParentFile().getAbsoluteFile().getParentFile().getParentFile(); 67 | } catch (MalformedURLException e) { 68 | fail(e.getLocalizedMessage()); 69 | } 70 | } 71 | return null; 72 | } 73 | 74 | @Override 75 | protected boolean allowCompilationErrors() { 76 | return true; 77 | } 78 | 79 | public void assertResultWarning(String actual, int warningsCount) { 80 | assertResult(actual, 0, warningsCount); 81 | } 82 | 83 | public void assertResultError(String actual, int errorsCount) { 84 | assertResult(actual, errorsCount, 0); 85 | } 86 | public void assertResultNoWarnings(String actual) { 87 | assertThat(actual).isEqualTo("No warnings."); 88 | } 89 | public void assertResult(String actual, int errorsCount, int warningsCount) { 90 | assertThat(actual).contains(errorsCount + " errors, " + warningsCount + " warnings"); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/hashmap/HashMapCase1.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import java.util.HashMap; 4 | 5 | /** 6 | * JDK7 泛型新写法 7 | * 变量定义为HashMap = new HashMap<>() 8 | * 变量初始化与变量声明在一起 9 | * case包含: 10 | * 局部变量 11 | * 成员变量 12 | * 静态变量 13 | * Created by chentong on 13/8/16. 14 | */ 15 | public class HashMapCase1 { 16 | // 1 17 | private HashMap map1 = new HashMap<>(); 18 | // 2 19 | private final HashMap map2 = new HashMap<>(); 20 | // 3 21 | private static final HashMap map3 = new HashMap<>(); 22 | public void test() { 23 | // 4 24 | HashMap map = new HashMap<>(); 25 | map.put(1, "name"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/hashmap/HashMapCase2.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * JDK7 泛型新写法 8 | * 变量定义为Map = new HashMap<>() 9 | * 变量初始化与变量声明在一起 10 | * case包含: 11 | * 局部变量 12 | * 成员变量 13 | * 静态变量 14 | * Created by chentong on 13/8/16. 15 | */ 16 | public class HashMapCase2 { 17 | // 1 18 | private Map map1 = new HashMap<>(); 19 | // 2 20 | private static Map map2 = new HashMap<>(); 21 | // 3 22 | private static final Map map3 = new HashMap<>(); 23 | public void test() { 24 | // 4 25 | Map map = new HashMap<>(); 26 | map.put(1, "name"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/hashmap/HashMapCase3.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * JDK7 泛型新写法 8 | * 变量定义为HashMap 9 | * 变量初始化与变量声明不在一起,但在同一个类中 10 | * case包含: 11 | * 成员变量 12 | * 静态变量 13 | * Created by chentong on 13/8/16. 14 | */ 15 | public class HashMapCase3 { 16 | public Map map; 17 | public static Map map2; 18 | public void test() { 19 | // 1 20 | map = new HashMap<>(); 21 | map.put(1, "name"); 22 | // 2 23 | map2 = new HashMap<>(); 24 | map2.put(1, "name");HashMapCase3.java 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/hashmap/HashMapCase4_1.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * JDK7 泛型新写法 8 | * 变量定义为HashMap 9 | * 变量初始化与变量声明不在一起,且变量声明在另一个类中。 此为声明类 10 | * case包含: 11 | * 成员变量 12 | * 静态变量 13 | * Created by chentong on 13/8/16. 14 | */ 15 | public class HashMapCase4_1 { 16 | public Map map; 17 | public static Map map2; 18 | } 19 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/hashmap/HashMapCase4_2.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * JDK7 泛型新写法 8 | * 变量定义为HashMap 9 | * 变量初始化与变量声明不在一起,且变量声明在另一个类中。 此为使用类 10 | * case包含: 11 | * 成员变量 12 | * 静态变量 13 | * Created by chentong on 13/8/16. 14 | */ 15 | public class HashMapCase4_2 { 16 | public void test() { 17 | // 1 18 | HashMapCase4_1.map2 = new HashMap<>(); 19 | HashMapCase4_1.map2.put(1, "name"); 20 | // 2 21 | HashMapCase4_1 case4_1 = new HashMapCase4_1(); 22 | case4_1.map = new HashMap<>(); 23 | case4_1.map.put(1, "name"); 24 | // 3 25 | Sub.map2 = new HashMap<>(); 26 | // 4 27 | Sub sub = new Sub(); 28 | sub.map = new HashMap<>(); 29 | } 30 | private static class Sub { 31 | public Map map; 32 | public static Map map2; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/hashmap/HashMapCase5.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * JDK7 泛型新写法 8 | * 变量定义为HashMap 9 | * 变量初始化与变量声明不在一起,传参 10 | * case包含: 11 | * 成员变量 12 | * 静态变量 13 | * Created by chentong on 13/8/16. 14 | */ 15 | public class HashMapCase5 { 16 | public Map map3; 17 | public static Map map2; 18 | 19 | public void test() { 20 | Map map1 = null; 21 | 22 | test1(map1); 23 | test2(map1); 24 | 25 | test1(map2); 26 | test2(map2); 27 | 28 | test1(map3); 29 | test2(map3); 30 | 31 | 32 | } 33 | // 仅会检查到参数这一层级 所以只有下面一个错误 34 | public void test1(Map map) { 35 | //1 36 | map = new HashMap<>(); 37 | map.put(1, "name"); 38 | } 39 | // 这种情况经测试确实不行 似乎现实意义也不大 40 | public void test2(Map map) { 41 | map = new HashMap<>(); 42 | map.put(1, "name"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/log/LogCase1.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Created by chentong on 16/8/16. 7 | */ 8 | public class LogCase1 { 9 | public void test() { 10 | Log.i("tag", "dddddd"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/log/LogCase2.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint.demo; 2 | 3 | /** 4 | * Created by chentong on 16/8/16. 5 | */ 6 | public class LogCase2 { 7 | public void test() { 8 | System.out.println("dafdsf"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/log/fake/Log.java: -------------------------------------------------------------------------------- 1 | package android.util; 2 | 3 | /** 4 | * Created by chentong on 16/8/16. 5 | */ 6 | public class Log { 7 | public static int i(String tag, String msg) { 8 | return println(LOG_ID_MAIN, INFO, tag, msg); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/log/fake/PrintStream.java: -------------------------------------------------------------------------------- 1 | package java.io; 2 | 3 | /** 4 | * Created by chentong on 16/8/16. 5 | */ 6 | public class PrintStream { 7 | public synchronized void println(String str) { 8 | print(str); 9 | newline(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /aar/lintCoreLibrary/src/test/resources/log/fake/System.java: -------------------------------------------------------------------------------- 1 | package java.lang; 2 | 3 | import java.io.PrintStream; 4 | 5 | /** 6 | * Created by chentong on 16/8/16. 7 | */ 8 | public class System { 9 | public static final PrintStream out; 10 | } 11 | -------------------------------------------------------------------------------- /aar/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lintCoreLibrary', ':lint' -------------------------------------------------------------------------------- /check_custom_lint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # coding=utf-8 3 | 4 | import argparse 5 | import codecs 6 | import os 7 | import re 8 | 9 | # 脚本功能说明: 10 | # 1. 自动添加plugin repositories & classpath & apply 11 | # 2. 自动判断是否需要使用retrolambda ast并进行添加 12 | # 3. 执行完成后reset改动,保证不影响后续检查 13 | 14 | __author__ = 'chentong' 15 | 16 | 17 | def handle_gradle_buildscript(gradle_path, use_retrolambda_ast): 18 | gradle_backup_path = os.path.join(os.path.dirname(gradle_path), "build_backup.gradle") 19 | os.rename(gradle_path, gradle_backup_path) 20 | 21 | buildscript_begin = False 22 | buildscript_dependencies_begin = False 23 | 24 | plugin_classpath_write = False 25 | plugin_classpath = "\t\tclasspath 'com.czt.mtlint:plugin:latest.integration'\n" 26 | 27 | retrolambda_ast_write = False 28 | retrolambda_ast_classpath = "\t\tclasspath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2'\n" 29 | 30 | exclude_lint_ast_write = False 31 | exclude_lint_ast = "\tconfigurations.classpath.exclude group: 'com.android.tools.external.lombok'\n" 32 | 33 | no_dynamic_cache_write = False 34 | no_dynamic_cache_line = "\t\tresolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'\n" 35 | no_dynamic_cache_all = "\tconfigurations.all {\n\t\tresolutionStrategy.cacheDynamicVersionsFor 0, 'seconds'\n\t}\n" 36 | 37 | 38 | with codecs.open(gradle_backup_path, 'r', 'utf-8') as f_old, codecs.open(gradle_path, 'w', 'utf-8') as f_new: 39 | for line in f_old: 40 | 41 | if line.startswith("buildscript {"): 42 | buildscript_begin = True 43 | 44 | if buildscript_begin: 45 | 46 | if "dependencies {" in line: 47 | # buildscipt中的dependencies 48 | buildscript_dependencies_begin = True 49 | 50 | if buildscript_dependencies_begin: 51 | if "com.czt.mtlint:plugin:" in line: 52 | # 如果应经使用了plugin,强制写入新版本 53 | f_new.write(plugin_classpath) 54 | plugin_classpath_write = True 55 | continue 56 | 57 | if "me.tatarka.retrolambda.projectlombok:lombok.ast:" in line: 58 | # 如果应经使用了retrolambda ast,强制写入新版本 59 | f_new.write(retrolambda_ast_classpath) 60 | retrolambda_ast_write = True 61 | continue 62 | 63 | if "}" in line: 64 | buildscript_dependencies_begin = False 65 | if not plugin_classpath_write: 66 | # 没有使用plugin classpath 67 | f_new.write(plugin_classpath) 68 | if (not retrolambda_ast_write) and use_retrolambda_ast: 69 | # 没有使用retrolambda ast 70 | f_new.write(retrolambda_ast_classpath) 71 | 72 | if "configurations.classpath.exclude group: 'com.android.tools.external.lombok'" in line: 73 | exclude_lint_ast_write = True 74 | 75 | if "resolutionStrategy.cacheDynamicVersionsFor" in line: 76 | # 强制更新动态版本缓存策略 77 | f_new.write(no_dynamic_cache_line) 78 | no_dynamic_cache_write = True 79 | continue 80 | 81 | 82 | if line.startswith("}"): 83 | buildscript_begin = False 84 | if (not exclude_lint_ast_write) and use_retrolambda_ast: 85 | f_new.write(exclude_lint_ast) 86 | if not no_dynamic_cache_write: 87 | f_new.write(no_dynamic_cache_all) 88 | 89 | f_new.write(line) 90 | 91 | os.remove(gradle_backup_path) 92 | 93 | 94 | def handle_module_build_gradle(src, use_retrolambda_ast): 95 | apply_plugin_text = "\napply plugin: 'MTLintPlugin'" 96 | module_build_gradle = os.path.join(src, "build.gradle") 97 | # 插入lint插件 98 | not_apply = True 99 | if apply_plugin_text in open(module_build_gradle).read(): 100 | not_apply = False 101 | if not_apply: 102 | with open(module_build_gradle, "a") as newFile: 103 | newFile.write(apply_plugin_text) 104 | 105 | # 处理module里的buildscript情况 106 | handle_gradle_buildscript(module_build_gradle, use_retrolambda_ast) 107 | 108 | 109 | 110 | 111 | def custom_lint(src): 112 | project_build_gradle = os.path.join(os.path.dirname(src), "build.gradle") 113 | 114 | use_retrolambda = False 115 | plugin_version_int = -1 116 | 117 | for line in open(project_build_gradle): 118 | if "com.android.tools.build:gradle:" in line: 119 | classpath_build = line.strip().split(" ")[-1][1:-1] 120 | plugin_version = classpath_build.split(":")[-1].split("-")[0] # - 排除1.4.0-beta1 1.3.0-SNAPSHOT这种情况,直接看版本号 121 | plugin_version_int = int(plugin_version.replace(".", "")) 122 | if plugin_version_int < 130: 123 | # 1.3.0以下版本不支持使用插件, 需要自己手动配置 124 | return 125 | if "me.tatarka:gradle-retrolambda:" in line: 126 | use_retrolambda = True 127 | 128 | # 1.5.0以上,使用retrolambda需要使用retrolambda出的ast,否则lint出错 129 | use_retrolambda_ast = False 130 | if use_retrolambda and (plugin_version_int >= 150): 131 | use_retrolambda_ast = True 132 | 133 | handle_gradle_buildscript(project_build_gradle, use_retrolambda_ast) 134 | handle_module_build_gradle(src, use_retrolambda_ast) 135 | 136 | os.chdir(src) 137 | code = os.system('../gradlew lintForArchon --stacktrace') 138 | 139 | # gradlew执行语法有问题 140 | if code == 512: 141 | exit(1) 142 | # gradlew不存在 143 | if code == 32512: 144 | exit(1) 145 | 146 | os.system('git reset --hard') 147 | 148 | # 报错 149 | if code == 256: 150 | # 没有文件生成 151 | project_lint_html = os.path.join(src, "lint-report/lint-report.html") 152 | if not os.path.exists(project_lint_html): 153 | exit(1) 154 | 155 | 156 | def main(): 157 | parser = argparse.ArgumentParser() 158 | parser.add_argument('-s', help='Module Source Folder, such as aar/app, ==> ./check_custom_lint.py -s aar/app') 159 | args = parser.parse_args() 160 | src = os.path.abspath(args.s) 161 | 162 | custom_lint(src) 163 | 164 | 165 | if __name__ == '__main__': 166 | main() 167 | -------------------------------------------------------------------------------- /plugin/README.md: -------------------------------------------------------------------------------- 1 | # 集成 2 | 3 | 仅支持gradle plugin 1.3.0 以上 4 | 5 | ``` groovy 6 | buildscript { 7 | repositories { 8 | jcenter() 9 | } 10 | dependencies { 11 | classpath 'com.czt.mtlint:plugin:latest.release' 12 | } 13 | } 14 | ``` 15 | 16 | ``` groovy 17 | apply plugin: 'MTLintPlugin' 18 | ``` 19 | 20 | # 功能介绍 21 | 22 | - 集成了原生lint和[自定义lint](https://github.com/GavinCT/MeituanCustomLintDemo/tree/master/aar)的所有检查规则 23 | - 内置lintOptions (所有warning视为error,只输出htmlReport到`${project.projectDir}/lint-report/lint-report.html`)和 lint.xml。集成此插件后,原有配置会被覆盖,没有配置的也会执行插件中的配置 24 | - 根据retrolambda版本判断是否在lint.xml加入try-with-resource警告屏蔽(1.8.0版以上开始支持try-with-resource) 25 | 26 | 注: plugin新增一个task:lintForArchon,会选择一个variantName(取遍历结果的第一个)。 27 | 28 | # lint.xml配置 29 | 30 | plugin/src/main/resources/config/lint.xml 31 | 32 | retrolambda_lint.xml是针对retrolambda的特殊配置,识别到开发者使用retrolambda之后会将该规则与lint.xml合并。 33 | 同样,有其他特殊情况,也可参照此例进行添加。 34 | 35 | # 注意 36 | 37 | ## 项目中使用了retrolambda 38 | 39 | 如果项目中使用了gradle plugin 1.5 和 retrolambda,请注意配置替换原有的抽象语法树(ast),避免lint因不识别lambda表达式而报错 40 | 41 | ``` groovy 42 | buildscript { 43 | repositories { 44 | jcenter() 45 | } 46 | 47 | dependencies { 48 | classpath 'com.android.tools.build:gradle:' 49 | classpath "me.tatarka:gradle-retrolambda:" 50 | classpath 'me.tatarka.retrolambda.projectlombok:lombok.ast:0.2.3.a2' 51 | } 52 | 53 | // Exclude the version that the android plugin depends on. 54 | configurations.classpath.exclude group: 'com.android.tools.external.lombok' 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | 3 | repositories { 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | compile gradleApi() 9 | compile localGroovy() 10 | compile 'com.android.tools.build:gradle:1.3.0' 11 | } 12 | 13 | 14 | buildscript { 15 | repositories { 16 | jcenter() 17 | } 18 | dependencies { 19 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6' 20 | // NOTE: Do not place your application dependencies here; they belong 21 | // in the individual module build.gradle files 22 | } 23 | } 24 | 25 | apply plugin: 'maven-publish' 26 | apply plugin: 'com.jfrog.bintray' 27 | 28 | 29 | def siteUrl = 'https://github.com/GavinCT/MeituanCustomLintDemo' // 项目的主页 30 | def gitUrl = 'https://github.com/GavinCT/MeituanCustomLintDemo.git' // Git仓库的url 31 | 32 | task sourceJar(type: Jar) { 33 | from sourceSets.main.allSource 34 | classifier 'sources' 35 | } 36 | task docJar(type: Jar, dependsOn: javadoc) { 37 | from tasks.javadoc.destinationDir 38 | classifier 'doc' 39 | } 40 | 41 | group = 'com.czt.mtlint' 42 | version = '1.0.2' 43 | 44 | publishing { 45 | publications { 46 | MyPublication(MavenPublication) { 47 | from components.java 48 | groupId group 49 | artifactId 'plugin' 50 | version version 51 | artifact sourceJar 52 | artifact docJar 53 | } 54 | } 55 | } 56 | 57 | Properties properties = new Properties() 58 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 59 | bintray { 60 | user = properties.getProperty("bintray.user") 61 | key = properties.getProperty("bintray.apikey") 62 | publications = ['MyPublication'] 63 | pkg { 64 | repo = "maven" 65 | name = "MeituanCustomLintDemoPlugin" //发布到JCenter上的项目名字 66 | websiteUrl = siteUrl 67 | vcsUrl = gitUrl 68 | licenses = ["Apache-2.0"] 69 | publish = true 70 | } 71 | } -------------------------------------------------------------------------------- /plugin/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /plugin/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GavinCT/MeituanLintDemo/fa80c6dc38657f1ab4e492d18d749fa6fcad51e0/plugin/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /plugin/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Nov 23 17:17:06 CST 2015 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-2.8-all.zip 7 | -------------------------------------------------------------------------------- /plugin/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 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 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /plugin/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /plugin/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * This settings file was auto generated by the Gradle buildInit task 3 | * by 'chentong' at '11/23/15 4:55 PM' with Gradle 2.2.1 4 | * 5 | * The settings file is used to specify which projects to include in your build. 6 | * In a single project build this file can be empty or even removed. 7 | * 8 | * Detailed information about configuring a multi-project build in Gradle can be found 9 | * in the user guide at http://gradle.org/docs/2.2.1/userguide/multi_project_builds.html 10 | */ 11 | 12 | /* 13 | // To declare projects as part of a multi-project build use the 'include' method 14 | include 'shared' 15 | include 'api' 16 | include 'services:webservice' 17 | */ 18 | 19 | rootProject.name = 'plugin' 20 | -------------------------------------------------------------------------------- /plugin/src/main/groovy/com/meituan/android/lint/MTLintPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint 2 | 3 | import com.android.build.gradle.AppPlugin 4 | import com.android.build.gradle.LibraryPlugin 5 | import com.android.build.gradle.api.BaseVariant 6 | import com.android.build.gradle.internal.dsl.LintOptions 7 | import com.android.build.gradle.tasks.Lint 8 | import org.gradle.api.* 9 | import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency 10 | import org.gradle.api.tasks.TaskState 11 | 12 | class MTLintPlugin implements Plugin { 13 | 14 | @Override 15 | void apply(Project project) { 16 | applyTask(project, getAndroidVariants(project)) 17 | } 18 | 19 | private static final String sPluginMisConfiguredErrorMessage = "Plugin requires the 'android' or 'android-library' plugin to be configured."; 20 | /** 21 | * get android variant list of the project 22 | * @param project the compiling project 23 | * @return android variants 24 | */ 25 | private static DomainObjectCollection getAndroidVariants(Project project) { 26 | 27 | if (project.getPlugins().hasPlugin(AppPlugin)) { 28 | return project.getPlugins().getPlugin(AppPlugin).extension.applicationVariants 29 | } 30 | 31 | if (project.getPlugins().hasPlugin(LibraryPlugin)) { 32 | return project.getPlugins().getPlugin(LibraryPlugin).extension.libraryVariants 33 | } 34 | 35 | throw new ProjectConfigurationException(sPluginMisConfiguredErrorMessage, null) 36 | } 37 | 38 | private void applyTask(Project project, DomainObjectCollection variants) { 39 | project.dependencies { 40 | 41 | if(project.getPlugins().hasPlugin('com.android.application')){ 42 | compile('com.meituan.android.lint:lint:latest.release') { 43 | force = true; 44 | } 45 | } else { 46 | provided('com.meituan.android.lint:lint:latest.release') { 47 | force = true; 48 | } 49 | } 50 | 51 | } 52 | project.configurations.all { 53 | resolutionStrategy.cacheDynamicVersionsFor 0, 'seconds' 54 | } 55 | 56 | def archonTaskExists = false 57 | 58 | variants.all { variant -> 59 | def variantName = variant.name.capitalize() 60 | Lint lintTask = project.tasks.getByName("lint" + variantName) as Lint 61 | 62 | //Lint 会把project下的lint.xml和lintConfig指定的lint.xml进行合并,为了确保只执行插件中的规则,采取此策略 63 | File lintFile = project.file("lint.xml") 64 | File lintOldFile = null 65 | 66 | /* 67 | lintOptions { 68 | lintConfig file("lint.xml") 69 | warningsAsErrors true 70 | abortOnError true 71 | htmlReport true 72 | htmlOutput file("lint-report/lint-report.html") 73 | xmlReport false 74 | } 75 | */ 76 | 77 | def newOptions = new LintOptions() 78 | newOptions.lintConfig = lintFile 79 | newOptions.warningsAsErrors = true 80 | newOptions.abortOnError = true 81 | newOptions.htmlReport = true 82 | //不放在build下,防止被clean掉 83 | newOptions.htmlOutput = project.file("${project.projectDir}/lint-report/lint-report.html") 84 | newOptions.xmlReport = false 85 | 86 | lintTask.lintOptions = newOptions 87 | 88 | lintTask.doFirst { 89 | 90 | if (lintFile.exists()) { 91 | lintOldFile = project.file("lintOld.xml") 92 | lintFile.renameTo(lintOldFile) 93 | } 94 | def isLintXmlReady = copyLintXml(project, lintFile) 95 | 96 | if (!isLintXmlReady) { 97 | if (lintOldFile != null) { 98 | lintOldFile.renameTo(lintFile) 99 | } 100 | throw new GradleException("lint.xml不存在") 101 | } 102 | 103 | } 104 | 105 | project.gradle.taskGraph.afterTask { task, TaskState state -> 106 | if (task == lintTask) { 107 | lintFile.delete() 108 | if (lintOldFile != null) { 109 | lintOldFile.renameTo(lintFile) 110 | } 111 | } 112 | } 113 | 114 | // For archon 115 | 116 | if (!archonTaskExists) { 117 | archonTaskExists = true 118 | project.task("lintForArchon").dependsOn lintTask 119 | } 120 | 121 | } 122 | } 123 | /** 124 | * copy lint xml 125 | * @return is lint xml ready 126 | */ 127 | boolean copyLintXml(Project project, File targetFile) { 128 | 129 | targetFile.parentFile.mkdirs() 130 | 131 | InputStream lintIns = this.class.getResourceAsStream("/config/lint.xml") 132 | OutputStream outputStream = new FileOutputStream(targetFile) 133 | 134 | int retrolambdaPluginVersion = getRetrolambdaPluginVersion(project) 135 | if (retrolambdaPluginVersion >= 180) { 136 | // 加入屏蔽try with resource 检测 1.8.0版本引入此功能 137 | InputStream retrolambdaLintIns = this.class.getResourceAsStream("/config/retrolambda_lint.xml") 138 | XMLMergeUtil.merge(outputStream, "/lint", lintIns, retrolambdaLintIns) 139 | } else { 140 | // 未使用 或 使用了不支持try with resource的版本 141 | IOUtils.copy(lintIns, outputStream) 142 | IOUtils.closeQuietly(outputStream) 143 | IOUtils.closeQuietly(lintIns) 144 | } 145 | 146 | if (targetFile.exists()) { 147 | return true 148 | } 149 | 150 | return false 151 | } 152 | /** 153 | * 获取使用的retrolambda plugin版本 154 | * @param project project 155 | * @return 没找到时返回-1 ,找到返回正常version 156 | */ 157 | def static int getRetrolambdaPluginVersion(Project project) { 158 | 159 | DefaultExternalModuleDependency retrolambdaPlugin = findClassPathDependencyVersion(project, 'me.tatarka', 'gradle-retrolambda') as DefaultExternalModuleDependency 160 | if (retrolambdaPlugin == null) { 161 | retrolambdaPlugin = findClassPathDependencyVersion(project.getRootProject(), 'me.tatarka', 'gradle-retrolambda') as DefaultExternalModuleDependency 162 | } 163 | if (retrolambdaPlugin == null) { 164 | return -1; 165 | } 166 | return retrolambdaPlugin.version.split("-")[0].replaceAll("\\.","").toInteger() 167 | } 168 | 169 | def static findClassPathDependencyVersion(Project project, group, attributeId) { 170 | return project.buildscript.configurations.classpath.dependencies.find { 171 | it.group != null && it.group.equals(group) && it.name.equals(attributeId) 172 | } 173 | } 174 | 175 | } -------------------------------------------------------------------------------- /plugin/src/main/java/com/meituan/android/lint/IOUtils.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | 7 | /** 8 | * IO Utils Apache 9 | * 10 | * Created by chentong on 29/12/15. 11 | */ 12 | public class IOUtils { 13 | public static void closeQuietly(InputStream input) { 14 | try { 15 | if(input != null) { 16 | input.close(); 17 | } 18 | } catch (IOException var2) { 19 | } 20 | 21 | } 22 | 23 | public static void closeQuietly(OutputStream output) { 24 | try { 25 | if(output != null) { 26 | output.close(); 27 | } 28 | } catch (IOException var2) { 29 | } 30 | 31 | } 32 | public static int copy(InputStream input, OutputStream output) throws IOException { 33 | long count = copyLarge(input, output); 34 | return count > 2147483647L?-1:(int)count; 35 | } 36 | 37 | private static long copyLarge(InputStream input, OutputStream output) throws IOException { 38 | byte[] buffer = new byte[4096]; 39 | long count = 0L; 40 | 41 | int n1; 42 | for(boolean n = false; -1 != (n1 = input.read(buffer)); count += (long)n1) { 43 | output.write(buffer, 0, n1); 44 | } 45 | 46 | return count; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /plugin/src/main/java/com/meituan/android/lint/XMLMergeUtil.java: -------------------------------------------------------------------------------- 1 | package com.meituan.android.lint; 2 | 3 | 4 | import org.w3c.dom.Document; 5 | import org.w3c.dom.Node; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.OutputStream; 10 | 11 | import javax.xml.parsers.DocumentBuilder; 12 | import javax.xml.parsers.DocumentBuilderFactory; 13 | import javax.xml.transform.Result; 14 | import javax.xml.transform.Transformer; 15 | import javax.xml.transform.TransformerFactory; 16 | import javax.xml.transform.dom.DOMSource; 17 | import javax.xml.transform.stream.StreamResult; 18 | import javax.xml.xpath.XPath; 19 | import javax.xml.xpath.XPathConstants; 20 | import javax.xml.xpath.XPathExpression; 21 | import javax.xml.xpath.XPathFactory; 22 | 23 | /** 24 | * Created by chentong on 27/11/15. 25 | */ 26 | public class XMLMergeUtil { 27 | 28 | public static void merge(OutputStream outputStream, String expression, 29 | InputStream... inputStreams) throws Exception { 30 | XPathFactory xPathFactory = XPathFactory.newInstance(); 31 | XPath xpath = xPathFactory.newXPath(); 32 | XPathExpression compiledExpression = xpath 33 | .compile(expression); 34 | Document doc = merge(compiledExpression, inputStreams); 35 | 36 | print(doc, outputStream); 37 | 38 | for (InputStream inputStream : inputStreams) { 39 | IOUtils.closeQuietly(inputStream); 40 | } 41 | IOUtils.closeQuietly(outputStream); 42 | } 43 | 44 | public static Document merge(String expression, 45 | InputStream... inputStreams) throws Exception { 46 | XPathFactory xPathFactory = XPathFactory.newInstance(); 47 | XPath xpath = xPathFactory.newXPath(); 48 | XPathExpression compiledExpression = xpath 49 | .compile(expression); 50 | return merge(compiledExpression, inputStreams); 51 | } 52 | 53 | private static Document merge(XPathExpression expression, 54 | InputStream... inputStreams) throws Exception { 55 | DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory 56 | .newInstance(); 57 | docBuilderFactory 58 | .setIgnoringElementContentWhitespace(true); 59 | DocumentBuilder docBuilder = docBuilderFactory 60 | .newDocumentBuilder(); 61 | Document base = docBuilder.parse(inputStreams[0]); 62 | 63 | Node results = (Node) expression.evaluate(base, 64 | XPathConstants.NODE); 65 | if (results == null) { 66 | throw new IOException(inputStreams[0] 67 | + ": expression does not evaluate to node"); 68 | } 69 | 70 | for (int i = 1; i < inputStreams.length; i++) { 71 | Document merge = docBuilder.parse(inputStreams[i]); 72 | Node nextResults = (Node) expression.evaluate(merge, 73 | XPathConstants.NODE); 74 | while (nextResults.hasChildNodes()) { 75 | Node kid = nextResults.getFirstChild(); 76 | nextResults.removeChild(kid); 77 | kid = base.importNode(kid, true); 78 | results.appendChild(kid); 79 | } 80 | } 81 | 82 | return base; 83 | } 84 | 85 | private static void print(Document doc, OutputStream targetFile) throws Exception { 86 | TransformerFactory transformerFactory = TransformerFactory 87 | .newInstance(); 88 | Transformer transformer = transformerFactory 89 | .newTransformer(); 90 | DOMSource source = new DOMSource(doc); 91 | Result result = new StreamResult(targetFile); 92 | transformer.transform(source, result); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /plugin/src/main/resources/META-INF/gradle-plugins/MTLintPlugin.properties: -------------------------------------------------------------------------------- 1 | implementation-class = com.meituan.android.lint.MTLintPlugin 2 | -------------------------------------------------------------------------------- /plugin/src/main/resources/config/lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /plugin/src/main/resources/config/retrolambda_lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------