blob: d1fe43ea0c7c908bbc94eaa277b36f3fa91b7d21 [file] [log] [blame]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001#!/usr/bin/env python
2
3# Copyright (C) 2014 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the 'License');
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an 'AS IS' BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Enforces common Android public API design patterns. It ignores lint messages from
19a previous API level, if provided.
20
21Usage: apilint.py current.txt
22Usage: apilint.py current.txt previous.txt
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070023
24You can also splice in blame details like this:
25$ git blame api/current.txt -t -e > /tmp/currentblame.txt
26$ apilint.py /tmp/currentblame.txt previous.txt --no-color
Jeff Sharkey8190f4882014-08-28 12:24:07 -070027"""
28
Adrian Roos1f1b6a82019-01-05 20:09:38 +010029import re, sys, collections, traceback, argparse, itertools
Jeff Sharkey8190f4882014-08-28 12:24:07 -070030
31
32BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8)
33
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070034ALLOW_GOOGLE = False
35USE_COLOR = True
36
Jeff Sharkey8190f4882014-08-28 12:24:07 -070037def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False):
38 # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070039 if not USE_COLOR: return ""
Jeff Sharkey8190f4882014-08-28 12:24:07 -070040 codes = []
41 if reset: codes.append("0")
42 else:
43 if not fg is None: codes.append("3%d" % (fg))
44 if not bg is None:
45 if not bright: codes.append("4%d" % (bg))
46 else: codes.append("10%d" % (bg))
47 if bold: codes.append("1")
48 elif dim: codes.append("2")
49 else: codes.append("22")
50 return "\033[%sm" % (";".join(codes))
51
52
53class Field():
Adrian Roosb787c182019-01-03 18:54:33 +010054 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
Adrian Roosb787c182019-01-03 18:54:33 +010060 if sig_format == 2:
61 V2LineParser(raw).parse_into_field(self)
62 elif sig_format == 1:
63 # drop generics for now; may need multiple passes
64 raw = re.sub("<[^<]+?>", "", raw)
65 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -060066
Adrian Roosb787c182019-01-03 18:54:33 +010067 raw = raw.split()
68 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069
Adrian Roosb787c182019-01-03 18:54:33 +010070 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
71 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070072
Adrian Roosb787c182019-01-03 18:54:33 +010073 # ignore annotations for now
74 raw = [ r for r in raw if not r.startswith("@") ]
Jeff Sharkey40d67f42018-07-17 13:29:40 -060075
Adrian Roosb787c182019-01-03 18:54:33 +010076 self.typ = raw[0]
77 self.name = raw[1].strip(";")
78 if len(raw) >= 4 and raw[2] == "=":
79 self.value = raw[3].strip(';"')
80 else:
81 self.value = None
82
83 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070084
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070085 def __hash__(self):
86 return hash(self.raw)
87
Jeff Sharkey8190f4882014-08-28 12:24:07 -070088 def __repr__(self):
89 return self.raw
90
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091class Method():
Adrian Roosb787c182019-01-03 18:54:33 +010092 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070093 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070094 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070095 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070096 self.blame = blame
97
Adrian Roosb787c182019-01-03 18:54:33 +010098 if sig_format == 2:
99 V2LineParser(raw).parse_into_method(self)
100 elif sig_format == 1:
101 # drop generics for now; may need multiple passes
102 raw = re.sub("<[^<]+?>", "", raw)
103 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104
Adrian Roosb787c182019-01-03 18:54:33 +0100105 # handle each clause differently
106 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107
Adrian Roosb787c182019-01-03 18:54:33 +0100108 # parse prefixes
109 raw = re.split("[\s]+", raw_prefix)
110 for r in ["", ";"]:
111 while r in raw: raw.remove(r)
112 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700113
Adrian Roosb787c182019-01-03 18:54:33 +0100114 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
115 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116
Adrian Roosb787c182019-01-03 18:54:33 +0100117 self.typ = raw[0]
118 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600119
Adrian Roosb787c182019-01-03 18:54:33 +0100120 # parse args
121 self.args = []
122 for arg in re.split(",\s*", raw_args):
123 arg = re.split("\s", arg)
124 # ignore annotations for now
125 arg = [ a for a in arg if not a.startswith("@") ]
126 if len(arg[0]) > 0:
127 self.args.append(arg[0])
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128
Adrian Roosb787c182019-01-03 18:54:33 +0100129 # parse throws
130 self.throws = []
131 for throw in re.split(",\s*", raw_throws):
132 self.throws.append(throw)
133 else:
134 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600135
Adrian Roosb787c182019-01-03 18:54:33 +0100136 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
137
138 def sig_matches(self, typ, name, args):
139 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700140
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700141 def __hash__(self):
142 return hash(self.raw)
143
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700144 def __repr__(self):
145 return self.raw
146
147
148class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100149 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700151 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700152 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700153 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700154 self.ctors = []
155 self.fields = []
156 self.methods = []
157
Adrian Roosb787c182019-01-03 18:54:33 +0100158 if sig_format == 2:
159 V2LineParser(raw).parse_into_class(self)
160 elif sig_format == 1:
161 # drop generics for now; may need multiple passes
162 raw = re.sub("<[^<]+?>", "", raw)
163 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600164
Adrian Roosb787c182019-01-03 18:54:33 +0100165 raw = raw.split()
166 self.split = list(raw)
167 if "class" in raw:
168 self.fullname = raw[raw.index("class")+1]
169 elif "interface" in raw:
170 self.fullname = raw[raw.index("interface")+1]
171 elif "@interface" in raw:
172 self.fullname = raw[raw.index("@interface")+1]
173 else:
174 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700175
Adrian Roosb787c182019-01-03 18:54:33 +0100176 if "extends" in raw:
177 self.extends = raw[raw.index("extends")+1]
178 else:
179 self.extends = None
180
181 if "implements" in raw:
182 self.implements = raw[raw.index("implements")+1]
183 else:
184 self.implements = None
Jeff Sharkey037458a2014-09-04 15:46:20 -0700185 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100186 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700187
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700188 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800189 self.fullname_path = self.fullname.split(".")
190
Adrian Roosb787c182019-01-03 18:54:33 +0100191 if self.extends is not None:
192 self.extends_path = self.extends.split(".")
193 else:
194 self.extends_path = []
195
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700196 self.name = self.fullname[self.fullname.rindex(".")+1:]
197
Adrian Roos6eb57b02018-12-13 22:08:29 +0100198 def merge_from(self, other):
199 self.ctors.extend(other.ctors)
200 self.fields.extend(other.fields)
201 self.methods.extend(other.methods)
202
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700203 def __hash__(self):
204 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
205
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700206 def __repr__(self):
207 return self.raw
208
209
210class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700211 def __init__(self, line, raw, blame):
212 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700213 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700215
216 raw = raw.split()
217 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800218 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700219
220 def __repr__(self):
221 return self.raw
222
Adrian Roose5eeae72019-01-04 20:10:06 +0100223class V2Tokenizer(object):
224 __slots__ = ["raw"]
225
Adrian Roos5cdfb692019-01-05 22:04:55 +0100226 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100227 STRING_SPECIAL = re.compile(r'["\\]')
228
229 def __init__(self, raw):
230 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100231
Adrian Roose5eeae72019-01-04 20:10:06 +0100232 def tokenize(self):
233 tokens = []
234 current = 0
235 raw = self.raw
236 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100237
Adrian Roose5eeae72019-01-04 20:10:06 +0100238 while current < length:
239 while current < length:
240 start = current
241 match = V2Tokenizer.DELIMITER.search(raw, start)
242 if match is not None:
243 match_start = match.start()
244 if match_start == current:
245 end = match.end()
246 else:
247 end = match_start
248 else:
249 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100250
Adrian Roose5eeae72019-01-04 20:10:06 +0100251 token = raw[start:end]
252 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100253
Adrian Roose5eeae72019-01-04 20:10:06 +0100254 if token == "" or token[0] == " ":
255 continue
256 else:
257 break
Adrian Roosb787c182019-01-03 18:54:33 +0100258
Adrian Roose5eeae72019-01-04 20:10:06 +0100259 if token == "@":
260 if raw[start:start+11] == "@interface ":
261 current = start + 11
262 tokens.append("@interface")
263 continue
264 elif token == '/':
265 if raw[start:start+2] == "//":
266 current = length
267 continue
268 elif token == '"':
269 current, string_token = self.tokenize_string(raw, length, current)
270 tokens.append(token + string_token)
271 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100272
Adrian Roose5eeae72019-01-04 20:10:06 +0100273 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100274
Adrian Roose5eeae72019-01-04 20:10:06 +0100275 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100276
Adrian Roose5eeae72019-01-04 20:10:06 +0100277 def tokenize_string(self, raw, length, current):
278 start = current
279 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100280 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100281 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100282 if match:
283 if match.group() == '"':
284 end = match.end()
285 break
286 elif match.group() == '\\':
287 # ignore whatever is after the slash
288 start += 2
289 else:
290 raise ValueError("Unexpected match: `%s`" % (match.group()))
291 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100292 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100293
Adrian Roose5eeae72019-01-04 20:10:06 +0100294 token = raw[current:end]
295 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100296
Adrian Roose5eeae72019-01-04 20:10:06 +0100297class V2LineParser(object):
298 __slots__ = ["tokenized", "current", "len"]
299
Adrian Roosd1e38922019-01-14 15:44:15 +0100300 MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split())
Adrian Roosb787c182019-01-03 18:54:33 +0100301 JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
302
303 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100304 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100305 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100306 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100307
308 def parse_into_method(self, method):
309 method.split = []
310 kind = self.parse_one_of("ctor", "method")
311 method.split.append(kind)
312 annotations = self.parse_annotations()
313 method.split.extend(self.parse_modifiers())
314 self.parse_matching_paren("<", ">")
315 if "@Deprecated" in annotations:
316 method.split.append("deprecated")
317 if kind == "ctor":
318 method.typ = "ctor"
319 else:
320 method.typ = self.parse_type()
321 method.split.append(method.typ)
322 method.name = self.parse_name()
323 method.split.append(method.name)
324 self.parse_token("(")
325 method.args = self.parse_args()
326 self.parse_token(")")
327 method.throws = self.parse_throws()
328 if "@interface" in method.clazz.split:
329 self.parse_annotation_default()
330 self.parse_token(";")
331 self.parse_eof()
332
333 def parse_into_class(self, clazz):
334 clazz.split = []
335 annotations = self.parse_annotations()
336 if "@Deprecated" in annotations:
337 clazz.split.append("deprecated")
338 clazz.split.extend(self.parse_modifiers())
339 kind = self.parse_one_of("class", "interface", "@interface", "enum")
340 if kind == "enum":
341 # enums are implicitly final
342 clazz.split.append("final")
343 clazz.split.append(kind)
344 clazz.fullname = self.parse_name()
345 self.parse_matching_paren("<", ">")
346 extends = self.parse_extends()
347 clazz.extends = extends[0] if extends else None
348 implements = self.parse_implements()
349 clazz.implements = implements[0] if implements else None
350 # The checks assume that interfaces are always found in implements, which isn't true for
351 # subinterfaces.
352 if not implements and "interface" in clazz.split:
353 clazz.implements = clazz.extends
354 self.parse_token("{")
355 self.parse_eof()
356
357 def parse_into_field(self, field):
Adrian Roosd1e38922019-01-14 15:44:15 +0100358 kind = self.parse_one_of("field", "property")
Adrian Roosb787c182019-01-03 18:54:33 +0100359 field.split = [kind]
360 annotations = self.parse_annotations()
361 if "@Deprecated" in annotations:
362 field.split.append("deprecated")
363 field.split.extend(self.parse_modifiers())
364 field.typ = self.parse_type()
365 field.split.append(field.typ)
366 field.name = self.parse_name()
367 field.split.append(field.name)
368 if self.parse_if("="):
369 field.value = self.parse_value_stripped()
370 else:
371 field.value = None
372
373 self.parse_token(";")
374 self.parse_eof()
375
376 def lookahead(self):
377 return self.tokenized[self.current]
378
379 def parse_one_of(self, *options):
380 found = self.lookahead()
381 if found not in options:
382 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
383 return self.parse_token()
384
385 def parse_token(self, tok = None):
386 found = self.lookahead()
387 if tok is not None and found != tok:
388 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
389 self.current += 1
390 return found
391
392 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100393 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100394
395 def parse_eof(self):
396 if not self.eof():
397 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
398
399 def parse_if(self, tok):
400 if not self.eof() and self.lookahead() == tok:
401 self.parse_token()
402 return True
403 return False
404
405 def parse_annotations(self):
406 ret = []
407 while self.lookahead() == "@":
408 ret.append(self.parse_annotation())
409 return ret
410
411 def parse_annotation(self):
412 ret = self.parse_token("@") + self.parse_token()
413 self.parse_matching_paren("(", ")")
414 return ret
415
416 def parse_matching_paren(self, open, close):
417 start = self.current
418 if not self.parse_if(open):
419 return
420 length = len(self.tokenized)
421 count = 1
422 while count > 0:
423 if self.current == length:
424 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
425 t = self.parse_token()
426 if t == open:
427 count += 1
428 elif t == close:
429 count -= 1
430 return self.tokenized[start:self.current]
431
432 def parse_modifiers(self):
433 ret = []
434 while self.lookahead() in V2LineParser.MODIFIERS:
435 ret.append(self.parse_token())
436 return ret
437
Adrian Roos5cdfb692019-01-05 22:04:55 +0100438 def parse_kotlin_nullability(self):
439 t = self.lookahead()
440 if t == "?" or t == "!":
441 return self.parse_token()
442 return None
443
Adrian Roosb787c182019-01-03 18:54:33 +0100444 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100445 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100446 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100447 if type[-1] == '.':
448 self.parse_annotations()
449 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100450 if type in V2LineParser.JAVA_LANG_TYPES:
451 type = "java.lang." + type
452 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100453 while True:
454 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100455 if t == "@":
456 self.parse_annotation()
457 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100458 type += self.parse_token()
459 elif self.parse_kotlin_nullability() is not None:
460 pass # discard nullability for now
461 else:
462 break
Adrian Roosb787c182019-01-03 18:54:33 +0100463 return type
464
465 def parse_arg_type(self):
466 type = self.parse_type()
467 if self.parse_if("..."):
468 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100469 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100470 return type
471
472 def parse_name(self):
473 return self.parse_token()
474
475 def parse_args(self):
476 args = []
477 if self.lookahead() == ")":
478 return args
479
480 while True:
481 args.append(self.parse_arg())
482 if self.lookahead() == ")":
483 return args
484 self.parse_token(",")
485
486 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100487 self.parse_if("vararg") # kotlin vararg
Adrian Roosb787c182019-01-03 18:54:33 +0100488 self.parse_annotations()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100489 type = self.parse_arg_type()
490 l = self.lookahead()
491 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100492 if self.lookahead() != '=':
493 self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100494 if self.parse_if('='): # kotlin default value
Adrian Roosd1e38922019-01-14 15:44:15 +0100495 self.parse_expression()
Adrian Roos5cdfb692019-01-05 22:04:55 +0100496 return type
Adrian Roosb787c182019-01-03 18:54:33 +0100497
Adrian Roosd1e38922019-01-14 15:44:15 +0100498 def parse_expression(self):
499 while not self.lookahead() in [')', ',', ';']:
500 (self.parse_matching_paren('(', ')') or
501 self.parse_matching_paren('{', '}') or
502 self.parse_token())
503
Adrian Roosb787c182019-01-03 18:54:33 +0100504 def parse_throws(self):
505 ret = []
506 if self.parse_if("throws"):
507 ret.append(self.parse_type())
508 while self.parse_if(","):
509 ret.append(self.parse_type())
510 return ret
511
512 def parse_extends(self):
513 if self.parse_if("extends"):
514 return self.parse_space_delimited_type_list()
515 return []
516
517 def parse_implements(self):
518 if self.parse_if("implements"):
519 return self.parse_space_delimited_type_list()
520 return []
521
522 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
523 types = []
524 while True:
525 types.append(self.parse_type())
526 if self.lookahead() in terminals:
527 return types
528
529 def parse_annotation_default(self):
530 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100531 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100532
533 def parse_value(self):
534 if self.lookahead() == "{":
535 return " ".join(self.parse_matching_paren("{", "}"))
536 elif self.lookahead() == "(":
537 return " ".join(self.parse_matching_paren("(", ")"))
538 else:
539 return self.parse_token()
540
541 def parse_value_stripped(self):
542 value = self.parse_value()
543 if value[0] == '"':
544 return value[1:-1]
545 return value
546
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700547
Adrian Roos038a0292018-12-19 17:11:21 +0100548def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
549 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700550 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100551 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100552
553 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100554 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100555 else:
556 base_classes = []
557
Adrian Roos038a0292018-12-19 17:11:21 +0100558 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100559 if clazz_cb:
560 clazz_cb(clazz)
561 else: # In callback mode, don't keep track of the full API
562 api[clazz.fullname] = clazz
563
Adrian Roos038a0292018-12-19 17:11:21 +0100564 def handle_missed_classes_with_base(clazz):
565 for c in _yield_until_matching_class(in_classes_with_base, clazz):
566 base_class = _skip_to_matching_class(base_classes, c)
567 if base_class:
568 handle_class(base_class)
569
570 for clazz in _parse_stream_to_generator(f):
571 # Before looking at clazz, let's see if there's some classes that were not present, but
572 # may have an entry in the base stream.
573 handle_missed_classes_with_base(clazz)
574
575 base_class = _skip_to_matching_class(base_classes, clazz)
576 if base_class:
577 clazz.merge_from(base_class)
578 if out_classes_with_base is not None:
579 out_classes_with_base.append(clazz)
580 handle_class(clazz)
581
582 handle_missed_classes_with_base(None)
583
Adrian Roos6eb57b02018-12-13 22:08:29 +0100584 return api
585
586def _parse_stream_to_generator(f):
587 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700588 pkg = None
589 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700590 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100591 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700592
593 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800594 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700595 line += 1
596 raw = raw.rstrip()
597 match = re_blame.match(raw)
598 if match is not None:
599 blame = match.groups()[0:2]
600 raw = match.groups()[2]
601 else:
602 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700603
Adrian Roosb787c182019-01-03 18:54:33 +0100604 if line == 1 and raw == "// Signature format: 2.0":
605 sig_format = 2
606 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700607 pkg = Package(line, raw, blame)
608 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100609 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700610 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100611 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700612 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100613 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roosd1e38922019-01-14 15:44:15 +0100614 elif raw.startswith(" field") or raw.startswith(" property"):
Adrian Roosb787c182019-01-03 18:54:33 +0100615 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100616 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100617 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800618
Adrian Roos5ed42b62018-12-19 17:10:22 +0100619def _retry_iterator(it):
620 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
621 for e in it:
622 while True:
623 retry = yield e
624 if not retry:
625 break
626 # send() was called, asking us to redeliver clazz on next(). Still need to yield
627 # a dummy value to the send() first though.
628 if (yield "Returning clazz on next()"):
629 raise TypeError("send() must be followed by next(), not send()")
630
Adrian Roos038a0292018-12-19 17:11:21 +0100631def _skip_to_matching_class(classes, needle):
632 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700633
Adrian Roos6eb57b02018-12-13 22:08:29 +0100634 This relies on classes being sorted by package and class name."""
635
636 for clazz in classes:
637 if clazz.pkg.name < needle.pkg.name:
638 # We haven't reached the right package yet
639 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100640 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
641 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100642 continue
643 if clazz.fullname == needle.fullname:
644 return clazz
645 # We ran past the right class. Send it back into the generator, then report failure.
646 classes.send(clazz)
647 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700648
Adrian Roos038a0292018-12-19 17:11:21 +0100649def _yield_until_matching_class(classes, needle):
650 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
651
652 This relies on classes being sorted by package and class name."""
653
654 for clazz in classes:
655 if needle is None:
656 yield clazz
657 elif clazz.pkg.name < needle.pkg.name:
658 # We haven't reached the right package yet
659 yield clazz
660 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
661 # We're in the right package, but not the right class yet
662 yield clazz
663 elif clazz.fullname == needle.fullname:
664 # Class found, abort.
665 return
666 else:
667 # We ran past the right class. Send it back into the iterator, then abort.
668 classes.send(clazz)
669 return
670
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700671class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800672 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700673 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700674 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800675 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700676 self.msg = msg
677
678 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800679 self.head = "Error %s" % (rule) if rule else "Error"
680 dump = "%s%s:%s %s" % (format(fg=RED, bg=BLACK, bold=True), self.head, format(reset=True), msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700681 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800682 self.head = "Warning %s" % (rule) if rule else "Warning"
683 dump = "%s%s:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), self.head, format(reset=True), msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700684
685 self.line = clazz.line
686 blame = clazz.blame
687 if detail is not None:
688 dump += "\n in " + repr(detail)
689 self.line = detail.line
690 blame = detail.blame
691 dump += "\n in " + repr(clazz)
692 dump += "\n in " + repr(clazz.pkg)
693 dump += "\n at line " + repr(self.line)
694 if blame is not None:
695 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
696
697 self.dump = dump
698
699 def __repr__(self):
700 return self.dump
701
702
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700703failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700704
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800705def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700706 """Records an API failure to be processed later."""
707 global failures
708
Adrian Roosb787c182019-01-03 18:54:33 +0100709 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700710 sig = sig.replace(" deprecated ", " ")
711
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800712 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700713
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700714
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800715def warn(clazz, detail, rule, msg):
716 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700717
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800718def error(clazz, detail, rule, msg):
719 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700720
721
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700722noticed = {}
723
724def notice(clazz):
725 global noticed
726
727 noticed[clazz.fullname] = hash(clazz)
728
729
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700730def verify_constants(clazz):
731 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700732 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600733 if clazz.fullname.startswith("android.os.Build"): return
734 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700735
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600736 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700737 for f in clazz.fields:
738 if "static" in f.split and "final" in f.split:
739 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800740 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600741 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700742 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
743 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600744 if f.typ in req and f.value is None:
745 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700746
747
748def verify_enums(clazz):
749 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100750 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800751 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700752
753
754def verify_class_names(clazz):
755 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700756 if clazz.fullname.startswith("android.opengl"): return
757 if clazz.fullname.startswith("android.renderscript"): return
758 if re.match("android\.R\.[a-z]+", clazz.fullname): return
759
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700760 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800761 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700762 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800763 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700764 if clazz.name.endswith("Impl"):
765 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700766
767
768def verify_method_names(clazz):
769 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700770 if clazz.fullname.startswith("android.opengl"): return
771 if clazz.fullname.startswith("android.renderscript"): return
772 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700773
774 for m in clazz.methods:
775 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800776 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700777 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800778 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700779
780
781def verify_callbacks(clazz):
782 """Verify Callback classes.
783 All callback classes must be abstract.
784 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700785 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700786
787 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800788 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700789 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800790 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700791
792 if clazz.name.endswith("Callback"):
793 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800794 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700795
796 for m in clazz.methods:
797 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800798 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700799
800
801def verify_listeners(clazz):
802 """Verify Listener classes.
803 All Listener classes must be interface.
804 All methods must follow onFoo() naming style.
805 If only a single method, it must match class name:
806 interface OnFooListener { void onFoo() }"""
807
808 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100809 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800810 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700811
812 for m in clazz.methods:
813 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800814 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700815
816 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
817 m = clazz.methods[0]
818 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800819 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700820
821
822def verify_actions(clazz):
823 """Verify intent actions.
824 All action names must be named ACTION_FOO.
825 All action values must be scoped by package and match name:
826 package android.foo {
827 String ACTION_BAR = "android.foo.action.BAR";
828 }"""
829 for f in clazz.fields:
830 if f.value is None: continue
831 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700832 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600833 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700834
835 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
836 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
837 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800838 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700839 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700840 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700841 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700842 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700843 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700844 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
845 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700846 else:
847 prefix = clazz.pkg.name + ".action"
848 expected = prefix + "." + f.name[7:]
849 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700850 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700851
852
853def verify_extras(clazz):
854 """Verify intent extras.
855 All extra names must be named EXTRA_FOO.
856 All extra values must be scoped by package and match name:
857 package android.foo {
858 String EXTRA_BAR = "android.foo.extra.BAR";
859 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700860 if clazz.fullname == "android.app.Notification": return
861 if clazz.fullname == "android.appwidget.AppWidgetManager": return
862
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700863 for f in clazz.fields:
864 if f.value is None: continue
865 if f.name.startswith("ACTION_"): continue
866
867 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
868 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
869 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800870 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700871 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700872 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700873 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700874 elif clazz.pkg.name == "android.app.admin":
875 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700876 else:
877 prefix = clazz.pkg.name + ".extra"
878 expected = prefix + "." + f.name[6:]
879 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700880 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700881
882
883def verify_equals(clazz):
884 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700885 eq = False
886 hc = False
887 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100888 if "static" in m.split: continue
889 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
890 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700891 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800892 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700893
894
895def verify_parcelable(clazz):
896 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100897 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700898 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
899 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
900 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
901
902 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800903 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700904
Adrian Roosb787c182019-01-03 18:54:33 +0100905 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700906 error(clazz, None, "FW8", "Parcelable classes must be final")
907
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700908 for c in clazz.ctors:
909 if c.args == ["android.os.Parcel"]:
910 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
911
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700912
913def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800914 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700915 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600916 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700917 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800918 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700919 for f in clazz.fields:
920 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800921 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700922
923
924def verify_fields(clazz):
925 """Verify that all exposed fields are final.
926 Exposed fields must follow myName style.
927 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700928
929 IGNORE_BARE_FIELDS = [
930 "android.app.ActivityManager.RecentTaskInfo",
931 "android.app.Notification",
932 "android.content.pm.ActivityInfo",
933 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600934 "android.content.pm.ComponentInfo",
935 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700936 "android.content.pm.FeatureGroupInfo",
937 "android.content.pm.InstrumentationInfo",
938 "android.content.pm.PackageInfo",
939 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600940 "android.content.res.Configuration",
941 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700942 "android.os.Message",
943 "android.system.StructPollfd",
944 ]
945
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700946 for f in clazz.fields:
947 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700948 if clazz.fullname in IGNORE_BARE_FIELDS:
949 pass
950 elif clazz.fullname.endswith("LayoutParams"):
951 pass
952 elif clazz.fullname.startswith("android.util.Mutable"):
953 pass
954 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800955 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700956
Adrian Roosd1e38922019-01-14 15:44:15 +0100957 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700958 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800959 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700960
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700961 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800962 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700963
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700964 if re.match("[A-Z_]+", f.name):
965 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800966 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700967
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700968
969def verify_register(clazz):
970 """Verify parity of registration methods.
971 Callback objects use register/unregister methods.
972 Listener objects use add/remove methods."""
973 methods = [ m.name for m in clazz.methods ]
974 for m in clazz.methods:
975 if "Callback" in m.raw:
976 if m.name.startswith("register"):
977 other = "unregister" + m.name[8:]
978 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800979 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700980 if m.name.startswith("unregister"):
981 other = "register" + m.name[10:]
982 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800983 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700984
985 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800986 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700987
988 if "Listener" in m.raw:
989 if m.name.startswith("add"):
990 other = "remove" + m.name[3:]
991 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800992 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700993 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
994 other = "add" + m.name[6:]
995 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800996 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700997
998 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800999 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001000
1001
1002def verify_sync(clazz):
1003 """Verify synchronized methods aren't exposed."""
1004 for m in clazz.methods:
1005 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001006 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001007
1008
1009def verify_intent_builder(clazz):
1010 """Verify that Intent builders are createFooIntent() style."""
1011 if clazz.name == "Intent": return
1012
1013 for m in clazz.methods:
1014 if m.typ == "android.content.Intent":
1015 if m.name.startswith("create") and m.name.endswith("Intent"):
1016 pass
1017 else:
Adam Powell539ea122015-04-10 13:01:37 -07001018 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001019
1020
1021def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001022 """Verify that helper classes are named consistently with what they extend.
1023 All developer extendable methods should be named onFoo()."""
1024 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001025 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001026 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001027 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001028 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001029
1030 found = False
1031 for f in clazz.fields:
1032 if f.name == "SERVICE_INTERFACE":
1033 found = True
1034 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001035 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001036
Adrian Roosb787c182019-01-03 18:54:33 +01001037 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001038 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001039 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001040 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001041
1042 found = False
1043 for f in clazz.fields:
1044 if f.name == "PROVIDER_INTERFACE":
1045 found = True
1046 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001047 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001048
Adrian Roosb787c182019-01-03 18:54:33 +01001049 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001050 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001051 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001052 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001053
Adrian Roosb787c182019-01-03 18:54:33 +01001054 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001055 test_methods = True
1056 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001057 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001058
1059 if test_methods:
1060 for m in clazz.methods:
1061 if "final" in m.split: continue
1062 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001063 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001064 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001065 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001066 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001067
1068
1069def verify_builder(clazz):
1070 """Verify builder classes.
1071 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001072 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001073 if not clazz.name.endswith("Builder"): return
1074
1075 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001076 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001077
1078 has_build = False
1079 for m in clazz.methods:
1080 if m.name == "build":
1081 has_build = True
1082 continue
1083
1084 if m.name.startswith("get"): continue
1085 if m.name.startswith("clear"): continue
1086
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001087 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001088 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001089
1090 if m.name.startswith("set"):
1091 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001092 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001093
1094 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001095 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001096
1097
1098def verify_aidl(clazz):
1099 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001100 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001101 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001102
1103
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001104def verify_internal(clazz):
1105 """Catch people exposing internal classes."""
1106 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001107 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001108
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001109def layering_build_ranking(ranking_list):
1110 r = {}
1111 for rank, ps in enumerate(ranking_list):
1112 if not isinstance(ps, list):
1113 ps = [ps]
1114 for p in ps:
1115 rs = r
1116 for n in p.split('.'):
1117 if n not in rs:
1118 rs[n] = {}
1119 rs = rs[n]
1120 rs['-rank'] = rank
1121 return r
1122
1123LAYERING_PACKAGE_RANKING = layering_build_ranking([
1124 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1125 "android.app",
1126 "android.widget",
1127 "android.view",
1128 "android.animation",
1129 "android.provider",
1130 ["android.content","android.graphics.drawable"],
1131 "android.database",
1132 "android.text",
1133 "android.graphics",
1134 "android.os",
1135 "android.util"
1136])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001137
1138def verify_layering(clazz):
1139 """Catch package layering violations.
1140 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001141
1142 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001143 r = None
1144 l = LAYERING_PACKAGE_RANKING
1145 for n in p.split('.'):
1146 if n in l:
1147 l = l[n]
1148 if '-rank' in l:
1149 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001150 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001151 break
1152 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001153
1154 cr = rank(clazz.pkg.name)
1155 if cr is None: return
1156
1157 for f in clazz.fields:
1158 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001159 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001160 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001161
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001162 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001163 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001164 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001165 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001166 for arg in m.args:
1167 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001168 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001169 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001170
1171
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001172def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001173 """Verifies that boolean accessors are named correctly.
1174 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001175
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001176 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1177 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001178
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001179 gets = [ m for m in clazz.methods if is_get(m) ]
1180 sets = [ m for m in clazz.methods if is_set(m) ]
1181
1182 def error_if_exists(methods, trigger, expected, actual):
1183 for m in methods:
1184 if m.name == actual:
1185 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001186
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001187 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001188 if is_get(m):
1189 if re.match("is[A-Z]", m.name):
1190 target = m.name[2:]
1191 expected = "setIs" + target
1192 error_if_exists(sets, m.name, expected, "setHas" + target)
1193 elif re.match("has[A-Z]", m.name):
1194 target = m.name[3:]
1195 expected = "setHas" + target
1196 error_if_exists(sets, m.name, expected, "setIs" + target)
1197 error_if_exists(sets, m.name, expected, "set" + target)
1198 elif re.match("get[A-Z]", m.name):
1199 target = m.name[3:]
1200 expected = "set" + target
1201 error_if_exists(sets, m.name, expected, "setIs" + target)
1202 error_if_exists(sets, m.name, expected, "setHas" + target)
1203
1204 if is_set(m):
1205 if re.match("set[A-Z]", m.name):
1206 target = m.name[3:]
1207 expected = "get" + target
1208 error_if_exists(sets, m.name, expected, "is" + target)
1209 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001210
1211
1212def verify_collections(clazz):
1213 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001214 if clazz.fullname == "android.os.Bundle": return
1215
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001216 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1217 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1218 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001219 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001220 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001221 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001222 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001223 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001224
1225
1226def verify_flags(clazz):
1227 """Verifies that flags are non-overlapping."""
1228 known = collections.defaultdict(int)
1229 for f in clazz.fields:
1230 if "FLAG_" in f.name:
1231 try:
1232 val = int(f.value)
1233 except:
1234 continue
1235
1236 scope = f.name[0:f.name.index("FLAG_")]
1237 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001238 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001239 known[scope] |= val
1240
1241
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001242def verify_exception(clazz):
1243 """Verifies that methods don't throw generic exceptions."""
1244 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001245 for t in m.throws:
1246 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1247 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001248
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001249 if t in ["android.os.RemoteException"]:
1250 if clazz.name == "android.content.ContentProviderClient": continue
1251 if clazz.name == "android.os.Binder": continue
1252 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001253
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001254 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1255
1256 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1257 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001258
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001259GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001260
1261def verify_google(clazz):
1262 """Verifies that APIs never reference Google."""
1263
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001264 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001265 error(clazz, None, None, "Must never reference Google")
1266
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001267 for test in clazz.ctors, clazz.fields, clazz.methods:
1268 for t in test:
1269 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1270 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001271
1272
1273def verify_bitset(clazz):
1274 """Verifies that we avoid using heavy BitSet."""
1275
1276 for f in clazz.fields:
1277 if f.typ == "java.util.BitSet":
1278 error(clazz, f, None, "Field type must not be heavy BitSet")
1279
1280 for m in clazz.methods:
1281 if m.typ == "java.util.BitSet":
1282 error(clazz, m, None, "Return type must not be heavy BitSet")
1283 for arg in m.args:
1284 if arg == "java.util.BitSet":
1285 error(clazz, m, None, "Argument type must not be heavy BitSet")
1286
1287
1288def verify_manager(clazz):
1289 """Verifies that FooManager is only obtained from Context."""
1290
1291 if not clazz.name.endswith("Manager"): return
1292
1293 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001294 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001295
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001296 for m in clazz.methods:
1297 if m.typ == clazz.fullname:
1298 error(clazz, m, None, "Managers must always be obtained from Context")
1299
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001300
1301def verify_boxed(clazz):
1302 """Verifies that methods avoid boxed primitives."""
1303
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001304 boxed = ["java.lang.Number","java.lang.Byte","java.lang.Double","java.lang.Float","java.lang.Integer","java.lang.Long","java.lang.Short"]
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001305
1306 for c in clazz.ctors:
1307 for arg in c.args:
1308 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001309 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001310
1311 for f in clazz.fields:
1312 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001313 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001314
1315 for m in clazz.methods:
1316 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001317 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001318 for arg in m.args:
1319 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001320 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001321
1322
1323def verify_static_utils(clazz):
1324 """Verifies that helper classes can't be constructed."""
1325 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001326 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001327
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001328 # Only care about classes with default constructors
1329 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1330 test = []
1331 test.extend(clazz.fields)
1332 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001333
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001334 if len(test) == 0: return
1335 for t in test:
1336 if "static" not in t.split:
1337 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001338
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001339 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1340
1341
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001342def verify_overload_args(clazz):
1343 """Verifies that method overloads add new arguments at the end."""
1344 if clazz.fullname.startswith("android.opengl"): return
1345
1346 overloads = collections.defaultdict(list)
1347 for m in clazz.methods:
1348 if "deprecated" in m.split: continue
1349 overloads[m.name].append(m)
1350
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001351 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001352 if len(methods) <= 1: continue
1353
1354 # Look for arguments common across all overloads
1355 def cluster(args):
1356 count = collections.defaultdict(int)
1357 res = set()
1358 for i in range(len(args)):
1359 a = args[i]
1360 res.add("%s#%d" % (a, count[a]))
1361 count[a] += 1
1362 return res
1363
1364 common_args = cluster(methods[0].args)
1365 for m in methods:
1366 common_args = common_args & cluster(m.args)
1367
1368 if len(common_args) == 0: continue
1369
1370 # Require that all common arguments are present at start of signature
1371 locked_sig = None
1372 for m in methods:
1373 sig = m.args[0:len(common_args)]
1374 if not common_args.issubset(cluster(sig)):
1375 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1376 elif not locked_sig:
1377 locked_sig = sig
1378 elif locked_sig != sig:
1379 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1380
1381
1382def verify_callback_handlers(clazz):
1383 """Verifies that methods adding listener/callback have overload
1384 for specifying delivery thread."""
1385
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001386 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001387 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001388 "animation",
1389 "view",
1390 "graphics",
1391 "transition",
1392 "widget",
1393 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001394 ]
1395 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001396 if s in clazz.pkg.name_path: return
1397 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001398
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001399 # Ignore UI classes which assume main thread
1400 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1401 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1402 if s in clazz.fullname: return
1403 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1404 for s in ["Loader"]:
1405 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001406
1407 found = {}
1408 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001409 examine = clazz.ctors + clazz.methods
1410 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001411 if m.name.startswith("unregister"): continue
1412 if m.name.startswith("remove"): continue
1413 if re.match("on[A-Z]+", m.name): continue
1414
1415 by_name[m.name].append(m)
1416
1417 for a in m.args:
1418 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1419 found[m.name] = m
1420
1421 for f in found.values():
1422 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001423 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001424 for m in by_name[f.name]:
1425 if "android.os.Handler" in m.args:
1426 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001427 if "java.util.concurrent.Executor" in m.args:
1428 takes_exec = True
1429 if not takes_exec:
1430 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001431
1432
1433def verify_context_first(clazz):
1434 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001435 examine = clazz.ctors + clazz.methods
1436 for m in examine:
1437 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001438 if "android.content.Context" in m.args[1:]:
1439 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001440 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1441 if "android.content.ContentResolver" in m.args[1:]:
1442 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001443
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001444
1445def verify_listener_last(clazz):
1446 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1447 examine = clazz.ctors + clazz.methods
1448 for m in examine:
1449 if "Listener" in m.name or "Callback" in m.name: continue
1450 found = False
1451 for a in m.args:
1452 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1453 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001454 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001455 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1456
1457
1458def verify_resource_names(clazz):
1459 """Verifies that resource names have consistent case."""
1460 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1461
1462 # Resources defined by files are foo_bar_baz
1463 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1464 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001465 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1466 if f.name.startswith("config_"):
1467 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1468
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001469 if re.match("[a-z1-9_]+$", f.name): continue
1470 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1471
1472 # Resources defined inside files are fooBarBaz
1473 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1474 for f in clazz.fields:
1475 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1476 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1477 if re.match("state_[a-z_]*$", f.name): continue
1478
1479 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1480 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1481
1482 # Styles are FooBar_Baz
1483 if clazz.name in ["style"]:
1484 for f in clazz.fields:
1485 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1486 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001487
1488
Jeff Sharkey331279b2016-02-29 16:02:02 -07001489def verify_files(clazz):
1490 """Verifies that methods accepting File also accept streams."""
1491
1492 has_file = set()
1493 has_stream = set()
1494
1495 test = []
1496 test.extend(clazz.ctors)
1497 test.extend(clazz.methods)
1498
1499 for m in test:
1500 if "java.io.File" in m.args:
1501 has_file.add(m)
1502 if "java.io.FileDescriptor" in m.args or "android.os.ParcelFileDescriptor" in m.args or "java.io.InputStream" in m.args or "java.io.OutputStream" in m.args:
1503 has_stream.add(m.name)
1504
1505 for m in has_file:
1506 if m.name not in has_stream:
1507 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1508
1509
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001510def verify_manager_list(clazz):
1511 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1512
1513 if not clazz.name.endswith("Manager"): return
1514
1515 for m in clazz.methods:
1516 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1517 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1518
1519
Jeff Sharkey26c80902016-12-21 13:41:17 -07001520def verify_abstract_inner(clazz):
1521 """Verifies that abstract inner classes are static."""
1522
1523 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001524 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001525 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1526
1527
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001528def verify_runtime_exceptions(clazz):
1529 """Verifies that runtime exceptions aren't listed in throws."""
1530
1531 banned = [
1532 "java.lang.NullPointerException",
1533 "java.lang.ClassCastException",
1534 "java.lang.IndexOutOfBoundsException",
1535 "java.lang.reflect.UndeclaredThrowableException",
1536 "java.lang.reflect.MalformedParametersException",
1537 "java.lang.reflect.MalformedParameterizedTypeException",
1538 "java.lang.invoke.WrongMethodTypeException",
1539 "java.lang.EnumConstantNotPresentException",
1540 "java.lang.IllegalMonitorStateException",
1541 "java.lang.SecurityException",
1542 "java.lang.UnsupportedOperationException",
1543 "java.lang.annotation.AnnotationTypeMismatchException",
1544 "java.lang.annotation.IncompleteAnnotationException",
1545 "java.lang.TypeNotPresentException",
1546 "java.lang.IllegalStateException",
1547 "java.lang.ArithmeticException",
1548 "java.lang.IllegalArgumentException",
1549 "java.lang.ArrayStoreException",
1550 "java.lang.NegativeArraySizeException",
1551 "java.util.MissingResourceException",
1552 "java.util.EmptyStackException",
1553 "java.util.concurrent.CompletionException",
1554 "java.util.concurrent.RejectedExecutionException",
1555 "java.util.IllformedLocaleException",
1556 "java.util.ConcurrentModificationException",
1557 "java.util.NoSuchElementException",
1558 "java.io.UncheckedIOException",
1559 "java.time.DateTimeException",
1560 "java.security.ProviderException",
1561 "java.nio.BufferUnderflowException",
1562 "java.nio.BufferOverflowException",
1563 ]
1564
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001565 examine = clazz.ctors + clazz.methods
1566 for m in examine:
1567 for t in m.throws:
1568 if t in banned:
1569 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001570
1571
1572def verify_error(clazz):
1573 """Verifies that we always use Exception instead of Error."""
1574 if not clazz.extends: return
1575 if clazz.extends.endswith("Error"):
1576 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1577 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1578 error(clazz, None, None, "Exceptions must be named FooException")
1579
1580
1581def verify_units(clazz):
1582 """Verifies that we use consistent naming for units."""
1583
1584 # If we find K, recommend replacing with V
1585 bad = {
1586 "Ns": "Nanos",
1587 "Ms": "Millis or Micros",
1588 "Sec": "Seconds", "Secs": "Seconds",
1589 "Hr": "Hours", "Hrs": "Hours",
1590 "Mo": "Months", "Mos": "Months",
1591 "Yr": "Years", "Yrs": "Years",
1592 "Byte": "Bytes", "Space": "Bytes",
1593 }
1594
1595 for m in clazz.methods:
1596 if m.typ not in ["short","int","long"]: continue
1597 for k, v in bad.iteritems():
1598 if m.name.endswith(k):
1599 error(clazz, m, None, "Expected method name units to be " + v)
1600 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1601 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1602 if m.name.endswith("Seconds"):
1603 error(clazz, m, None, "Returned time values must be in milliseconds")
1604
1605 for m in clazz.methods:
1606 typ = m.typ
1607 if typ == "void":
1608 if len(m.args) != 1: continue
1609 typ = m.args[0]
1610
1611 if m.name.endswith("Fraction") and typ != "float":
1612 error(clazz, m, None, "Fractions must use floats")
1613 if m.name.endswith("Percentage") and typ != "int":
1614 error(clazz, m, None, "Percentage must use ints")
1615
1616
1617def verify_closable(clazz):
1618 """Verifies that classes are AutoClosable."""
Adrian Roosb787c182019-01-03 18:54:33 +01001619 if clazz.implements == "java.lang.AutoCloseable": return
1620 if clazz.implements == "java.io.Closeable": return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001621
1622 for m in clazz.methods:
1623 if len(m.args) > 0: continue
1624 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1625 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1626 return
1627
1628
Jake Wharton9e6738f2017-08-23 11:59:55 -04001629def verify_member_name_not_kotlin_keyword(clazz):
1630 """Prevent method names which are keywords in Kotlin."""
1631
1632 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1633 # This list does not include Java keywords as those are already impossible to use.
1634 keywords = [
1635 'as',
1636 'fun',
1637 'in',
1638 'is',
1639 'object',
1640 'typealias',
1641 'val',
1642 'var',
1643 'when',
1644 ]
1645
1646 for m in clazz.methods:
1647 if m.name in keywords:
1648 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1649 for f in clazz.fields:
1650 if f.name in keywords:
1651 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1652
1653
1654def verify_method_name_not_kotlin_operator(clazz):
1655 """Warn about method names which become operators in Kotlin."""
1656
1657 binary = set()
1658
1659 def unique_binary_op(m, op):
1660 if op in binary:
1661 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1662 binary.add(op)
1663
1664 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001665 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001666 continue
1667
1668 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1669 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1670 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1671
1672 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1673 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1674 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1675 # practical way of checking that relationship here.
1676 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1677
1678 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1679 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1680 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1681 unique_binary_op(m, m.name)
1682
1683 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1684 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1685 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1686
1687 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1688 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1689 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1690
1691 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1692 if m.name == 'invoke':
1693 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1694
1695 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1696 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1697 and len(m.args) == 1 \
1698 and m.typ == 'void':
1699 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1700 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1701
1702
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001703def verify_collections_over_arrays(clazz):
1704 """Warn that [] should be Collections."""
1705
Adrian Roosb787c182019-01-03 18:54:33 +01001706 if "@interface" in clazz.split:
1707 return
1708
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001709 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1710 for m in clazz.methods:
1711 if m.typ.endswith("[]") and m.typ not in safe:
1712 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1713 for arg in m.args:
1714 if arg.endswith("[]") and arg not in safe:
1715 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1716
1717
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001718def verify_user_handle(clazz):
1719 """Methods taking UserHandle should be ForUser or AsUser."""
1720 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1721 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1722 if clazz.fullname == "android.content.pm.LauncherApps": return
1723 if clazz.fullname == "android.os.UserHandle": return
1724 if clazz.fullname == "android.os.UserManager": return
1725
1726 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001727 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001728
1729 has_arg = "android.os.UserHandle" in m.args
1730 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1731
1732 if clazz.fullname.endswith("Manager") and has_arg:
1733 warn(clazz, m, None, "When a method overload is needed to target a specific "
1734 "UserHandle, callers should be directed to use "
1735 "Context.createPackageContextAsUser() and re-obtain the relevant "
1736 "Manager, and no new API should be added")
1737 elif has_arg and not has_name:
1738 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1739 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001740
1741
1742def verify_params(clazz):
1743 """Parameter classes should be 'Params'."""
1744 if clazz.name.endswith("Params"): return
1745 if clazz.fullname == "android.app.ActivityOptions": return
1746 if clazz.fullname == "android.app.BroadcastOptions": return
1747 if clazz.fullname == "android.os.Bundle": return
1748 if clazz.fullname == "android.os.BaseBundle": return
1749 if clazz.fullname == "android.os.PersistableBundle": return
1750
1751 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1752 for b in bad:
1753 if clazz.name.endswith(b):
1754 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1755
1756
1757def verify_services(clazz):
1758 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1759 if clazz.fullname != "android.content.Context": return
1760
1761 for f in clazz.fields:
1762 if f.typ != "java.lang.String": continue
1763 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1764 if found:
1765 expected = found.group(1).lower()
1766 if f.value != expected:
1767 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1768
1769
1770def verify_tense(clazz):
1771 """Verify tenses of method names."""
1772 if clazz.fullname.startswith("android.opengl"): return
1773
1774 for m in clazz.methods:
1775 if m.name.endswith("Enable"):
1776 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1777
1778
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001779def verify_icu(clazz):
1780 """Verifies that richer ICU replacements are used."""
1781 better = {
1782 "java.util.TimeZone": "android.icu.util.TimeZone",
1783 "java.util.Calendar": "android.icu.util.Calendar",
1784 "java.util.Locale": "android.icu.util.ULocale",
1785 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1786 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1787 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1788 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1789 "java.lang.Character": "android.icu.lang.UCharacter",
1790 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1791 "java.text.Collator": "android.icu.text.Collator",
1792 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1793 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1794 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1795 "java.text.DateFormat": "android.icu.text.DateFormat",
1796 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1797 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1798 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1799 }
1800
1801 for m in clazz.ctors + clazz.methods:
1802 types = []
1803 types.extend(m.typ)
1804 types.extend(m.args)
1805 for arg in types:
1806 if arg in better:
1807 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1808
1809
1810def verify_clone(clazz):
1811 """Verify that clone() isn't implemented; see EJ page 61."""
1812 for m in clazz.methods:
1813 if m.name == "clone":
1814 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1815
1816
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001817def verify_pfd(clazz):
1818 """Verify that android APIs use PFD over FD."""
1819 examine = clazz.ctors + clazz.methods
1820 for m in examine:
1821 if m.typ == "java.io.FileDescriptor":
1822 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1823 if m.typ == "int":
1824 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1825 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1826 for arg in m.args:
1827 if arg == "java.io.FileDescriptor":
1828 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1829
1830 for f in clazz.fields:
1831 if f.typ == "java.io.FileDescriptor":
1832 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1833
1834
1835def verify_numbers(clazz):
1836 """Discourage small numbers types like short and byte."""
1837
1838 discouraged = ["short","byte"]
1839
1840 for c in clazz.ctors:
1841 for arg in c.args:
1842 if arg in discouraged:
1843 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1844
1845 for f in clazz.fields:
1846 if f.typ in discouraged:
1847 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1848
1849 for m in clazz.methods:
1850 if m.typ in discouraged:
1851 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1852 for arg in m.args:
1853 if arg in discouraged:
1854 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1855
1856
1857def verify_singleton(clazz):
1858 """Catch singleton objects with constructors."""
1859
1860 singleton = False
1861 for m in clazz.methods:
1862 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1863 singleton = True
1864
1865 if singleton:
1866 for c in clazz.ctors:
1867 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1868
1869
1870
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001871def is_interesting(clazz):
1872 """Test if given class is interesting from an Android PoV."""
1873
1874 if clazz.pkg.name.startswith("java"): return False
1875 if clazz.pkg.name.startswith("junit"): return False
1876 if clazz.pkg.name.startswith("org.apache"): return False
1877 if clazz.pkg.name.startswith("org.xml"): return False
1878 if clazz.pkg.name.startswith("org.json"): return False
1879 if clazz.pkg.name.startswith("org.w3c"): return False
1880 if clazz.pkg.name.startswith("android.icu."): return False
1881 return True
1882
1883
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001884def examine_clazz(clazz):
1885 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001886
1887 notice(clazz)
1888
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001889 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001890
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001891 verify_constants(clazz)
1892 verify_enums(clazz)
1893 verify_class_names(clazz)
1894 verify_method_names(clazz)
1895 verify_callbacks(clazz)
1896 verify_listeners(clazz)
1897 verify_actions(clazz)
1898 verify_extras(clazz)
1899 verify_equals(clazz)
1900 verify_parcelable(clazz)
1901 verify_protected(clazz)
1902 verify_fields(clazz)
1903 verify_register(clazz)
1904 verify_sync(clazz)
1905 verify_intent_builder(clazz)
1906 verify_helper_classes(clazz)
1907 verify_builder(clazz)
1908 verify_aidl(clazz)
1909 verify_internal(clazz)
1910 verify_layering(clazz)
1911 verify_boolean(clazz)
1912 verify_collections(clazz)
1913 verify_flags(clazz)
1914 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001915 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001916 verify_bitset(clazz)
1917 verify_manager(clazz)
1918 verify_boxed(clazz)
1919 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001920 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001921 verify_callback_handlers(clazz)
1922 verify_context_first(clazz)
1923 verify_listener_last(clazz)
1924 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001925 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001926 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001927 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001928 verify_runtime_exceptions(clazz)
1929 verify_error(clazz)
1930 verify_units(clazz)
1931 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001932 verify_member_name_not_kotlin_keyword(clazz)
1933 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001934 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001935 verify_user_handle(clazz)
1936 verify_params(clazz)
1937 verify_services(clazz)
1938 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001939 verify_icu(clazz)
1940 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001941 verify_pfd(clazz)
1942 verify_numbers(clazz)
1943 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001944
1945
Adrian Roos038a0292018-12-19 17:11:21 +01001946def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001947 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001948 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001949 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001950 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001951 _parse_stream(stream, examine_clazz, base_f=base_stream,
1952 in_classes_with_base=in_classes_with_base,
1953 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001954 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001955
1956
1957def examine_api(api):
1958 """Find all style issues in the given parsed API."""
1959 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001960 failures = {}
1961 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001962 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001963 return failures
1964
1965
Jeff Sharkey037458a2014-09-04 15:46:20 -07001966def verify_compat(cur, prev):
1967 """Find any incompatible API changes between two levels."""
1968 global failures
1969
1970 def class_exists(api, test):
1971 return test.fullname in api
1972
1973 def ctor_exists(api, clazz, test):
1974 for m in clazz.ctors:
1975 if m.ident == test.ident: return True
1976 return False
1977
1978 def all_methods(api, clazz):
1979 methods = list(clazz.methods)
1980 if clazz.extends is not None:
1981 methods.extend(all_methods(api, api[clazz.extends]))
1982 return methods
1983
1984 def method_exists(api, clazz, test):
1985 methods = all_methods(api, clazz)
1986 for m in methods:
1987 if m.ident == test.ident: return True
1988 return False
1989
1990 def field_exists(api, clazz, test):
1991 for f in clazz.fields:
1992 if f.ident == test.ident: return True
1993 return False
1994
1995 failures = {}
1996 for key in sorted(prev.keys()):
1997 prev_clazz = prev[key]
1998
1999 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002000 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002001 continue
2002
2003 cur_clazz = cur[key]
2004
2005 for test in prev_clazz.ctors:
2006 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002007 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002008
2009 methods = all_methods(prev, prev_clazz)
2010 for test in methods:
2011 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002012 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002013
2014 for test in prev_clazz.fields:
2015 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002016 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002017
2018 return failures
2019
2020
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002021def show_deprecations_at_birth(cur, prev):
2022 """Show API deprecations at birth."""
2023 global failures
2024
2025 # Remove all existing things so we're left with new
2026 for prev_clazz in prev.values():
2027 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002028 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002029
2030 sigs = { i.ident: i for i in prev_clazz.ctors }
2031 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2032 sigs = { i.ident: i for i in prev_clazz.methods }
2033 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2034 sigs = { i.ident: i for i in prev_clazz.fields }
2035 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2036
2037 # Forget about class entirely when nothing new
2038 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2039 del cur[prev_clazz.fullname]
2040
2041 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002042 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002043 error(clazz, None, None, "Found API deprecation at birth")
2044
2045 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002046 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002047 error(clazz, i, None, "Found API deprecation at birth")
2048
2049 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2050 format(reset=True)))
2051 for f in sorted(failures):
2052 print failures[f]
2053 print
2054
2055
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002056def show_stats(cur, prev):
2057 """Show API stats."""
2058
2059 stats = collections.defaultdict(int)
2060 for cur_clazz in cur.values():
2061 if not is_interesting(cur_clazz): continue
2062
2063 if cur_clazz.fullname not in prev:
2064 stats['new_classes'] += 1
2065 stats['new_ctors'] += len(cur_clazz.ctors)
2066 stats['new_methods'] += len(cur_clazz.methods)
2067 stats['new_fields'] += len(cur_clazz.fields)
2068 else:
2069 prev_clazz = prev[cur_clazz.fullname]
2070
2071 sigs = { i.ident: i for i in prev_clazz.ctors }
2072 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2073 sigs = { i.ident: i for i in prev_clazz.methods }
2074 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2075 sigs = { i.ident: i for i in prev_clazz.fields }
2076 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2077
2078 if ctors + methods + fields > 0:
2079 stats['extend_classes'] += 1
2080 stats['extend_ctors'] += ctors
2081 stats['extend_methods'] += methods
2082 stats['extend_fields'] += fields
2083
2084 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2085 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2086
2087
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002088if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002089 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2090 patterns. It ignores lint messages from a previous API level, if provided.")
2091 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2092 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2093 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002094 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2095 help="The base current.txt to use when examining system-current.txt or"
2096 " test-current.txt")
2097 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2098 help="The base previous.txt to use when examining system-previous.txt or"
2099 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002100 parser.add_argument("--no-color", action='store_const', const=True,
2101 help="Disable terminal colors")
2102 parser.add_argument("--allow-google", action='store_const', const=True,
2103 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002104 parser.add_argument("--show-noticed", action='store_const', const=True,
2105 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002106 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2107 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002108 parser.add_argument("--show-stats", action='store_const', const=True,
2109 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002110 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002111
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002112 if args['no_color']:
2113 USE_COLOR = False
2114
2115 if args['allow_google']:
2116 ALLOW_GOOGLE = True
2117
2118 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002119 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002120 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002121 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002122
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002123 if args['show_deprecations_at_birth']:
2124 with current_file as f:
2125 cur = _parse_stream(f)
2126 with previous_file as f:
2127 prev = _parse_stream(f)
2128 show_deprecations_at_birth(cur, prev)
2129 sys.exit()
2130
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002131 if args['show_stats']:
2132 with current_file as f:
2133 cur = _parse_stream(f)
2134 with previous_file as f:
2135 prev = _parse_stream(f)
2136 show_stats(cur, prev)
2137 sys.exit()
2138
Adrian Roos038a0292018-12-19 17:11:21 +01002139 classes_with_base = []
2140
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002141 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002142 if base_current_file:
2143 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002144 cur_fail, cur_noticed = examine_stream(f, base_f,
2145 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002146 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002147 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2148
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002149 if not previous_file is None:
2150 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002151 if base_previous_file:
2152 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002153 prev_fail, prev_noticed = examine_stream(f, base_f,
2154 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002155 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002156 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002157
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002158 # ignore errors from previous API level
2159 for p in prev_fail:
2160 if p in cur_fail:
2161 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002162
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002163 # ignore classes unchanged from previous API level
2164 for k, v in prev_noticed.iteritems():
2165 if k in cur_noticed and v == cur_noticed[k]:
2166 del cur_noticed[k]
2167
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002168 """
2169 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002170 # look for compatibility issues
2171 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002172
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002173 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2174 for f in sorted(compat_fail):
2175 print compat_fail[f]
2176 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002177 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002178
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002179 if args['show_noticed'] and len(cur_noticed) != 0:
2180 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2181 for f in sorted(cur_noticed.keys()):
2182 print f
2183 print
2184
Jason Monk53b2a732017-11-10 15:43:17 -05002185 if len(cur_fail) != 0:
2186 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2187 for f in sorted(cur_fail):
2188 print cur_fail[f]
2189 print
2190 sys.exit(77)