• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tools.lint.checks.infrastructure
18 
19 import com.android.SdkConstants.DOT_JAVA
20 import com.android.SdkConstants.DOT_KT
21 import java.util.regex.Pattern
22 
23 // Copy in metalava from lint to avoid compilation dependency directly on lint-tests
24 
25 /**
26  * A pair of package name and class name inferred from Java or Kotlin
27  * source code. The [source] is the source code, and the [extension] is
28  * the file extension (including the leading dot) which states whether
29  * this is a Kotlin source file, a Java source file, a Groovy source
30  * file, etc.
31  */
32 class ClassName(source: String, extension: String = DOT_JAVA) {
33     val packageName: String?
34     val className: String?
35 
36     init {
37         val withoutComments = stripComments(source, extension)
38         packageName = getPackage(withoutComments)
39         className = getClassName(withoutComments)
40     }
41 
packageNameWithDefaultnull42     fun packageNameWithDefault() = packageName ?: ""
43 }
44 
45 /**
46  * Strips line and block comments from the given Java or Kotlin source
47  * file.
48  */
49 @Suppress("LocalVariableName")
50 fun stripComments(source: String, extension: String, stripLineComments: Boolean = true): String {
51     val sb = StringBuilder(source.length)
52     var state = 0
53     val INIT = 0
54     val INIT_SLASH = 1
55     val LINE_COMMENT = 2
56     val BLOCK_COMMENT = 3
57     val BLOCK_COMMENT_ASTERISK = 4
58     val BLOCK_COMMENT_SLASH = 5
59     val IN_STRING = 6
60     val IN_STRING_ESCAPE = 7
61     val IN_CHAR = 8
62     val AFTER_CHAR = 9
63     var blockCommentDepth = 0
64     for (c in source) {
65         when (state) {
66             INIT -> {
67                 when (c) {
68                     '/' -> state = INIT_SLASH
69                     '"' -> {
70                         state = IN_STRING
71                         sb.append(c)
72                     }
73                     '\'' -> {
74                         state = IN_CHAR
75                         sb.append(c)
76                     }
77                     else -> sb.append(c)
78                 }
79             }
80             INIT_SLASH -> {
81                 when {
82                     c == '*' -> { blockCommentDepth++; state = BLOCK_COMMENT }
83                     c == '/' && stripLineComments -> state = LINE_COMMENT
84                     else -> {
85                         state = INIT
86                         sb.append('/') // because we skipped it in init
87                         sb.append(c)
88                     }
89                 }
90             }
91             LINE_COMMENT -> {
92                 when (c) {
93                     '\n' -> state = INIT
94                 }
95             }
96             BLOCK_COMMENT -> {
97                 when (c) {
98                     '*' -> state = BLOCK_COMMENT_ASTERISK
99                     '/' -> state = BLOCK_COMMENT_SLASH
100                 }
101             }
102 
103             BLOCK_COMMENT_ASTERISK -> {
104                 state = when (c) {
105                     '/' -> {
106                         blockCommentDepth--
107                         if (blockCommentDepth == 0) {
108                             INIT
109                         } else {
110                             BLOCK_COMMENT
111                         }
112                     }
113                     '*' -> BLOCK_COMMENT_ASTERISK
114                     else -> BLOCK_COMMENT
115                 }
116             }
117             BLOCK_COMMENT_SLASH -> {
118                 if (c == '*' && extension == DOT_KT) {
119                     blockCommentDepth++
120                 }
121                 if (c != '/') {
122                     state = BLOCK_COMMENT
123                 }
124             }
125             IN_STRING -> {
126                 when (c) {
127                     '\\' -> state = IN_STRING_ESCAPE
128                     '"' -> state = INIT
129                 }
130                 sb.append(c)
131             }
132             IN_STRING_ESCAPE -> {
133                 sb.append(c)
134                 state = IN_STRING
135             }
136             IN_CHAR -> {
137                 if (c != '\\') {
138                     state = AFTER_CHAR
139                 }
140                 sb.append(c)
141             }
142             AFTER_CHAR -> {
143                 sb.append(c)
144                 if (c == '\\') {
145                     state = INIT
146                 }
147             }
148         }
149     }
150 
151     return sb.toString()
152 }
153 
154 private val PACKAGE_PATTERN = Pattern.compile("""package\s+([\S&&[^;]]*)""")
155 
156 private val CLASS_PATTERN = Pattern.compile(
157     """(\bclass\b|\binterface\b|\benum class\b|\benum\b|\bobject\b)+?\s*([^\s:(]+)""",
158     Pattern.MULTILINE
159 )
160 
getPackagenull161 fun getPackage(source: String): String? {
162     val matcher = PACKAGE_PATTERN.matcher(source)
163     return if (matcher.find()) {
164         matcher.group(1).trim { it <= ' ' }
165     } else {
166         null
167     }
168 }
169 
getClassNamenull170 fun getClassName(source: String): String? {
171     val matcher = CLASS_PATTERN.matcher(source.replace('\n', ' '))
172     var start = 0
173     while (matcher.find(start)) {
174         val cls = matcher.group(2)
175         val groupStart = matcher.start(1)
176 
177         // Make sure this "class" reference isn't part of an annotation on the class
178         // referencing a class literal -- Foo.class, or in Kotlin, Foo::class.java)
179         if (groupStart == 0 || source[groupStart - 1] != '.' && source[groupStart - 1] != ':') {
180             val trimmed = cls.trim { it <= ' ' }
181             val typeParameter = trimmed.indexOf('<')
182             return if (typeParameter != -1) {
183                 trimmed.substring(0, typeParameter)
184             } else {
185                 trimmed
186             }
187         }
188         start = matcher.end(2)
189     }
190 
191     return null
192 }
193