blob: 6476abd8268e632dc374fa6f064ed3fb0643bac1 [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
Adam Metcalf1c7e70a2015-03-27 16:11:23 -070029import re, sys, collections, traceback, argparse
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
Jeff Sharkey8b141b92018-04-11 10:05:44 -060053def ident(raw):
54 """Strips superficial signature changes, giving us a strong key that
55 can be used to identify members across API levels."""
56 raw = raw.replace(" deprecated ", " ")
57 raw = raw.replace(" synchronized ", " ")
58 raw = raw.replace(" final ", " ")
59 raw = re.sub("<.+?>", "", raw)
60 if " throws " in raw:
61 raw = raw[:raw.index(" throws ")]
62 return raw
63
64
Jeff Sharkey8190f4882014-08-28 12:24:07 -070065class Field():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070066 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070067 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070068 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070070 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070071
Jeff Sharkey40d67f42018-07-17 13:29:40 -060072 # drop generics for now; may need multiple passes
73 raw = re.sub("<[^<]+?>", "", raw)
74 raw = re.sub("<[^<]+?>", "", raw)
75
Jeff Sharkey8190f4882014-08-28 12:24:07 -070076 raw = raw.split()
77 self.split = list(raw)
78
79 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
80 while r in raw: raw.remove(r)
81
Jeff Sharkey40d67f42018-07-17 13:29:40 -060082 # ignore annotations for now
83 raw = [ r for r in raw if not r.startswith("@") ]
84
Jeff Sharkey8190f4882014-08-28 12:24:07 -070085 self.typ = raw[0]
86 self.name = raw[1].strip(";")
87 if len(raw) >= 4 and raw[2] == "=":
88 self.value = raw[3].strip(';"')
89 else:
90 self.value = None
Jeff Sharkey8b141b92018-04-11 10:05:44 -060091 self.ident = ident(self.raw)
Jeff Sharkey037458a2014-09-04 15:46:20 -070092
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070093 def __hash__(self):
94 return hash(self.raw)
95
Jeff Sharkey8190f4882014-08-28 12:24:07 -070096 def __repr__(self):
97 return self.raw
98
99
100class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700101 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700102 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700103 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700105 self.blame = blame
106
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600107 # drop generics for now; may need multiple passes
108 raw = re.sub("<[^<]+?>", "", raw)
109 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700110
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600111 # handle each clause differently
112 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
113
114 # parse prefixes
115 raw = re.split("[\s]+", raw_prefix)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700116 for r in ["", ";"]:
117 while r in raw: raw.remove(r)
118 self.split = list(raw)
119
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600120 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700121 while r in raw: raw.remove(r)
122
123 self.typ = raw[0]
124 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600125
126 # parse args
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700127 self.args = []
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600128 for arg in re.split(",\s*", raw_args):
129 arg = re.split("\s", arg)
130 # ignore annotations for now
131 arg = [ a for a in arg if not a.startswith("@") ]
132 if len(arg[0]) > 0:
133 self.args.append(arg[0])
134
135 # parse throws
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700136 self.throws = []
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600137 for throw in re.split(",\s*", raw_throws):
138 self.throws.append(throw)
139
Jeff Sharkey8b141b92018-04-11 10:05:44 -0600140 self.ident = ident(self.raw)
Jeff Sharkey037458a2014-09-04 15:46:20 -0700141
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700142 def __hash__(self):
143 return hash(self.raw)
144
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700145 def __repr__(self):
146 return self.raw
147
148
149class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700150 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700151 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700152 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700153 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700154 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700155 self.ctors = []
156 self.fields = []
157 self.methods = []
158
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600159 # drop generics for now; may need multiple passes
160 raw = re.sub("<[^<]+?>", "", raw)
161 raw = re.sub("<[^<]+?>", "", raw)
162
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700163 raw = raw.split()
164 self.split = list(raw)
165 if "class" in raw:
166 self.fullname = raw[raw.index("class")+1]
167 elif "interface" in raw:
168 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600169 elif "@interface" in raw:
170 self.fullname = raw[raw.index("@interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700171 else:
172 raise ValueError("Funky class type %s" % (self.raw))
173
174 if "extends" in raw:
175 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800176 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700177 else:
178 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800179 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700180
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700181 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800182 self.fullname_path = self.fullname.split(".")
183
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700184 self.name = self.fullname[self.fullname.rindex(".")+1:]
185
Adrian Roos6eb57b02018-12-13 22:08:29 +0100186 def merge_from(self, other):
187 self.ctors.extend(other.ctors)
188 self.fields.extend(other.fields)
189 self.methods.extend(other.methods)
190
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700191 def __hash__(self):
192 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
193
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700194 def __repr__(self):
195 return self.raw
196
197
198class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700199 def __init__(self, line, raw, blame):
200 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700201 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700202 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700203
204 raw = raw.split()
205 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800206 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700207
208 def __repr__(self):
209 return self.raw
210
211
Adrian Roos038a0292018-12-19 17:11:21 +0100212def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
213 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100215 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100216
217 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100218 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100219 else:
220 base_classes = []
221
Adrian Roos038a0292018-12-19 17:11:21 +0100222 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100223 if clazz_cb:
224 clazz_cb(clazz)
225 else: # In callback mode, don't keep track of the full API
226 api[clazz.fullname] = clazz
227
Adrian Roos038a0292018-12-19 17:11:21 +0100228 def handle_missed_classes_with_base(clazz):
229 for c in _yield_until_matching_class(in_classes_with_base, clazz):
230 base_class = _skip_to_matching_class(base_classes, c)
231 if base_class:
232 handle_class(base_class)
233
234 for clazz in _parse_stream_to_generator(f):
235 # Before looking at clazz, let's see if there's some classes that were not present, but
236 # may have an entry in the base stream.
237 handle_missed_classes_with_base(clazz)
238
239 base_class = _skip_to_matching_class(base_classes, clazz)
240 if base_class:
241 clazz.merge_from(base_class)
242 if out_classes_with_base is not None:
243 out_classes_with_base.append(clazz)
244 handle_class(clazz)
245
246 handle_missed_classes_with_base(None)
247
Adrian Roos6eb57b02018-12-13 22:08:29 +0100248 return api
249
250def _parse_stream_to_generator(f):
251 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700252 pkg = None
253 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700254 blame = None
255
256 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800257 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700258 line += 1
259 raw = raw.rstrip()
260 match = re_blame.match(raw)
261 if match is not None:
262 blame = match.groups()[0:2]
263 raw = match.groups()[2]
264 else:
265 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700266
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700267 if raw.startswith("package"):
268 pkg = Package(line, raw, blame)
269 elif raw.startswith(" ") and raw.endswith("{"):
270 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700271 elif raw.startswith(" ctor"):
272 clazz.ctors.append(Method(clazz, line, raw, blame))
273 elif raw.startswith(" method"):
274 clazz.methods.append(Method(clazz, line, raw, blame))
275 elif raw.startswith(" field"):
276 clazz.fields.append(Field(clazz, line, raw, blame))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100277 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100278 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800279
Adrian Roos5ed42b62018-12-19 17:10:22 +0100280def _retry_iterator(it):
281 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
282 for e in it:
283 while True:
284 retry = yield e
285 if not retry:
286 break
287 # send() was called, asking us to redeliver clazz on next(). Still need to yield
288 # a dummy value to the send() first though.
289 if (yield "Returning clazz on next()"):
290 raise TypeError("send() must be followed by next(), not send()")
291
Adrian Roos038a0292018-12-19 17:11:21 +0100292def _skip_to_matching_class(classes, needle):
293 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700294
Adrian Roos6eb57b02018-12-13 22:08:29 +0100295 This relies on classes being sorted by package and class name."""
296
297 for clazz in classes:
298 if clazz.pkg.name < needle.pkg.name:
299 # We haven't reached the right package yet
300 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100301 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
302 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100303 continue
304 if clazz.fullname == needle.fullname:
305 return clazz
306 # We ran past the right class. Send it back into the generator, then report failure.
307 classes.send(clazz)
308 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700309
Adrian Roos038a0292018-12-19 17:11:21 +0100310def _yield_until_matching_class(classes, needle):
311 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
312
313 This relies on classes being sorted by package and class name."""
314
315 for clazz in classes:
316 if needle is None:
317 yield clazz
318 elif clazz.pkg.name < needle.pkg.name:
319 # We haven't reached the right package yet
320 yield clazz
321 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
322 # We're in the right package, but not the right class yet
323 yield clazz
324 elif clazz.fullname == needle.fullname:
325 # Class found, abort.
326 return
327 else:
328 # We ran past the right class. Send it back into the iterator, then abort.
329 classes.send(clazz)
330 return
331
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700332class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800333 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700334 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700335 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800336 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700337 self.msg = msg
338
339 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800340 self.head = "Error %s" % (rule) if rule else "Error"
341 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 -0700342 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800343 self.head = "Warning %s" % (rule) if rule else "Warning"
344 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 -0700345
346 self.line = clazz.line
347 blame = clazz.blame
348 if detail is not None:
349 dump += "\n in " + repr(detail)
350 self.line = detail.line
351 blame = detail.blame
352 dump += "\n in " + repr(clazz)
353 dump += "\n in " + repr(clazz.pkg)
354 dump += "\n at line " + repr(self.line)
355 if blame is not None:
356 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
357
358 self.dump = dump
359
360 def __repr__(self):
361 return self.dump
362
363
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700364failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700365
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800366def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700367 """Records an API failure to be processed later."""
368 global failures
369
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700370 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
371 sig = sig.replace(" deprecated ", " ")
372
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800373 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700374
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700375
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800376def warn(clazz, detail, rule, msg):
377 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700378
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800379def error(clazz, detail, rule, msg):
380 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700381
382
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700383noticed = {}
384
385def notice(clazz):
386 global noticed
387
388 noticed[clazz.fullname] = hash(clazz)
389
390
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700391def verify_constants(clazz):
392 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700393 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600394 if clazz.fullname.startswith("android.os.Build"): return
395 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700396
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600397 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700398 for f in clazz.fields:
399 if "static" in f.split and "final" in f.split:
400 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800401 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600402 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700403 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
404 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600405 if f.typ in req and f.value is None:
406 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700407
408
409def verify_enums(clazz):
410 """Enums are bad, mmkay?"""
411 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800412 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700413
414
415def verify_class_names(clazz):
416 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700417 if clazz.fullname.startswith("android.opengl"): return
418 if clazz.fullname.startswith("android.renderscript"): return
419 if re.match("android\.R\.[a-z]+", clazz.fullname): return
420
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700421 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800422 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700423 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800424 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700425 if clazz.name.endswith("Impl"):
426 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700427
428
429def verify_method_names(clazz):
430 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700431 if clazz.fullname.startswith("android.opengl"): return
432 if clazz.fullname.startswith("android.renderscript"): return
433 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700434
435 for m in clazz.methods:
436 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800437 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700438 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800439 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700440
441
442def verify_callbacks(clazz):
443 """Verify Callback classes.
444 All callback classes must be abstract.
445 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700446 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700447
448 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800449 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700450 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800451 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700452
453 if clazz.name.endswith("Callback"):
454 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800455 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700456
457 for m in clazz.methods:
458 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800459 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700460
461
462def verify_listeners(clazz):
463 """Verify Listener classes.
464 All Listener classes must be interface.
465 All methods must follow onFoo() naming style.
466 If only a single method, it must match class name:
467 interface OnFooListener { void onFoo() }"""
468
469 if clazz.name.endswith("Listener"):
470 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800471 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700472
473 for m in clazz.methods:
474 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800475 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700476
477 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
478 m = clazz.methods[0]
479 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800480 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700481
482
483def verify_actions(clazz):
484 """Verify intent actions.
485 All action names must be named ACTION_FOO.
486 All action values must be scoped by package and match name:
487 package android.foo {
488 String ACTION_BAR = "android.foo.action.BAR";
489 }"""
490 for f in clazz.fields:
491 if f.value is None: continue
492 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700493 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600494 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700495
496 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
497 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
498 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800499 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700500 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700501 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700502 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700503 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700504 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700505 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
506 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700507 else:
508 prefix = clazz.pkg.name + ".action"
509 expected = prefix + "." + f.name[7:]
510 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700511 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700512
513
514def verify_extras(clazz):
515 """Verify intent extras.
516 All extra names must be named EXTRA_FOO.
517 All extra values must be scoped by package and match name:
518 package android.foo {
519 String EXTRA_BAR = "android.foo.extra.BAR";
520 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700521 if clazz.fullname == "android.app.Notification": return
522 if clazz.fullname == "android.appwidget.AppWidgetManager": return
523
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700524 for f in clazz.fields:
525 if f.value is None: continue
526 if f.name.startswith("ACTION_"): continue
527
528 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
529 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
530 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800531 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700532 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700533 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700534 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700535 elif clazz.pkg.name == "android.app.admin":
536 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700537 else:
538 prefix = clazz.pkg.name + ".extra"
539 expected = prefix + "." + f.name[6:]
540 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700541 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700542
543
544def verify_equals(clazz):
545 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e62016-12-21 13:46:33 -0700546 eq = False
547 hc = False
548 for m in clazz.methods:
549 if " static " in m.raw: continue
550 if "boolean equals(java.lang.Object)" in m.raw: eq = True
551 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700552 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800553 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700554
555
556def verify_parcelable(clazz):
557 """Verify that Parcelable objects aren't hiding required bits."""
558 if "implements android.os.Parcelable" in clazz.raw:
559 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
560 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
561 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
562
563 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800564 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700565
Joe LaPenna45380002017-04-20 12:49:48 -0700566 if ((" final class " not in clazz.raw) and
567 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700568 error(clazz, None, "FW8", "Parcelable classes must be final")
569
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700570 for c in clazz.ctors:
571 if c.args == ["android.os.Parcel"]:
572 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
573
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700574
575def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800576 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700577 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600578 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700579 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800580 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700581 for f in clazz.fields:
582 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800583 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700584
585
586def verify_fields(clazz):
587 """Verify that all exposed fields are final.
588 Exposed fields must follow myName style.
589 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700590
591 IGNORE_BARE_FIELDS = [
592 "android.app.ActivityManager.RecentTaskInfo",
593 "android.app.Notification",
594 "android.content.pm.ActivityInfo",
595 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600596 "android.content.pm.ComponentInfo",
597 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700598 "android.content.pm.FeatureGroupInfo",
599 "android.content.pm.InstrumentationInfo",
600 "android.content.pm.PackageInfo",
601 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600602 "android.content.res.Configuration",
603 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700604 "android.os.Message",
605 "android.system.StructPollfd",
606 ]
607
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700608 for f in clazz.fields:
609 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700610 if clazz.fullname in IGNORE_BARE_FIELDS:
611 pass
612 elif clazz.fullname.endswith("LayoutParams"):
613 pass
614 elif clazz.fullname.startswith("android.util.Mutable"):
615 pass
616 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800617 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700618
619 if not "static" in f.split:
620 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800621 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700622
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700623 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800624 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700625
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700626 if re.match("[A-Z_]+", f.name):
627 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800628 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700629
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700630
631def verify_register(clazz):
632 """Verify parity of registration methods.
633 Callback objects use register/unregister methods.
634 Listener objects use add/remove methods."""
635 methods = [ m.name for m in clazz.methods ]
636 for m in clazz.methods:
637 if "Callback" in m.raw:
638 if m.name.startswith("register"):
639 other = "unregister" + m.name[8:]
640 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800641 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700642 if m.name.startswith("unregister"):
643 other = "register" + m.name[10:]
644 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800645 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700646
647 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800648 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700649
650 if "Listener" in m.raw:
651 if m.name.startswith("add"):
652 other = "remove" + m.name[3:]
653 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800654 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700655 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
656 other = "add" + m.name[6:]
657 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800658 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700659
660 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800661 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700662
663
664def verify_sync(clazz):
665 """Verify synchronized methods aren't exposed."""
666 for m in clazz.methods:
667 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800668 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700669
670
671def verify_intent_builder(clazz):
672 """Verify that Intent builders are createFooIntent() style."""
673 if clazz.name == "Intent": return
674
675 for m in clazz.methods:
676 if m.typ == "android.content.Intent":
677 if m.name.startswith("create") and m.name.endswith("Intent"):
678 pass
679 else:
Adam Powell539ea122015-04-10 13:01:37 -0700680 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700681
682
683def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700684 """Verify that helper classes are named consistently with what they extend.
685 All developer extendable methods should be named onFoo()."""
686 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700687 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700688 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700689 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800690 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700691
692 found = False
693 for f in clazz.fields:
694 if f.name == "SERVICE_INTERFACE":
695 found = True
696 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700697 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700698
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700699 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700700 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700701 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800702 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700703
704 found = False
705 for f in clazz.fields:
706 if f.name == "PROVIDER_INTERFACE":
707 found = True
708 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700709 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700710
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700711 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700712 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700713 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800714 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700715
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700716 if "extends android.app.Activity" in clazz.raw:
717 test_methods = True
718 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800719 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700720
721 if test_methods:
722 for m in clazz.methods:
723 if "final" in m.split: continue
724 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700725 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800726 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700727 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800728 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700729
730
731def verify_builder(clazz):
732 """Verify builder classes.
733 Methods should return the builder to enable chaining."""
734 if " extends " in clazz.raw: return
735 if not clazz.name.endswith("Builder"): return
736
737 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800738 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700739
740 has_build = False
741 for m in clazz.methods:
742 if m.name == "build":
743 has_build = True
744 continue
745
746 if m.name.startswith("get"): continue
747 if m.name.startswith("clear"): continue
748
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700749 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800750 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700751
752 if m.name.startswith("set"):
753 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800754 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700755
756 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800757 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700758
759
760def verify_aidl(clazz):
761 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700762 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800763 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700764
765
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700766def verify_internal(clazz):
767 """Catch people exposing internal classes."""
768 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800769 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700770
771
772def verify_layering(clazz):
773 """Catch package layering violations.
774 For example, something in android.os depending on android.app."""
775 ranking = [
776 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
777 "android.app",
778 "android.widget",
779 "android.view",
780 "android.animation",
781 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700782 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700783 "android.database",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700784 "android.text",
Siyamed Sinir0a2e15d2018-09-13 16:06:59 -0700785 "android.graphics",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700786 "android.os",
787 "android.util"
788 ]
789
790 def rank(p):
791 for i in range(len(ranking)):
792 if isinstance(ranking[i], list):
793 for j in ranking[i]:
794 if p.startswith(j): return i
795 else:
796 if p.startswith(ranking[i]): return i
797
798 cr = rank(clazz.pkg.name)
799 if cr is None: return
800
801 for f in clazz.fields:
802 ir = rank(f.typ)
803 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800804 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700805
806 for m in clazz.methods:
807 ir = rank(m.typ)
808 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800809 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700810 for arg in m.args:
811 ir = rank(arg)
812 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800813 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700814
815
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800816def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800817 """Verifies that boolean accessors are named correctly.
818 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700819
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800820 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
821 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700822
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800823 gets = [ m for m in clazz.methods if is_get(m) ]
824 sets = [ m for m in clazz.methods if is_set(m) ]
825
826 def error_if_exists(methods, trigger, expected, actual):
827 for m in methods:
828 if m.name == actual:
829 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700830
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700831 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800832 if is_get(m):
833 if re.match("is[A-Z]", m.name):
834 target = m.name[2:]
835 expected = "setIs" + target
836 error_if_exists(sets, m.name, expected, "setHas" + target)
837 elif re.match("has[A-Z]", m.name):
838 target = m.name[3:]
839 expected = "setHas" + target
840 error_if_exists(sets, m.name, expected, "setIs" + target)
841 error_if_exists(sets, m.name, expected, "set" + target)
842 elif re.match("get[A-Z]", m.name):
843 target = m.name[3:]
844 expected = "set" + target
845 error_if_exists(sets, m.name, expected, "setIs" + target)
846 error_if_exists(sets, m.name, expected, "setHas" + target)
847
848 if is_set(m):
849 if re.match("set[A-Z]", m.name):
850 target = m.name[3:]
851 expected = "get" + target
852 error_if_exists(sets, m.name, expected, "is" + target)
853 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700854
855
856def verify_collections(clazz):
857 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700858 if clazz.fullname == "android.os.Bundle": return
859
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700860 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
861 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
862 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700863 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800864 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700865 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700866 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800867 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700868
869
870def verify_flags(clazz):
871 """Verifies that flags are non-overlapping."""
872 known = collections.defaultdict(int)
873 for f in clazz.fields:
874 if "FLAG_" in f.name:
875 try:
876 val = int(f.value)
877 except:
878 continue
879
880 scope = f.name[0:f.name.index("FLAG_")]
881 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800882 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700883 known[scope] |= val
884
885
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800886def verify_exception(clazz):
887 """Verifies that methods don't throw generic exceptions."""
888 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700889 for t in m.throws:
890 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
891 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800892
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700893 if t in ["android.os.RemoteException"]:
894 if clazz.name == "android.content.ContentProviderClient": continue
895 if clazz.name == "android.os.Binder": continue
896 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700897
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700898 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
899
900 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
901 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700902
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800903
904def verify_google(clazz):
905 """Verifies that APIs never reference Google."""
906
907 if re.search("google", clazz.raw, re.IGNORECASE):
908 error(clazz, None, None, "Must never reference Google")
909
910 test = []
911 test.extend(clazz.ctors)
912 test.extend(clazz.fields)
913 test.extend(clazz.methods)
914
915 for t in test:
916 if re.search("google", t.raw, re.IGNORECASE):
917 error(clazz, t, None, "Must never reference Google")
918
919
920def verify_bitset(clazz):
921 """Verifies that we avoid using heavy BitSet."""
922
923 for f in clazz.fields:
924 if f.typ == "java.util.BitSet":
925 error(clazz, f, None, "Field type must not be heavy BitSet")
926
927 for m in clazz.methods:
928 if m.typ == "java.util.BitSet":
929 error(clazz, m, None, "Return type must not be heavy BitSet")
930 for arg in m.args:
931 if arg == "java.util.BitSet":
932 error(clazz, m, None, "Argument type must not be heavy BitSet")
933
934
935def verify_manager(clazz):
936 """Verifies that FooManager is only obtained from Context."""
937
938 if not clazz.name.endswith("Manager"): return
939
940 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800941 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800942
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600943 for m in clazz.methods:
944 if m.typ == clazz.fullname:
945 error(clazz, m, None, "Managers must always be obtained from Context")
946
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800947
948def verify_boxed(clazz):
949 """Verifies that methods avoid boxed primitives."""
950
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800951 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 -0800952
953 for c in clazz.ctors:
954 for arg in c.args:
955 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800956 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800957
958 for f in clazz.fields:
959 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800960 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800961
962 for m in clazz.methods:
963 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800964 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800965 for arg in m.args:
966 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800967 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800968
969
970def verify_static_utils(clazz):
971 """Verifies that helper classes can't be constructed."""
972 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600973 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800974
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600975 # Only care about classes with default constructors
976 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
977 test = []
978 test.extend(clazz.fields)
979 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800980
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600981 if len(test) == 0: return
982 for t in test:
983 if "static" not in t.split:
984 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800985
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800986 error(clazz, None, None, "Fully-static utility classes must not have constructor")
987
988
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800989def verify_overload_args(clazz):
990 """Verifies that method overloads add new arguments at the end."""
991 if clazz.fullname.startswith("android.opengl"): return
992
993 overloads = collections.defaultdict(list)
994 for m in clazz.methods:
995 if "deprecated" in m.split: continue
996 overloads[m.name].append(m)
997
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800998 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800999 if len(methods) <= 1: continue
1000
1001 # Look for arguments common across all overloads
1002 def cluster(args):
1003 count = collections.defaultdict(int)
1004 res = set()
1005 for i in range(len(args)):
1006 a = args[i]
1007 res.add("%s#%d" % (a, count[a]))
1008 count[a] += 1
1009 return res
1010
1011 common_args = cluster(methods[0].args)
1012 for m in methods:
1013 common_args = common_args & cluster(m.args)
1014
1015 if len(common_args) == 0: continue
1016
1017 # Require that all common arguments are present at start of signature
1018 locked_sig = None
1019 for m in methods:
1020 sig = m.args[0:len(common_args)]
1021 if not common_args.issubset(cluster(sig)):
1022 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1023 elif not locked_sig:
1024 locked_sig = sig
1025 elif locked_sig != sig:
1026 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1027
1028
1029def verify_callback_handlers(clazz):
1030 """Verifies that methods adding listener/callback have overload
1031 for specifying delivery thread."""
1032
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001033 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001034 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001035 "animation",
1036 "view",
1037 "graphics",
1038 "transition",
1039 "widget",
1040 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001041 ]
1042 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001043 if s in clazz.pkg.name_path: return
1044 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001045
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001046 # Ignore UI classes which assume main thread
1047 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1048 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1049 if s in clazz.fullname: return
1050 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1051 for s in ["Loader"]:
1052 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001053
1054 found = {}
1055 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001056 examine = clazz.ctors + clazz.methods
1057 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001058 if m.name.startswith("unregister"): continue
1059 if m.name.startswith("remove"): continue
1060 if re.match("on[A-Z]+", m.name): continue
1061
1062 by_name[m.name].append(m)
1063
1064 for a in m.args:
1065 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1066 found[m.name] = m
1067
1068 for f in found.values():
1069 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001070 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001071 for m in by_name[f.name]:
1072 if "android.os.Handler" in m.args:
1073 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001074 if "java.util.concurrent.Executor" in m.args:
1075 takes_exec = True
1076 if not takes_exec:
1077 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001078
1079
1080def verify_context_first(clazz):
1081 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001082 examine = clazz.ctors + clazz.methods
1083 for m in examine:
1084 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001085 if "android.content.Context" in m.args[1:]:
1086 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001087 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1088 if "android.content.ContentResolver" in m.args[1:]:
1089 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001090
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001091
1092def verify_listener_last(clazz):
1093 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1094 examine = clazz.ctors + clazz.methods
1095 for m in examine:
1096 if "Listener" in m.name or "Callback" in m.name: continue
1097 found = False
1098 for a in m.args:
1099 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1100 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001101 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001102 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1103
1104
1105def verify_resource_names(clazz):
1106 """Verifies that resource names have consistent case."""
1107 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1108
1109 # Resources defined by files are foo_bar_baz
1110 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1111 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001112 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1113 if f.name.startswith("config_"):
1114 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1115
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001116 if re.match("[a-z1-9_]+$", f.name): continue
1117 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1118
1119 # Resources defined inside files are fooBarBaz
1120 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1121 for f in clazz.fields:
1122 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1123 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1124 if re.match("state_[a-z_]*$", f.name): continue
1125
1126 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1127 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1128
1129 # Styles are FooBar_Baz
1130 if clazz.name in ["style"]:
1131 for f in clazz.fields:
1132 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1133 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001134
1135
Jeff Sharkey331279b2016-02-29 16:02:02 -07001136def verify_files(clazz):
1137 """Verifies that methods accepting File also accept streams."""
1138
1139 has_file = set()
1140 has_stream = set()
1141
1142 test = []
1143 test.extend(clazz.ctors)
1144 test.extend(clazz.methods)
1145
1146 for m in test:
1147 if "java.io.File" in m.args:
1148 has_file.add(m)
1149 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:
1150 has_stream.add(m.name)
1151
1152 for m in has_file:
1153 if m.name not in has_stream:
1154 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1155
1156
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001157def verify_manager_list(clazz):
1158 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1159
1160 if not clazz.name.endswith("Manager"): return
1161
1162 for m in clazz.methods:
1163 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1164 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1165
1166
Jeff Sharkey26c80902016-12-21 13:41:17 -07001167def verify_abstract_inner(clazz):
1168 """Verifies that abstract inner classes are static."""
1169
1170 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1171 if " abstract " in clazz.raw and " static " not in clazz.raw:
1172 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1173
1174
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001175def verify_runtime_exceptions(clazz):
1176 """Verifies that runtime exceptions aren't listed in throws."""
1177
1178 banned = [
1179 "java.lang.NullPointerException",
1180 "java.lang.ClassCastException",
1181 "java.lang.IndexOutOfBoundsException",
1182 "java.lang.reflect.UndeclaredThrowableException",
1183 "java.lang.reflect.MalformedParametersException",
1184 "java.lang.reflect.MalformedParameterizedTypeException",
1185 "java.lang.invoke.WrongMethodTypeException",
1186 "java.lang.EnumConstantNotPresentException",
1187 "java.lang.IllegalMonitorStateException",
1188 "java.lang.SecurityException",
1189 "java.lang.UnsupportedOperationException",
1190 "java.lang.annotation.AnnotationTypeMismatchException",
1191 "java.lang.annotation.IncompleteAnnotationException",
1192 "java.lang.TypeNotPresentException",
1193 "java.lang.IllegalStateException",
1194 "java.lang.ArithmeticException",
1195 "java.lang.IllegalArgumentException",
1196 "java.lang.ArrayStoreException",
1197 "java.lang.NegativeArraySizeException",
1198 "java.util.MissingResourceException",
1199 "java.util.EmptyStackException",
1200 "java.util.concurrent.CompletionException",
1201 "java.util.concurrent.RejectedExecutionException",
1202 "java.util.IllformedLocaleException",
1203 "java.util.ConcurrentModificationException",
1204 "java.util.NoSuchElementException",
1205 "java.io.UncheckedIOException",
1206 "java.time.DateTimeException",
1207 "java.security.ProviderException",
1208 "java.nio.BufferUnderflowException",
1209 "java.nio.BufferOverflowException",
1210 ]
1211
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001212 examine = clazz.ctors + clazz.methods
1213 for m in examine:
1214 for t in m.throws:
1215 if t in banned:
1216 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001217
1218
1219def verify_error(clazz):
1220 """Verifies that we always use Exception instead of Error."""
1221 if not clazz.extends: return
1222 if clazz.extends.endswith("Error"):
1223 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1224 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1225 error(clazz, None, None, "Exceptions must be named FooException")
1226
1227
1228def verify_units(clazz):
1229 """Verifies that we use consistent naming for units."""
1230
1231 # If we find K, recommend replacing with V
1232 bad = {
1233 "Ns": "Nanos",
1234 "Ms": "Millis or Micros",
1235 "Sec": "Seconds", "Secs": "Seconds",
1236 "Hr": "Hours", "Hrs": "Hours",
1237 "Mo": "Months", "Mos": "Months",
1238 "Yr": "Years", "Yrs": "Years",
1239 "Byte": "Bytes", "Space": "Bytes",
1240 }
1241
1242 for m in clazz.methods:
1243 if m.typ not in ["short","int","long"]: continue
1244 for k, v in bad.iteritems():
1245 if m.name.endswith(k):
1246 error(clazz, m, None, "Expected method name units to be " + v)
1247 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1248 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1249 if m.name.endswith("Seconds"):
1250 error(clazz, m, None, "Returned time values must be in milliseconds")
1251
1252 for m in clazz.methods:
1253 typ = m.typ
1254 if typ == "void":
1255 if len(m.args) != 1: continue
1256 typ = m.args[0]
1257
1258 if m.name.endswith("Fraction") and typ != "float":
1259 error(clazz, m, None, "Fractions must use floats")
1260 if m.name.endswith("Percentage") and typ != "int":
1261 error(clazz, m, None, "Percentage must use ints")
1262
1263
1264def verify_closable(clazz):
1265 """Verifies that classes are AutoClosable."""
1266 if "implements java.lang.AutoCloseable" in clazz.raw: return
1267 if "implements java.io.Closeable" in clazz.raw: return
1268
1269 for m in clazz.methods:
1270 if len(m.args) > 0: continue
1271 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1272 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1273 return
1274
1275
Jake Wharton9e6738f2017-08-23 11:59:55 -04001276def verify_member_name_not_kotlin_keyword(clazz):
1277 """Prevent method names which are keywords in Kotlin."""
1278
1279 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1280 # This list does not include Java keywords as those are already impossible to use.
1281 keywords = [
1282 'as',
1283 'fun',
1284 'in',
1285 'is',
1286 'object',
1287 'typealias',
1288 'val',
1289 'var',
1290 'when',
1291 ]
1292
1293 for m in clazz.methods:
1294 if m.name in keywords:
1295 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1296 for f in clazz.fields:
1297 if f.name in keywords:
1298 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1299
1300
1301def verify_method_name_not_kotlin_operator(clazz):
1302 """Warn about method names which become operators in Kotlin."""
1303
1304 binary = set()
1305
1306 def unique_binary_op(m, op):
1307 if op in binary:
1308 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1309 binary.add(op)
1310
1311 for m in clazz.methods:
1312 if 'static' in m.split:
1313 continue
1314
1315 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1316 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1317 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1318
1319 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1320 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1321 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1322 # practical way of checking that relationship here.
1323 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1324
1325 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1326 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1327 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1328 unique_binary_op(m, m.name)
1329
1330 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1331 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1332 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1333
1334 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1335 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1336 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1337
1338 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1339 if m.name == 'invoke':
1340 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1341
1342 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1343 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1344 and len(m.args) == 1 \
1345 and m.typ == 'void':
1346 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1347 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1348
1349
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001350def verify_collections_over_arrays(clazz):
1351 """Warn that [] should be Collections."""
1352
1353 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1354 for m in clazz.methods:
1355 if m.typ.endswith("[]") and m.typ not in safe:
1356 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1357 for arg in m.args:
1358 if arg.endswith("[]") and arg not in safe:
1359 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1360
1361
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001362def verify_user_handle(clazz):
1363 """Methods taking UserHandle should be ForUser or AsUser."""
1364 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1365 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1366 if clazz.fullname == "android.content.pm.LauncherApps": return
1367 if clazz.fullname == "android.os.UserHandle": return
1368 if clazz.fullname == "android.os.UserManager": return
1369
1370 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001371 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001372
1373 has_arg = "android.os.UserHandle" in m.args
1374 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1375
1376 if clazz.fullname.endswith("Manager") and has_arg:
1377 warn(clazz, m, None, "When a method overload is needed to target a specific "
1378 "UserHandle, callers should be directed to use "
1379 "Context.createPackageContextAsUser() and re-obtain the relevant "
1380 "Manager, and no new API should be added")
1381 elif has_arg and not has_name:
1382 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1383 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001384
1385
1386def verify_params(clazz):
1387 """Parameter classes should be 'Params'."""
1388 if clazz.name.endswith("Params"): return
1389 if clazz.fullname == "android.app.ActivityOptions": return
1390 if clazz.fullname == "android.app.BroadcastOptions": return
1391 if clazz.fullname == "android.os.Bundle": return
1392 if clazz.fullname == "android.os.BaseBundle": return
1393 if clazz.fullname == "android.os.PersistableBundle": return
1394
1395 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1396 for b in bad:
1397 if clazz.name.endswith(b):
1398 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1399
1400
1401def verify_services(clazz):
1402 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1403 if clazz.fullname != "android.content.Context": return
1404
1405 for f in clazz.fields:
1406 if f.typ != "java.lang.String": continue
1407 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1408 if found:
1409 expected = found.group(1).lower()
1410 if f.value != expected:
1411 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1412
1413
1414def verify_tense(clazz):
1415 """Verify tenses of method names."""
1416 if clazz.fullname.startswith("android.opengl"): return
1417
1418 for m in clazz.methods:
1419 if m.name.endswith("Enable"):
1420 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1421
1422
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001423def verify_icu(clazz):
1424 """Verifies that richer ICU replacements are used."""
1425 better = {
1426 "java.util.TimeZone": "android.icu.util.TimeZone",
1427 "java.util.Calendar": "android.icu.util.Calendar",
1428 "java.util.Locale": "android.icu.util.ULocale",
1429 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1430 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1431 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1432 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1433 "java.lang.Character": "android.icu.lang.UCharacter",
1434 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1435 "java.text.Collator": "android.icu.text.Collator",
1436 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1437 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1438 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1439 "java.text.DateFormat": "android.icu.text.DateFormat",
1440 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1441 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1442 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1443 }
1444
1445 for m in clazz.ctors + clazz.methods:
1446 types = []
1447 types.extend(m.typ)
1448 types.extend(m.args)
1449 for arg in types:
1450 if arg in better:
1451 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1452
1453
1454def verify_clone(clazz):
1455 """Verify that clone() isn't implemented; see EJ page 61."""
1456 for m in clazz.methods:
1457 if m.name == "clone":
1458 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1459
1460
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001461def verify_pfd(clazz):
1462 """Verify that android APIs use PFD over FD."""
1463 examine = clazz.ctors + clazz.methods
1464 for m in examine:
1465 if m.typ == "java.io.FileDescriptor":
1466 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1467 if m.typ == "int":
1468 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1469 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1470 for arg in m.args:
1471 if arg == "java.io.FileDescriptor":
1472 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1473
1474 for f in clazz.fields:
1475 if f.typ == "java.io.FileDescriptor":
1476 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1477
1478
1479def verify_numbers(clazz):
1480 """Discourage small numbers types like short and byte."""
1481
1482 discouraged = ["short","byte"]
1483
1484 for c in clazz.ctors:
1485 for arg in c.args:
1486 if arg in discouraged:
1487 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1488
1489 for f in clazz.fields:
1490 if f.typ in discouraged:
1491 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1492
1493 for m in clazz.methods:
1494 if m.typ in discouraged:
1495 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1496 for arg in m.args:
1497 if arg in discouraged:
1498 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1499
1500
1501def verify_singleton(clazz):
1502 """Catch singleton objects with constructors."""
1503
1504 singleton = False
1505 for m in clazz.methods:
1506 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1507 singleton = True
1508
1509 if singleton:
1510 for c in clazz.ctors:
1511 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1512
1513
1514
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001515def is_interesting(clazz):
1516 """Test if given class is interesting from an Android PoV."""
1517
1518 if clazz.pkg.name.startswith("java"): return False
1519 if clazz.pkg.name.startswith("junit"): return False
1520 if clazz.pkg.name.startswith("org.apache"): return False
1521 if clazz.pkg.name.startswith("org.xml"): return False
1522 if clazz.pkg.name.startswith("org.json"): return False
1523 if clazz.pkg.name.startswith("org.w3c"): return False
1524 if clazz.pkg.name.startswith("android.icu."): return False
1525 return True
1526
1527
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001528def examine_clazz(clazz):
1529 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001530
1531 notice(clazz)
1532
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001533 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001534
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001535 verify_constants(clazz)
1536 verify_enums(clazz)
1537 verify_class_names(clazz)
1538 verify_method_names(clazz)
1539 verify_callbacks(clazz)
1540 verify_listeners(clazz)
1541 verify_actions(clazz)
1542 verify_extras(clazz)
1543 verify_equals(clazz)
1544 verify_parcelable(clazz)
1545 verify_protected(clazz)
1546 verify_fields(clazz)
1547 verify_register(clazz)
1548 verify_sync(clazz)
1549 verify_intent_builder(clazz)
1550 verify_helper_classes(clazz)
1551 verify_builder(clazz)
1552 verify_aidl(clazz)
1553 verify_internal(clazz)
1554 verify_layering(clazz)
1555 verify_boolean(clazz)
1556 verify_collections(clazz)
1557 verify_flags(clazz)
1558 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001559 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001560 verify_bitset(clazz)
1561 verify_manager(clazz)
1562 verify_boxed(clazz)
1563 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001564 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001565 verify_callback_handlers(clazz)
1566 verify_context_first(clazz)
1567 verify_listener_last(clazz)
1568 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001569 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001570 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001571 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001572 verify_runtime_exceptions(clazz)
1573 verify_error(clazz)
1574 verify_units(clazz)
1575 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001576 verify_member_name_not_kotlin_keyword(clazz)
1577 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001578 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001579 verify_user_handle(clazz)
1580 verify_params(clazz)
1581 verify_services(clazz)
1582 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001583 verify_icu(clazz)
1584 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001585 verify_pfd(clazz)
1586 verify_numbers(clazz)
1587 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001588
1589
Adrian Roos038a0292018-12-19 17:11:21 +01001590def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001591 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001592 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001593 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001594 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01001595 _parse_stream(stream, examine_clazz, base_f=base_stream,
1596 in_classes_with_base=in_classes_with_base,
1597 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001598 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001599
1600
1601def examine_api(api):
1602 """Find all style issues in the given parsed API."""
1603 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001604 failures = {}
1605 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001606 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001607 return failures
1608
1609
Jeff Sharkey037458a2014-09-04 15:46:20 -07001610def verify_compat(cur, prev):
1611 """Find any incompatible API changes between two levels."""
1612 global failures
1613
1614 def class_exists(api, test):
1615 return test.fullname in api
1616
1617 def ctor_exists(api, clazz, test):
1618 for m in clazz.ctors:
1619 if m.ident == test.ident: return True
1620 return False
1621
1622 def all_methods(api, clazz):
1623 methods = list(clazz.methods)
1624 if clazz.extends is not None:
1625 methods.extend(all_methods(api, api[clazz.extends]))
1626 return methods
1627
1628 def method_exists(api, clazz, test):
1629 methods = all_methods(api, clazz)
1630 for m in methods:
1631 if m.ident == test.ident: return True
1632 return False
1633
1634 def field_exists(api, clazz, test):
1635 for f in clazz.fields:
1636 if f.ident == test.ident: return True
1637 return False
1638
1639 failures = {}
1640 for key in sorted(prev.keys()):
1641 prev_clazz = prev[key]
1642
1643 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001644 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001645 continue
1646
1647 cur_clazz = cur[key]
1648
1649 for test in prev_clazz.ctors:
1650 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001651 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001652
1653 methods = all_methods(prev, prev_clazz)
1654 for test in methods:
1655 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001656 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001657
1658 for test in prev_clazz.fields:
1659 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001660 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001661
1662 return failures
1663
1664
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001665def show_deprecations_at_birth(cur, prev):
1666 """Show API deprecations at birth."""
1667 global failures
1668
1669 # Remove all existing things so we're left with new
1670 for prev_clazz in prev.values():
1671 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001672 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001673
1674 sigs = { i.ident: i for i in prev_clazz.ctors }
1675 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1676 sigs = { i.ident: i for i in prev_clazz.methods }
1677 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1678 sigs = { i.ident: i for i in prev_clazz.fields }
1679 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1680
1681 # Forget about class entirely when nothing new
1682 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1683 del cur[prev_clazz.fullname]
1684
1685 for clazz in cur.values():
1686 if " deprecated " in clazz.raw and not clazz.fullname in prev:
1687 error(clazz, None, None, "Found API deprecation at birth")
1688
1689 for i in clazz.ctors + clazz.methods + clazz.fields:
1690 if " deprecated " in i.raw:
1691 error(clazz, i, None, "Found API deprecation at birth")
1692
1693 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1694 format(reset=True)))
1695 for f in sorted(failures):
1696 print failures[f]
1697 print
1698
1699
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001700def show_stats(cur, prev):
1701 """Show API stats."""
1702
1703 stats = collections.defaultdict(int)
1704 for cur_clazz in cur.values():
1705 if not is_interesting(cur_clazz): continue
1706
1707 if cur_clazz.fullname not in prev:
1708 stats['new_classes'] += 1
1709 stats['new_ctors'] += len(cur_clazz.ctors)
1710 stats['new_methods'] += len(cur_clazz.methods)
1711 stats['new_fields'] += len(cur_clazz.fields)
1712 else:
1713 prev_clazz = prev[cur_clazz.fullname]
1714
1715 sigs = { i.ident: i for i in prev_clazz.ctors }
1716 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
1717 sigs = { i.ident: i for i in prev_clazz.methods }
1718 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
1719 sigs = { i.ident: i for i in prev_clazz.fields }
1720 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
1721
1722 if ctors + methods + fields > 0:
1723 stats['extend_classes'] += 1
1724 stats['extend_ctors'] += ctors
1725 stats['extend_methods'] += methods
1726 stats['extend_fields'] += fields
1727
1728 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
1729 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
1730
1731
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001732if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001733 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1734 patterns. It ignores lint messages from a previous API level, if provided.")
1735 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1736 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1737 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01001738 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
1739 help="The base current.txt to use when examining system-current.txt or"
1740 " test-current.txt")
1741 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
1742 help="The base previous.txt to use when examining system-previous.txt or"
1743 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001744 parser.add_argument("--no-color", action='store_const', const=True,
1745 help="Disable terminal colors")
1746 parser.add_argument("--allow-google", action='store_const', const=True,
1747 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001748 parser.add_argument("--show-noticed", action='store_const', const=True,
1749 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001750 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
1751 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001752 parser.add_argument("--show-stats", action='store_const', const=True,
1753 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001754 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001755
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001756 if args['no_color']:
1757 USE_COLOR = False
1758
1759 if args['allow_google']:
1760 ALLOW_GOOGLE = True
1761
1762 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01001763 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001764 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01001765 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001766
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001767 if args['show_deprecations_at_birth']:
1768 with current_file as f:
1769 cur = _parse_stream(f)
1770 with previous_file as f:
1771 prev = _parse_stream(f)
1772 show_deprecations_at_birth(cur, prev)
1773 sys.exit()
1774
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001775 if args['show_stats']:
1776 with current_file as f:
1777 cur = _parse_stream(f)
1778 with previous_file as f:
1779 prev = _parse_stream(f)
1780 show_stats(cur, prev)
1781 sys.exit()
1782
Adrian Roos038a0292018-12-19 17:11:21 +01001783 classes_with_base = []
1784
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001785 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01001786 if base_current_file:
1787 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01001788 cur_fail, cur_noticed = examine_stream(f, base_f,
1789 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01001790 else:
Adrian Roos038a0292018-12-19 17:11:21 +01001791 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
1792
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001793 if not previous_file is None:
1794 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01001795 if base_previous_file:
1796 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01001797 prev_fail, prev_noticed = examine_stream(f, base_f,
1798 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01001799 else:
Adrian Roos038a0292018-12-19 17:11:21 +01001800 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001801
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001802 # ignore errors from previous API level
1803 for p in prev_fail:
1804 if p in cur_fail:
1805 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001806
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001807 # ignore classes unchanged from previous API level
1808 for k, v in prev_noticed.iteritems():
1809 if k in cur_noticed and v == cur_noticed[k]:
1810 del cur_noticed[k]
1811
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001812 """
1813 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001814 # look for compatibility issues
1815 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001816
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001817 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1818 for f in sorted(compat_fail):
1819 print compat_fail[f]
1820 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001821 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001822
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001823 if args['show_noticed'] and len(cur_noticed) != 0:
1824 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1825 for f in sorted(cur_noticed.keys()):
1826 print f
1827 print
1828
Jason Monk53b2a732017-11-10 15:43:17 -05001829 if len(cur_fail) != 0:
1830 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1831 for f in sorted(cur_fail):
1832 print cur_fail[f]
1833 print
1834 sys.exit(77)