blob: b5a990e6e3cf2d3eb99678751658d7f41cb292c5 [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 Roos6eb57b02018-12-13 22:08:29 +0100212def _parse_stream(f, clazz_cb=None, base_f=None):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700213 api = {}
Adrian Roos6eb57b02018-12-13 22:08:29 +0100214
215 if base_f:
216 base_classes = _parse_stream_to_generator(base_f)
217 else:
218 base_classes = []
219
220 for clazz in _parse_stream_to_generator(f):
221 base_class = _parse_to_matching_class(base_classes, clazz)
222 if base_class:
223 clazz.merge_from(base_class)
224
225 if clazz_cb:
226 clazz_cb(clazz)
227 else: # In callback mode, don't keep track of the full API
228 api[clazz.fullname] = clazz
229
230 return api
231
232def _parse_stream_to_generator(f):
233 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700234 pkg = None
235 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700236 blame = None
237
238 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800239 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700240 line += 1
241 raw = raw.rstrip()
242 match = re_blame.match(raw)
243 if match is not None:
244 blame = match.groups()[0:2]
245 raw = match.groups()[2]
246 else:
247 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700248
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700249 if raw.startswith("package"):
250 pkg = Package(line, raw, blame)
251 elif raw.startswith(" ") and raw.endswith("{"):
252 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700253 elif raw.startswith(" ctor"):
254 clazz.ctors.append(Method(clazz, line, raw, blame))
255 elif raw.startswith(" method"):
256 clazz.methods.append(Method(clazz, line, raw, blame))
257 elif raw.startswith(" field"):
258 clazz.fields.append(Field(clazz, line, raw, blame))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100259 elif raw.startswith(" }") and clazz:
260 while True:
261 retry = yield clazz
262 if not retry:
263 break
264 # send() was called, asking us to redeliver clazz on next(). Still need to yield
265 # a dummy value to the send() first though.
266 if (yield "Returning clazz on next()"):
267 raise TypeError("send() must be followed by next(), not send()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700268
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800269
Adrian Roos6eb57b02018-12-13 22:08:29 +0100270def _parse_to_matching_class(classes, needle):
271 """Takes a classes generator and parses it until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700272
Adrian Roos6eb57b02018-12-13 22:08:29 +0100273 This relies on classes being sorted by package and class name."""
274
275 for clazz in classes:
276 if clazz.pkg.name < needle.pkg.name:
277 # We haven't reached the right package yet
278 continue
279 if clazz.name < needle.name:
280 # We haven't reached the right class yet
281 continue
282 if clazz.fullname == needle.fullname:
283 return clazz
284 # We ran past the right class. Send it back into the generator, then report failure.
285 classes.send(clazz)
286 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700287
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700288class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800289 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700290 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700291 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800292 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700293 self.msg = msg
294
295 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800296 self.head = "Error %s" % (rule) if rule else "Error"
297 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 -0700298 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800299 self.head = "Warning %s" % (rule) if rule else "Warning"
300 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 -0700301
302 self.line = clazz.line
303 blame = clazz.blame
304 if detail is not None:
305 dump += "\n in " + repr(detail)
306 self.line = detail.line
307 blame = detail.blame
308 dump += "\n in " + repr(clazz)
309 dump += "\n in " + repr(clazz.pkg)
310 dump += "\n at line " + repr(self.line)
311 if blame is not None:
312 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
313
314 self.dump = dump
315
316 def __repr__(self):
317 return self.dump
318
319
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700320failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700321
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800322def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700323 """Records an API failure to be processed later."""
324 global failures
325
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700326 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
327 sig = sig.replace(" deprecated ", " ")
328
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800329 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700330
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700331
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800332def warn(clazz, detail, rule, msg):
333 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700334
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800335def error(clazz, detail, rule, msg):
336 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700337
338
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700339noticed = {}
340
341def notice(clazz):
342 global noticed
343
344 noticed[clazz.fullname] = hash(clazz)
345
346
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700347def verify_constants(clazz):
348 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700349 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600350 if clazz.fullname.startswith("android.os.Build"): return
351 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700352
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600353 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700354 for f in clazz.fields:
355 if "static" in f.split and "final" in f.split:
356 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800357 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600358 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700359 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
360 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600361 if f.typ in req and f.value is None:
362 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700363
364
365def verify_enums(clazz):
366 """Enums are bad, mmkay?"""
367 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800368 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700369
370
371def verify_class_names(clazz):
372 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700373 if clazz.fullname.startswith("android.opengl"): return
374 if clazz.fullname.startswith("android.renderscript"): return
375 if re.match("android\.R\.[a-z]+", clazz.fullname): return
376
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700377 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800378 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700379 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800380 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700381 if clazz.name.endswith("Impl"):
382 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700383
384
385def verify_method_names(clazz):
386 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700387 if clazz.fullname.startswith("android.opengl"): return
388 if clazz.fullname.startswith("android.renderscript"): return
389 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700390
391 for m in clazz.methods:
392 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800393 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700394 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800395 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700396
397
398def verify_callbacks(clazz):
399 """Verify Callback classes.
400 All callback classes must be abstract.
401 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700402 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700403
404 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800405 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700406 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800407 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700408
409 if clazz.name.endswith("Callback"):
410 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800411 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700412
413 for m in clazz.methods:
414 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800415 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700416
417
418def verify_listeners(clazz):
419 """Verify Listener classes.
420 All Listener classes must be interface.
421 All methods must follow onFoo() naming style.
422 If only a single method, it must match class name:
423 interface OnFooListener { void onFoo() }"""
424
425 if clazz.name.endswith("Listener"):
426 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800427 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700428
429 for m in clazz.methods:
430 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800431 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700432
433 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
434 m = clazz.methods[0]
435 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800436 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700437
438
439def verify_actions(clazz):
440 """Verify intent actions.
441 All action names must be named ACTION_FOO.
442 All action values must be scoped by package and match name:
443 package android.foo {
444 String ACTION_BAR = "android.foo.action.BAR";
445 }"""
446 for f in clazz.fields:
447 if f.value is None: continue
448 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700449 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600450 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700451
452 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
453 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
454 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800455 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700456 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700457 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700458 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700459 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700460 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700461 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
462 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700463 else:
464 prefix = clazz.pkg.name + ".action"
465 expected = prefix + "." + f.name[7:]
466 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700467 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700468
469
470def verify_extras(clazz):
471 """Verify intent extras.
472 All extra names must be named EXTRA_FOO.
473 All extra values must be scoped by package and match name:
474 package android.foo {
475 String EXTRA_BAR = "android.foo.extra.BAR";
476 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700477 if clazz.fullname == "android.app.Notification": return
478 if clazz.fullname == "android.appwidget.AppWidgetManager": return
479
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700480 for f in clazz.fields:
481 if f.value is None: continue
482 if f.name.startswith("ACTION_"): continue
483
484 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
485 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
486 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800487 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700488 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700489 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700490 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700491 elif clazz.pkg.name == "android.app.admin":
492 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700493 else:
494 prefix = clazz.pkg.name + ".extra"
495 expected = prefix + "." + f.name[6:]
496 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700497 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700498
499
500def verify_equals(clazz):
501 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700502 eq = False
503 hc = False
504 for m in clazz.methods:
505 if " static " in m.raw: continue
506 if "boolean equals(java.lang.Object)" in m.raw: eq = True
507 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700508 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800509 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700510
511
512def verify_parcelable(clazz):
513 """Verify that Parcelable objects aren't hiding required bits."""
514 if "implements android.os.Parcelable" in clazz.raw:
515 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
516 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
517 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
518
519 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800520 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700521
Joe LaPenna45380002017-04-20 12:49:48 -0700522 if ((" final class " not in clazz.raw) and
523 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700524 error(clazz, None, "FW8", "Parcelable classes must be final")
525
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700526 for c in clazz.ctors:
527 if c.args == ["android.os.Parcel"]:
528 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
529
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700530
531def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800532 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700533 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600534 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700535 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800536 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700537 for f in clazz.fields:
538 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800539 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700540
541
542def verify_fields(clazz):
543 """Verify that all exposed fields are final.
544 Exposed fields must follow myName style.
545 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700546
547 IGNORE_BARE_FIELDS = [
548 "android.app.ActivityManager.RecentTaskInfo",
549 "android.app.Notification",
550 "android.content.pm.ActivityInfo",
551 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600552 "android.content.pm.ComponentInfo",
553 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700554 "android.content.pm.FeatureGroupInfo",
555 "android.content.pm.InstrumentationInfo",
556 "android.content.pm.PackageInfo",
557 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600558 "android.content.res.Configuration",
559 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700560 "android.os.Message",
561 "android.system.StructPollfd",
562 ]
563
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700564 for f in clazz.fields:
565 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700566 if clazz.fullname in IGNORE_BARE_FIELDS:
567 pass
568 elif clazz.fullname.endswith("LayoutParams"):
569 pass
570 elif clazz.fullname.startswith("android.util.Mutable"):
571 pass
572 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800573 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700574
575 if not "static" in f.split:
576 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800577 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700578
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700579 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800580 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700581
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700582 if re.match("[A-Z_]+", f.name):
583 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800584 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700585
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700586
587def verify_register(clazz):
588 """Verify parity of registration methods.
589 Callback objects use register/unregister methods.
590 Listener objects use add/remove methods."""
591 methods = [ m.name for m in clazz.methods ]
592 for m in clazz.methods:
593 if "Callback" in m.raw:
594 if m.name.startswith("register"):
595 other = "unregister" + m.name[8:]
596 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800597 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700598 if m.name.startswith("unregister"):
599 other = "register" + m.name[10:]
600 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800601 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700602
603 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800604 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700605
606 if "Listener" in m.raw:
607 if m.name.startswith("add"):
608 other = "remove" + m.name[3:]
609 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800610 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700611 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
612 other = "add" + m.name[6:]
613 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800614 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700615
616 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800617 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700618
619
620def verify_sync(clazz):
621 """Verify synchronized methods aren't exposed."""
622 for m in clazz.methods:
623 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800624 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700625
626
627def verify_intent_builder(clazz):
628 """Verify that Intent builders are createFooIntent() style."""
629 if clazz.name == "Intent": return
630
631 for m in clazz.methods:
632 if m.typ == "android.content.Intent":
633 if m.name.startswith("create") and m.name.endswith("Intent"):
634 pass
635 else:
Adam Powell539ea122015-04-10 13:01:37 -0700636 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700637
638
639def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700640 """Verify that helper classes are named consistently with what they extend.
641 All developer extendable methods should be named onFoo()."""
642 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700643 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700644 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700645 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800646 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700647
648 found = False
649 for f in clazz.fields:
650 if f.name == "SERVICE_INTERFACE":
651 found = True
652 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700653 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700654
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700655 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700656 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700657 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800658 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700659
660 found = False
661 for f in clazz.fields:
662 if f.name == "PROVIDER_INTERFACE":
663 found = True
664 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700665 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700666
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700667 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700668 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700669 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800670 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700671
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700672 if "extends android.app.Activity" in clazz.raw:
673 test_methods = True
674 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800675 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700676
677 if test_methods:
678 for m in clazz.methods:
679 if "final" in m.split: continue
680 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700681 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800682 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700683 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800684 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700685
686
687def verify_builder(clazz):
688 """Verify builder classes.
689 Methods should return the builder to enable chaining."""
690 if " extends " in clazz.raw: return
691 if not clazz.name.endswith("Builder"): return
692
693 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800694 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700695
696 has_build = False
697 for m in clazz.methods:
698 if m.name == "build":
699 has_build = True
700 continue
701
702 if m.name.startswith("get"): continue
703 if m.name.startswith("clear"): continue
704
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700705 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800706 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700707
708 if m.name.startswith("set"):
709 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800710 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700711
712 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800713 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700714
715
716def verify_aidl(clazz):
717 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700718 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800719 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700720
721
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700722def verify_internal(clazz):
723 """Catch people exposing internal classes."""
724 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800725 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700726
727
728def verify_layering(clazz):
729 """Catch package layering violations.
730 For example, something in android.os depending on android.app."""
731 ranking = [
732 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
733 "android.app",
734 "android.widget",
735 "android.view",
736 "android.animation",
737 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700738 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700739 "android.database",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700740 "android.text",
Siyamed Sinir0a2e15d2018-09-13 16:06:59 -0700741 "android.graphics",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700742 "android.os",
743 "android.util"
744 ]
745
746 def rank(p):
747 for i in range(len(ranking)):
748 if isinstance(ranking[i], list):
749 for j in ranking[i]:
750 if p.startswith(j): return i
751 else:
752 if p.startswith(ranking[i]): return i
753
754 cr = rank(clazz.pkg.name)
755 if cr is None: return
756
757 for f in clazz.fields:
758 ir = rank(f.typ)
759 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800760 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700761
762 for m in clazz.methods:
763 ir = rank(m.typ)
764 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800765 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700766 for arg in m.args:
767 ir = rank(arg)
768 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800769 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700770
771
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800772def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800773 """Verifies that boolean accessors are named correctly.
774 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700775
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800776 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
777 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700778
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800779 gets = [ m for m in clazz.methods if is_get(m) ]
780 sets = [ m for m in clazz.methods if is_set(m) ]
781
782 def error_if_exists(methods, trigger, expected, actual):
783 for m in methods:
784 if m.name == actual:
785 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700786
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700787 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800788 if is_get(m):
789 if re.match("is[A-Z]", m.name):
790 target = m.name[2:]
791 expected = "setIs" + target
792 error_if_exists(sets, m.name, expected, "setHas" + target)
793 elif re.match("has[A-Z]", m.name):
794 target = m.name[3:]
795 expected = "setHas" + target
796 error_if_exists(sets, m.name, expected, "setIs" + target)
797 error_if_exists(sets, m.name, expected, "set" + target)
798 elif re.match("get[A-Z]", m.name):
799 target = m.name[3:]
800 expected = "set" + target
801 error_if_exists(sets, m.name, expected, "setIs" + target)
802 error_if_exists(sets, m.name, expected, "setHas" + target)
803
804 if is_set(m):
805 if re.match("set[A-Z]", m.name):
806 target = m.name[3:]
807 expected = "get" + target
808 error_if_exists(sets, m.name, expected, "is" + target)
809 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700810
811
812def verify_collections(clazz):
813 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700814 if clazz.fullname == "android.os.Bundle": return
815
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700816 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
817 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
818 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700819 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800820 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700821 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700822 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800823 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700824
825
826def verify_flags(clazz):
827 """Verifies that flags are non-overlapping."""
828 known = collections.defaultdict(int)
829 for f in clazz.fields:
830 if "FLAG_" in f.name:
831 try:
832 val = int(f.value)
833 except:
834 continue
835
836 scope = f.name[0:f.name.index("FLAG_")]
837 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800838 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700839 known[scope] |= val
840
841
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800842def verify_exception(clazz):
843 """Verifies that methods don't throw generic exceptions."""
844 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700845 for t in m.throws:
846 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
847 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800848
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700849 if t in ["android.os.RemoteException"]:
850 if clazz.name == "android.content.ContentProviderClient": continue
851 if clazz.name == "android.os.Binder": continue
852 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700853
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700854 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
855
856 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
857 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700858
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800859
860def verify_google(clazz):
861 """Verifies that APIs never reference Google."""
862
863 if re.search("google", clazz.raw, re.IGNORECASE):
864 error(clazz, None, None, "Must never reference Google")
865
866 test = []
867 test.extend(clazz.ctors)
868 test.extend(clazz.fields)
869 test.extend(clazz.methods)
870
871 for t in test:
872 if re.search("google", t.raw, re.IGNORECASE):
873 error(clazz, t, None, "Must never reference Google")
874
875
876def verify_bitset(clazz):
877 """Verifies that we avoid using heavy BitSet."""
878
879 for f in clazz.fields:
880 if f.typ == "java.util.BitSet":
881 error(clazz, f, None, "Field type must not be heavy BitSet")
882
883 for m in clazz.methods:
884 if m.typ == "java.util.BitSet":
885 error(clazz, m, None, "Return type must not be heavy BitSet")
886 for arg in m.args:
887 if arg == "java.util.BitSet":
888 error(clazz, m, None, "Argument type must not be heavy BitSet")
889
890
891def verify_manager(clazz):
892 """Verifies that FooManager is only obtained from Context."""
893
894 if not clazz.name.endswith("Manager"): return
895
896 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800897 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800898
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600899 for m in clazz.methods:
900 if m.typ == clazz.fullname:
901 error(clazz, m, None, "Managers must always be obtained from Context")
902
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800903
904def verify_boxed(clazz):
905 """Verifies that methods avoid boxed primitives."""
906
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800907 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 -0800908
909 for c in clazz.ctors:
910 for arg in c.args:
911 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800912 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800913
914 for f in clazz.fields:
915 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800916 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800917
918 for m in clazz.methods:
919 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800920 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800921 for arg in m.args:
922 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800923 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800924
925
926def verify_static_utils(clazz):
927 """Verifies that helper classes can't be constructed."""
928 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600929 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800930
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600931 # Only care about classes with default constructors
932 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
933 test = []
934 test.extend(clazz.fields)
935 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800936
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600937 if len(test) == 0: return
938 for t in test:
939 if "static" not in t.split:
940 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800941
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800942 error(clazz, None, None, "Fully-static utility classes must not have constructor")
943
944
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800945def verify_overload_args(clazz):
946 """Verifies that method overloads add new arguments at the end."""
947 if clazz.fullname.startswith("android.opengl"): return
948
949 overloads = collections.defaultdict(list)
950 for m in clazz.methods:
951 if "deprecated" in m.split: continue
952 overloads[m.name].append(m)
953
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800954 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800955 if len(methods) <= 1: continue
956
957 # Look for arguments common across all overloads
958 def cluster(args):
959 count = collections.defaultdict(int)
960 res = set()
961 for i in range(len(args)):
962 a = args[i]
963 res.add("%s#%d" % (a, count[a]))
964 count[a] += 1
965 return res
966
967 common_args = cluster(methods[0].args)
968 for m in methods:
969 common_args = common_args & cluster(m.args)
970
971 if len(common_args) == 0: continue
972
973 # Require that all common arguments are present at start of signature
974 locked_sig = None
975 for m in methods:
976 sig = m.args[0:len(common_args)]
977 if not common_args.issubset(cluster(sig)):
978 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
979 elif not locked_sig:
980 locked_sig = sig
981 elif locked_sig != sig:
982 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
983
984
985def verify_callback_handlers(clazz):
986 """Verifies that methods adding listener/callback have overload
987 for specifying delivery thread."""
988
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800989 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800990 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800991 "animation",
992 "view",
993 "graphics",
994 "transition",
995 "widget",
996 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800997 ]
998 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800999 if s in clazz.pkg.name_path: return
1000 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001001
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001002 # Ignore UI classes which assume main thread
1003 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1004 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1005 if s in clazz.fullname: return
1006 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1007 for s in ["Loader"]:
1008 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001009
1010 found = {}
1011 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001012 examine = clazz.ctors + clazz.methods
1013 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001014 if m.name.startswith("unregister"): continue
1015 if m.name.startswith("remove"): continue
1016 if re.match("on[A-Z]+", m.name): continue
1017
1018 by_name[m.name].append(m)
1019
1020 for a in m.args:
1021 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1022 found[m.name] = m
1023
1024 for f in found.values():
1025 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001026 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001027 for m in by_name[f.name]:
1028 if "android.os.Handler" in m.args:
1029 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001030 if "java.util.concurrent.Executor" in m.args:
1031 takes_exec = True
1032 if not takes_exec:
1033 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001034
1035
1036def verify_context_first(clazz):
1037 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001038 examine = clazz.ctors + clazz.methods
1039 for m in examine:
1040 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001041 if "android.content.Context" in m.args[1:]:
1042 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001043 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1044 if "android.content.ContentResolver" in m.args[1:]:
1045 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001046
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001047
1048def verify_listener_last(clazz):
1049 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1050 examine = clazz.ctors + clazz.methods
1051 for m in examine:
1052 if "Listener" in m.name or "Callback" in m.name: continue
1053 found = False
1054 for a in m.args:
1055 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1056 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001057 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001058 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1059
1060
1061def verify_resource_names(clazz):
1062 """Verifies that resource names have consistent case."""
1063 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1064
1065 # Resources defined by files are foo_bar_baz
1066 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1067 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001068 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1069 if f.name.startswith("config_"):
1070 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1071
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001072 if re.match("[a-z1-9_]+$", f.name): continue
1073 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1074
1075 # Resources defined inside files are fooBarBaz
1076 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1077 for f in clazz.fields:
1078 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1079 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1080 if re.match("state_[a-z_]*$", f.name): continue
1081
1082 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1083 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1084
1085 # Styles are FooBar_Baz
1086 if clazz.name in ["style"]:
1087 for f in clazz.fields:
1088 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1089 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001090
1091
Jeff Sharkey331279b2016-02-29 16:02:02 -07001092def verify_files(clazz):
1093 """Verifies that methods accepting File also accept streams."""
1094
1095 has_file = set()
1096 has_stream = set()
1097
1098 test = []
1099 test.extend(clazz.ctors)
1100 test.extend(clazz.methods)
1101
1102 for m in test:
1103 if "java.io.File" in m.args:
1104 has_file.add(m)
1105 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:
1106 has_stream.add(m.name)
1107
1108 for m in has_file:
1109 if m.name not in has_stream:
1110 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1111
1112
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001113def verify_manager_list(clazz):
1114 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1115
1116 if not clazz.name.endswith("Manager"): return
1117
1118 for m in clazz.methods:
1119 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1120 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1121
1122
Jeff Sharkey26c80902016-12-21 13:41:17 -07001123def verify_abstract_inner(clazz):
1124 """Verifies that abstract inner classes are static."""
1125
1126 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1127 if " abstract " in clazz.raw and " static " not in clazz.raw:
1128 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1129
1130
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001131def verify_runtime_exceptions(clazz):
1132 """Verifies that runtime exceptions aren't listed in throws."""
1133
1134 banned = [
1135 "java.lang.NullPointerException",
1136 "java.lang.ClassCastException",
1137 "java.lang.IndexOutOfBoundsException",
1138 "java.lang.reflect.UndeclaredThrowableException",
1139 "java.lang.reflect.MalformedParametersException",
1140 "java.lang.reflect.MalformedParameterizedTypeException",
1141 "java.lang.invoke.WrongMethodTypeException",
1142 "java.lang.EnumConstantNotPresentException",
1143 "java.lang.IllegalMonitorStateException",
1144 "java.lang.SecurityException",
1145 "java.lang.UnsupportedOperationException",
1146 "java.lang.annotation.AnnotationTypeMismatchException",
1147 "java.lang.annotation.IncompleteAnnotationException",
1148 "java.lang.TypeNotPresentException",
1149 "java.lang.IllegalStateException",
1150 "java.lang.ArithmeticException",
1151 "java.lang.IllegalArgumentException",
1152 "java.lang.ArrayStoreException",
1153 "java.lang.NegativeArraySizeException",
1154 "java.util.MissingResourceException",
1155 "java.util.EmptyStackException",
1156 "java.util.concurrent.CompletionException",
1157 "java.util.concurrent.RejectedExecutionException",
1158 "java.util.IllformedLocaleException",
1159 "java.util.ConcurrentModificationException",
1160 "java.util.NoSuchElementException",
1161 "java.io.UncheckedIOException",
1162 "java.time.DateTimeException",
1163 "java.security.ProviderException",
1164 "java.nio.BufferUnderflowException",
1165 "java.nio.BufferOverflowException",
1166 ]
1167
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001168 examine = clazz.ctors + clazz.methods
1169 for m in examine:
1170 for t in m.throws:
1171 if t in banned:
1172 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001173
1174
1175def verify_error(clazz):
1176 """Verifies that we always use Exception instead of Error."""
1177 if not clazz.extends: return
1178 if clazz.extends.endswith("Error"):
1179 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1180 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1181 error(clazz, None, None, "Exceptions must be named FooException")
1182
1183
1184def verify_units(clazz):
1185 """Verifies that we use consistent naming for units."""
1186
1187 # If we find K, recommend replacing with V
1188 bad = {
1189 "Ns": "Nanos",
1190 "Ms": "Millis or Micros",
1191 "Sec": "Seconds", "Secs": "Seconds",
1192 "Hr": "Hours", "Hrs": "Hours",
1193 "Mo": "Months", "Mos": "Months",
1194 "Yr": "Years", "Yrs": "Years",
1195 "Byte": "Bytes", "Space": "Bytes",
1196 }
1197
1198 for m in clazz.methods:
1199 if m.typ not in ["short","int","long"]: continue
1200 for k, v in bad.iteritems():
1201 if m.name.endswith(k):
1202 error(clazz, m, None, "Expected method name units to be " + v)
1203 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1204 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1205 if m.name.endswith("Seconds"):
1206 error(clazz, m, None, "Returned time values must be in milliseconds")
1207
1208 for m in clazz.methods:
1209 typ = m.typ
1210 if typ == "void":
1211 if len(m.args) != 1: continue
1212 typ = m.args[0]
1213
1214 if m.name.endswith("Fraction") and typ != "float":
1215 error(clazz, m, None, "Fractions must use floats")
1216 if m.name.endswith("Percentage") and typ != "int":
1217 error(clazz, m, None, "Percentage must use ints")
1218
1219
1220def verify_closable(clazz):
1221 """Verifies that classes are AutoClosable."""
1222 if "implements java.lang.AutoCloseable" in clazz.raw: return
1223 if "implements java.io.Closeable" in clazz.raw: return
1224
1225 for m in clazz.methods:
1226 if len(m.args) > 0: continue
1227 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1228 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1229 return
1230
1231
Jake Wharton9e6738f2017-08-23 11:59:55 -04001232def verify_member_name_not_kotlin_keyword(clazz):
1233 """Prevent method names which are keywords in Kotlin."""
1234
1235 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1236 # This list does not include Java keywords as those are already impossible to use.
1237 keywords = [
1238 'as',
1239 'fun',
1240 'in',
1241 'is',
1242 'object',
1243 'typealias',
1244 'val',
1245 'var',
1246 'when',
1247 ]
1248
1249 for m in clazz.methods:
1250 if m.name in keywords:
1251 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1252 for f in clazz.fields:
1253 if f.name in keywords:
1254 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1255
1256
1257def verify_method_name_not_kotlin_operator(clazz):
1258 """Warn about method names which become operators in Kotlin."""
1259
1260 binary = set()
1261
1262 def unique_binary_op(m, op):
1263 if op in binary:
1264 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1265 binary.add(op)
1266
1267 for m in clazz.methods:
1268 if 'static' in m.split:
1269 continue
1270
1271 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1272 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1273 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1274
1275 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1276 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1277 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1278 # practical way of checking that relationship here.
1279 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1280
1281 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1282 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1283 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1284 unique_binary_op(m, m.name)
1285
1286 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1287 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1288 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1289
1290 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1291 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1292 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1293
1294 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1295 if m.name == 'invoke':
1296 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1297
1298 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1299 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1300 and len(m.args) == 1 \
1301 and m.typ == 'void':
1302 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1303 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1304
1305
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001306def verify_collections_over_arrays(clazz):
1307 """Warn that [] should be Collections."""
1308
1309 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1310 for m in clazz.methods:
1311 if m.typ.endswith("[]") and m.typ not in safe:
1312 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1313 for arg in m.args:
1314 if arg.endswith("[]") and arg not in safe:
1315 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1316
1317
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001318def verify_user_handle(clazz):
1319 """Methods taking UserHandle should be ForUser or AsUser."""
1320 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1321 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1322 if clazz.fullname == "android.content.pm.LauncherApps": return
1323 if clazz.fullname == "android.os.UserHandle": return
1324 if clazz.fullname == "android.os.UserManager": return
1325
1326 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001327 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001328
1329 has_arg = "android.os.UserHandle" in m.args
1330 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1331
1332 if clazz.fullname.endswith("Manager") and has_arg:
1333 warn(clazz, m, None, "When a method overload is needed to target a specific "
1334 "UserHandle, callers should be directed to use "
1335 "Context.createPackageContextAsUser() and re-obtain the relevant "
1336 "Manager, and no new API should be added")
1337 elif has_arg and not has_name:
1338 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1339 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001340
1341
1342def verify_params(clazz):
1343 """Parameter classes should be 'Params'."""
1344 if clazz.name.endswith("Params"): return
1345 if clazz.fullname == "android.app.ActivityOptions": return
1346 if clazz.fullname == "android.app.BroadcastOptions": return
1347 if clazz.fullname == "android.os.Bundle": return
1348 if clazz.fullname == "android.os.BaseBundle": return
1349 if clazz.fullname == "android.os.PersistableBundle": return
1350
1351 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1352 for b in bad:
1353 if clazz.name.endswith(b):
1354 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1355
1356
1357def verify_services(clazz):
1358 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1359 if clazz.fullname != "android.content.Context": return
1360
1361 for f in clazz.fields:
1362 if f.typ != "java.lang.String": continue
1363 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1364 if found:
1365 expected = found.group(1).lower()
1366 if f.value != expected:
1367 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1368
1369
1370def verify_tense(clazz):
1371 """Verify tenses of method names."""
1372 if clazz.fullname.startswith("android.opengl"): return
1373
1374 for m in clazz.methods:
1375 if m.name.endswith("Enable"):
1376 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1377
1378
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001379def verify_icu(clazz):
1380 """Verifies that richer ICU replacements are used."""
1381 better = {
1382 "java.util.TimeZone": "android.icu.util.TimeZone",
1383 "java.util.Calendar": "android.icu.util.Calendar",
1384 "java.util.Locale": "android.icu.util.ULocale",
1385 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1386 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1387 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1388 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1389 "java.lang.Character": "android.icu.lang.UCharacter",
1390 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1391 "java.text.Collator": "android.icu.text.Collator",
1392 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1393 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1394 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1395 "java.text.DateFormat": "android.icu.text.DateFormat",
1396 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1397 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1398 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1399 }
1400
1401 for m in clazz.ctors + clazz.methods:
1402 types = []
1403 types.extend(m.typ)
1404 types.extend(m.args)
1405 for arg in types:
1406 if arg in better:
1407 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1408
1409
1410def verify_clone(clazz):
1411 """Verify that clone() isn't implemented; see EJ page 61."""
1412 for m in clazz.methods:
1413 if m.name == "clone":
1414 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1415
1416
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001417def verify_pfd(clazz):
1418 """Verify that android APIs use PFD over FD."""
1419 examine = clazz.ctors + clazz.methods
1420 for m in examine:
1421 if m.typ == "java.io.FileDescriptor":
1422 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1423 if m.typ == "int":
1424 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1425 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1426 for arg in m.args:
1427 if arg == "java.io.FileDescriptor":
1428 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1429
1430 for f in clazz.fields:
1431 if f.typ == "java.io.FileDescriptor":
1432 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1433
1434
1435def verify_numbers(clazz):
1436 """Discourage small numbers types like short and byte."""
1437
1438 discouraged = ["short","byte"]
1439
1440 for c in clazz.ctors:
1441 for arg in c.args:
1442 if arg in discouraged:
1443 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1444
1445 for f in clazz.fields:
1446 if f.typ in discouraged:
1447 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1448
1449 for m in clazz.methods:
1450 if m.typ in discouraged:
1451 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1452 for arg in m.args:
1453 if arg in discouraged:
1454 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1455
1456
1457def verify_singleton(clazz):
1458 """Catch singleton objects with constructors."""
1459
1460 singleton = False
1461 for m in clazz.methods:
1462 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1463 singleton = True
1464
1465 if singleton:
1466 for c in clazz.ctors:
1467 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1468
1469
1470
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001471def is_interesting(clazz):
1472 """Test if given class is interesting from an Android PoV."""
1473
1474 if clazz.pkg.name.startswith("java"): return False
1475 if clazz.pkg.name.startswith("junit"): return False
1476 if clazz.pkg.name.startswith("org.apache"): return False
1477 if clazz.pkg.name.startswith("org.xml"): return False
1478 if clazz.pkg.name.startswith("org.json"): return False
1479 if clazz.pkg.name.startswith("org.w3c"): return False
1480 if clazz.pkg.name.startswith("android.icu."): return False
1481 return True
1482
1483
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001484def examine_clazz(clazz):
1485 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001486
1487 notice(clazz)
1488
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001489 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001490
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001491 verify_constants(clazz)
1492 verify_enums(clazz)
1493 verify_class_names(clazz)
1494 verify_method_names(clazz)
1495 verify_callbacks(clazz)
1496 verify_listeners(clazz)
1497 verify_actions(clazz)
1498 verify_extras(clazz)
1499 verify_equals(clazz)
1500 verify_parcelable(clazz)
1501 verify_protected(clazz)
1502 verify_fields(clazz)
1503 verify_register(clazz)
1504 verify_sync(clazz)
1505 verify_intent_builder(clazz)
1506 verify_helper_classes(clazz)
1507 verify_builder(clazz)
1508 verify_aidl(clazz)
1509 verify_internal(clazz)
1510 verify_layering(clazz)
1511 verify_boolean(clazz)
1512 verify_collections(clazz)
1513 verify_flags(clazz)
1514 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001515 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001516 verify_bitset(clazz)
1517 verify_manager(clazz)
1518 verify_boxed(clazz)
1519 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001520 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001521 verify_callback_handlers(clazz)
1522 verify_context_first(clazz)
1523 verify_listener_last(clazz)
1524 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001525 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001526 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001527 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001528 verify_runtime_exceptions(clazz)
1529 verify_error(clazz)
1530 verify_units(clazz)
1531 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001532 verify_member_name_not_kotlin_keyword(clazz)
1533 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001534 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001535 verify_user_handle(clazz)
1536 verify_params(clazz)
1537 verify_services(clazz)
1538 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001539 verify_icu(clazz)
1540 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001541 verify_pfd(clazz)
1542 verify_numbers(clazz)
1543 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001544
1545
Adrian Roos6eb57b02018-12-13 22:08:29 +01001546def examine_stream(stream, base_stream=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001547 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001548 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001549 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001550 noticed = {}
Adrian Roos6eb57b02018-12-13 22:08:29 +01001551 _parse_stream(stream, examine_clazz, base_f=base_stream)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001552 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001553
1554
1555def examine_api(api):
1556 """Find all style issues in the given parsed API."""
1557 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001558 failures = {}
1559 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001560 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001561 return failures
1562
1563
Jeff Sharkey037458a2014-09-04 15:46:20 -07001564def verify_compat(cur, prev):
1565 """Find any incompatible API changes between two levels."""
1566 global failures
1567
1568 def class_exists(api, test):
1569 return test.fullname in api
1570
1571 def ctor_exists(api, clazz, test):
1572 for m in clazz.ctors:
1573 if m.ident == test.ident: return True
1574 return False
1575
1576 def all_methods(api, clazz):
1577 methods = list(clazz.methods)
1578 if clazz.extends is not None:
1579 methods.extend(all_methods(api, api[clazz.extends]))
1580 return methods
1581
1582 def method_exists(api, clazz, test):
1583 methods = all_methods(api, clazz)
1584 for m in methods:
1585 if m.ident == test.ident: return True
1586 return False
1587
1588 def field_exists(api, clazz, test):
1589 for f in clazz.fields:
1590 if f.ident == test.ident: return True
1591 return False
1592
1593 failures = {}
1594 for key in sorted(prev.keys()):
1595 prev_clazz = prev[key]
1596
1597 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001598 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001599 continue
1600
1601 cur_clazz = cur[key]
1602
1603 for test in prev_clazz.ctors:
1604 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001605 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001606
1607 methods = all_methods(prev, prev_clazz)
1608 for test in methods:
1609 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001610 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001611
1612 for test in prev_clazz.fields:
1613 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001614 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001615
1616 return failures
1617
1618
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001619def show_deprecations_at_birth(cur, prev):
1620 """Show API deprecations at birth."""
1621 global failures
1622
1623 # Remove all existing things so we're left with new
1624 for prev_clazz in prev.values():
1625 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001626 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001627
1628 sigs = { i.ident: i for i in prev_clazz.ctors }
1629 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1630 sigs = { i.ident: i for i in prev_clazz.methods }
1631 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1632 sigs = { i.ident: i for i in prev_clazz.fields }
1633 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1634
1635 # Forget about class entirely when nothing new
1636 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1637 del cur[prev_clazz.fullname]
1638
1639 for clazz in cur.values():
1640 if " deprecated " in clazz.raw and not clazz.fullname in prev:
1641 error(clazz, None, None, "Found API deprecation at birth")
1642
1643 for i in clazz.ctors + clazz.methods + clazz.fields:
1644 if " deprecated " in i.raw:
1645 error(clazz, i, None, "Found API deprecation at birth")
1646
1647 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1648 format(reset=True)))
1649 for f in sorted(failures):
1650 print failures[f]
1651 print
1652
1653
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001654def show_stats(cur, prev):
1655 """Show API stats."""
1656
1657 stats = collections.defaultdict(int)
1658 for cur_clazz in cur.values():
1659 if not is_interesting(cur_clazz): continue
1660
1661 if cur_clazz.fullname not in prev:
1662 stats['new_classes'] += 1
1663 stats['new_ctors'] += len(cur_clazz.ctors)
1664 stats['new_methods'] += len(cur_clazz.methods)
1665 stats['new_fields'] += len(cur_clazz.fields)
1666 else:
1667 prev_clazz = prev[cur_clazz.fullname]
1668
1669 sigs = { i.ident: i for i in prev_clazz.ctors }
1670 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
1671 sigs = { i.ident: i for i in prev_clazz.methods }
1672 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
1673 sigs = { i.ident: i for i in prev_clazz.fields }
1674 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
1675
1676 if ctors + methods + fields > 0:
1677 stats['extend_classes'] += 1
1678 stats['extend_ctors'] += ctors
1679 stats['extend_methods'] += methods
1680 stats['extend_fields'] += fields
1681
1682 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
1683 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
1684
1685
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001686if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001687 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1688 patterns. It ignores lint messages from a previous API level, if provided.")
1689 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1690 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1691 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01001692 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
1693 help="The base current.txt to use when examining system-current.txt or"
1694 " test-current.txt")
1695 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
1696 help="The base previous.txt to use when examining system-previous.txt or"
1697 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001698 parser.add_argument("--no-color", action='store_const', const=True,
1699 help="Disable terminal colors")
1700 parser.add_argument("--allow-google", action='store_const', const=True,
1701 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001702 parser.add_argument("--show-noticed", action='store_const', const=True,
1703 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001704 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
1705 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001706 parser.add_argument("--show-stats", action='store_const', const=True,
1707 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001708 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001709
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001710 if args['no_color']:
1711 USE_COLOR = False
1712
1713 if args['allow_google']:
1714 ALLOW_GOOGLE = True
1715
1716 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01001717 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001718 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01001719 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001720
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001721 if args['show_deprecations_at_birth']:
1722 with current_file as f:
1723 cur = _parse_stream(f)
1724 with previous_file as f:
1725 prev = _parse_stream(f)
1726 show_deprecations_at_birth(cur, prev)
1727 sys.exit()
1728
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001729 if args['show_stats']:
1730 with current_file as f:
1731 cur = _parse_stream(f)
1732 with previous_file as f:
1733 prev = _parse_stream(f)
1734 show_stats(cur, prev)
1735 sys.exit()
1736
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001737 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01001738 if base_current_file:
1739 with base_current_file as base_f:
1740 cur_fail, cur_noticed = examine_stream(f, base_f)
1741 else:
1742 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001743 if not previous_file is None:
1744 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01001745 if base_previous_file:
1746 with base_previous_file as base_f:
1747 prev_fail, prev_noticed = examine_stream(f, base_f)
1748 else:
1749 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001750
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001751 # ignore errors from previous API level
1752 for p in prev_fail:
1753 if p in cur_fail:
1754 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001755
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001756 # ignore classes unchanged from previous API level
1757 for k, v in prev_noticed.iteritems():
1758 if k in cur_noticed and v == cur_noticed[k]:
1759 del cur_noticed[k]
1760
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001761 """
1762 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001763 # look for compatibility issues
1764 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001765
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001766 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1767 for f in sorted(compat_fail):
1768 print compat_fail[f]
1769 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001770 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001771
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001772 if args['show_noticed'] and len(cur_noticed) != 0:
1773 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1774 for f in sorted(cur_noticed.keys()):
1775 print f
1776 print
1777
Jason Monk53b2a732017-11-10 15:43:17 -05001778 if len(cur_fail) != 0:
1779 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1780 for f in sorted(cur_fail):
1781 print cur_fail[f]
1782 print
1783 sys.exit(77)