blob: 1eec218c7be386a1d5f45062e879cfb848bcaaae [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 Roos5ed42b62018-12-19 17:10:22 +0100270def _retry_iterator(it):
271 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
272 for e in it:
273 while True:
274 retry = yield e
275 if not retry:
276 break
277 # send() was called, asking us to redeliver clazz on next(). Still need to yield
278 # a dummy value to the send() first though.
279 if (yield "Returning clazz on next()"):
280 raise TypeError("send() must be followed by next(), not send()")
281
Adrian Roos6eb57b02018-12-13 22:08:29 +0100282def _parse_to_matching_class(classes, needle):
283 """Takes a classes generator and parses it until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700284
Adrian Roos6eb57b02018-12-13 22:08:29 +0100285 This relies on classes being sorted by package and class name."""
286
287 for clazz in classes:
288 if clazz.pkg.name < needle.pkg.name:
289 # We haven't reached the right package yet
290 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100291 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
292 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100293 continue
294 if clazz.fullname == needle.fullname:
295 return clazz
296 # We ran past the right class. Send it back into the generator, then report failure.
297 classes.send(clazz)
298 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700299
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700300class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800301 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700302 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700303 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800304 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700305 self.msg = msg
306
307 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800308 self.head = "Error %s" % (rule) if rule else "Error"
309 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 -0700310 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800311 self.head = "Warning %s" % (rule) if rule else "Warning"
312 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 -0700313
314 self.line = clazz.line
315 blame = clazz.blame
316 if detail is not None:
317 dump += "\n in " + repr(detail)
318 self.line = detail.line
319 blame = detail.blame
320 dump += "\n in " + repr(clazz)
321 dump += "\n in " + repr(clazz.pkg)
322 dump += "\n at line " + repr(self.line)
323 if blame is not None:
324 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
325
326 self.dump = dump
327
328 def __repr__(self):
329 return self.dump
330
331
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700332failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700333
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800334def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700335 """Records an API failure to be processed later."""
336 global failures
337
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700338 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
339 sig = sig.replace(" deprecated ", " ")
340
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800341 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700342
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700343
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800344def warn(clazz, detail, rule, msg):
345 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700346
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800347def error(clazz, detail, rule, msg):
348 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700349
350
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700351noticed = {}
352
353def notice(clazz):
354 global noticed
355
356 noticed[clazz.fullname] = hash(clazz)
357
358
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700359def verify_constants(clazz):
360 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700361 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600362 if clazz.fullname.startswith("android.os.Build"): return
363 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700364
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600365 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700366 for f in clazz.fields:
367 if "static" in f.split and "final" in f.split:
368 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800369 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600370 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700371 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
372 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600373 if f.typ in req and f.value is None:
374 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700375
376
377def verify_enums(clazz):
378 """Enums are bad, mmkay?"""
379 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800380 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700381
382
383def verify_class_names(clazz):
384 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700385 if clazz.fullname.startswith("android.opengl"): return
386 if clazz.fullname.startswith("android.renderscript"): return
387 if re.match("android\.R\.[a-z]+", clazz.fullname): return
388
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700389 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800390 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700391 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800392 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700393 if clazz.name.endswith("Impl"):
394 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700395
396
397def verify_method_names(clazz):
398 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700399 if clazz.fullname.startswith("android.opengl"): return
400 if clazz.fullname.startswith("android.renderscript"): return
401 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700402
403 for m in clazz.methods:
404 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800405 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700406 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800407 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700408
409
410def verify_callbacks(clazz):
411 """Verify Callback classes.
412 All callback classes must be abstract.
413 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700414 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700415
416 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800417 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700418 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800419 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700420
421 if clazz.name.endswith("Callback"):
422 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800423 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700424
425 for m in clazz.methods:
426 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800427 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700428
429
430def verify_listeners(clazz):
431 """Verify Listener classes.
432 All Listener classes must be interface.
433 All methods must follow onFoo() naming style.
434 If only a single method, it must match class name:
435 interface OnFooListener { void onFoo() }"""
436
437 if clazz.name.endswith("Listener"):
438 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800439 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700440
441 for m in clazz.methods:
442 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800443 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700444
445 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
446 m = clazz.methods[0]
447 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800448 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700449
450
451def verify_actions(clazz):
452 """Verify intent actions.
453 All action names must be named ACTION_FOO.
454 All action values must be scoped by package and match name:
455 package android.foo {
456 String ACTION_BAR = "android.foo.action.BAR";
457 }"""
458 for f in clazz.fields:
459 if f.value is None: continue
460 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700461 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600462 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700463
464 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
465 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
466 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800467 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700468 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700469 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700470 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700471 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700472 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700473 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
474 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700475 else:
476 prefix = clazz.pkg.name + ".action"
477 expected = prefix + "." + f.name[7:]
478 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700479 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700480
481
482def verify_extras(clazz):
483 """Verify intent extras.
484 All extra names must be named EXTRA_FOO.
485 All extra values must be scoped by package and match name:
486 package android.foo {
487 String EXTRA_BAR = "android.foo.extra.BAR";
488 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700489 if clazz.fullname == "android.app.Notification": return
490 if clazz.fullname == "android.appwidget.AppWidgetManager": return
491
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700492 for f in clazz.fields:
493 if f.value is None: continue
494 if f.name.startswith("ACTION_"): continue
495
496 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
497 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
498 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800499 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700500 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700501 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700502 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700503 elif clazz.pkg.name == "android.app.admin":
504 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700505 else:
506 prefix = clazz.pkg.name + ".extra"
507 expected = prefix + "." + f.name[6:]
508 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700509 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700510
511
512def verify_equals(clazz):
513 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700514 eq = False
515 hc = False
516 for m in clazz.methods:
517 if " static " in m.raw: continue
518 if "boolean equals(java.lang.Object)" in m.raw: eq = True
519 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700520 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800521 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700522
523
524def verify_parcelable(clazz):
525 """Verify that Parcelable objects aren't hiding required bits."""
526 if "implements android.os.Parcelable" in clazz.raw:
527 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
528 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
529 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
530
531 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800532 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700533
Joe LaPenna45380002017-04-20 12:49:48 -0700534 if ((" final class " not in clazz.raw) and
535 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700536 error(clazz, None, "FW8", "Parcelable classes must be final")
537
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700538 for c in clazz.ctors:
539 if c.args == ["android.os.Parcel"]:
540 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
541
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700542
543def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800544 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700545 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600546 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700547 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800548 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700549 for f in clazz.fields:
550 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800551 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700552
553
554def verify_fields(clazz):
555 """Verify that all exposed fields are final.
556 Exposed fields must follow myName style.
557 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700558
559 IGNORE_BARE_FIELDS = [
560 "android.app.ActivityManager.RecentTaskInfo",
561 "android.app.Notification",
562 "android.content.pm.ActivityInfo",
563 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600564 "android.content.pm.ComponentInfo",
565 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700566 "android.content.pm.FeatureGroupInfo",
567 "android.content.pm.InstrumentationInfo",
568 "android.content.pm.PackageInfo",
569 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600570 "android.content.res.Configuration",
571 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700572 "android.os.Message",
573 "android.system.StructPollfd",
574 ]
575
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700576 for f in clazz.fields:
577 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700578 if clazz.fullname in IGNORE_BARE_FIELDS:
579 pass
580 elif clazz.fullname.endswith("LayoutParams"):
581 pass
582 elif clazz.fullname.startswith("android.util.Mutable"):
583 pass
584 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800585 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700586
587 if not "static" in f.split:
588 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800589 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700590
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700591 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800592 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700593
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700594 if re.match("[A-Z_]+", f.name):
595 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800596 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700597
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700598
599def verify_register(clazz):
600 """Verify parity of registration methods.
601 Callback objects use register/unregister methods.
602 Listener objects use add/remove methods."""
603 methods = [ m.name for m in clazz.methods ]
604 for m in clazz.methods:
605 if "Callback" in m.raw:
606 if m.name.startswith("register"):
607 other = "unregister" + m.name[8:]
608 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800609 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700610 if m.name.startswith("unregister"):
611 other = "register" + m.name[10:]
612 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800613 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700614
615 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800616 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700617
618 if "Listener" in m.raw:
619 if m.name.startswith("add"):
620 other = "remove" + m.name[3:]
621 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800622 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700623 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
624 other = "add" + m.name[6:]
625 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800626 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700627
628 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800629 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700630
631
632def verify_sync(clazz):
633 """Verify synchronized methods aren't exposed."""
634 for m in clazz.methods:
635 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800636 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700637
638
639def verify_intent_builder(clazz):
640 """Verify that Intent builders are createFooIntent() style."""
641 if clazz.name == "Intent": return
642
643 for m in clazz.methods:
644 if m.typ == "android.content.Intent":
645 if m.name.startswith("create") and m.name.endswith("Intent"):
646 pass
647 else:
Adam Powell539ea122015-04-10 13:01:37 -0700648 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700649
650
651def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700652 """Verify that helper classes are named consistently with what they extend.
653 All developer extendable methods should be named onFoo()."""
654 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700655 if "extends android.app.Service" 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("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800658 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700659
660 found = False
661 for f in clazz.fields:
662 if f.name == "SERVICE_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.ContentProvider" 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("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800670 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700671
672 found = False
673 for f in clazz.fields:
674 if f.name == "PROVIDER_INTERFACE":
675 found = True
676 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700677 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700678
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700679 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700680 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700681 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800682 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700683
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700684 if "extends android.app.Activity" in clazz.raw:
685 test_methods = True
686 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800687 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700688
689 if test_methods:
690 for m in clazz.methods:
691 if "final" in m.split: continue
692 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700693 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800694 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700695 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800696 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700697
698
699def verify_builder(clazz):
700 """Verify builder classes.
701 Methods should return the builder to enable chaining."""
702 if " extends " in clazz.raw: return
703 if not clazz.name.endswith("Builder"): return
704
705 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800706 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700707
708 has_build = False
709 for m in clazz.methods:
710 if m.name == "build":
711 has_build = True
712 continue
713
714 if m.name.startswith("get"): continue
715 if m.name.startswith("clear"): continue
716
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700717 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800718 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700719
720 if m.name.startswith("set"):
721 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800722 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700723
724 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800725 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700726
727
728def verify_aidl(clazz):
729 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700730 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800731 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700732
733
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700734def verify_internal(clazz):
735 """Catch people exposing internal classes."""
736 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800737 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700738
739
740def verify_layering(clazz):
741 """Catch package layering violations.
742 For example, something in android.os depending on android.app."""
743 ranking = [
744 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
745 "android.app",
746 "android.widget",
747 "android.view",
748 "android.animation",
749 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700750 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700751 "android.database",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700752 "android.text",
Siyamed Sinir0a2e15d2018-09-13 16:06:59 -0700753 "android.graphics",
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700754 "android.os",
755 "android.util"
756 ]
757
758 def rank(p):
759 for i in range(len(ranking)):
760 if isinstance(ranking[i], list):
761 for j in ranking[i]:
762 if p.startswith(j): return i
763 else:
764 if p.startswith(ranking[i]): return i
765
766 cr = rank(clazz.pkg.name)
767 if cr is None: return
768
769 for f in clazz.fields:
770 ir = rank(f.typ)
771 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800772 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700773
774 for m in clazz.methods:
775 ir = rank(m.typ)
776 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800777 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700778 for arg in m.args:
779 ir = rank(arg)
780 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800781 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700782
783
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800784def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800785 """Verifies that boolean accessors are named correctly.
786 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700787
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800788 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
789 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700790
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800791 gets = [ m for m in clazz.methods if is_get(m) ]
792 sets = [ m for m in clazz.methods if is_set(m) ]
793
794 def error_if_exists(methods, trigger, expected, actual):
795 for m in methods:
796 if m.name == actual:
797 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700798
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700799 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800800 if is_get(m):
801 if re.match("is[A-Z]", m.name):
802 target = m.name[2:]
803 expected = "setIs" + target
804 error_if_exists(sets, m.name, expected, "setHas" + target)
805 elif re.match("has[A-Z]", m.name):
806 target = m.name[3:]
807 expected = "setHas" + target
808 error_if_exists(sets, m.name, expected, "setIs" + target)
809 error_if_exists(sets, m.name, expected, "set" + target)
810 elif re.match("get[A-Z]", m.name):
811 target = m.name[3:]
812 expected = "set" + target
813 error_if_exists(sets, m.name, expected, "setIs" + target)
814 error_if_exists(sets, m.name, expected, "setHas" + target)
815
816 if is_set(m):
817 if re.match("set[A-Z]", m.name):
818 target = m.name[3:]
819 expected = "get" + target
820 error_if_exists(sets, m.name, expected, "is" + target)
821 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700822
823
824def verify_collections(clazz):
825 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700826 if clazz.fullname == "android.os.Bundle": return
827
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700828 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
829 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
830 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700831 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800832 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700833 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700834 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800835 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700836
837
838def verify_flags(clazz):
839 """Verifies that flags are non-overlapping."""
840 known = collections.defaultdict(int)
841 for f in clazz.fields:
842 if "FLAG_" in f.name:
843 try:
844 val = int(f.value)
845 except:
846 continue
847
848 scope = f.name[0:f.name.index("FLAG_")]
849 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800850 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700851 known[scope] |= val
852
853
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800854def verify_exception(clazz):
855 """Verifies that methods don't throw generic exceptions."""
856 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700857 for t in m.throws:
858 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
859 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800860
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700861 if t in ["android.os.RemoteException"]:
862 if clazz.name == "android.content.ContentProviderClient": continue
863 if clazz.name == "android.os.Binder": continue
864 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700865
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700866 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
867
868 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
869 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700870
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800871
872def verify_google(clazz):
873 """Verifies that APIs never reference Google."""
874
875 if re.search("google", clazz.raw, re.IGNORECASE):
876 error(clazz, None, None, "Must never reference Google")
877
878 test = []
879 test.extend(clazz.ctors)
880 test.extend(clazz.fields)
881 test.extend(clazz.methods)
882
883 for t in test:
884 if re.search("google", t.raw, re.IGNORECASE):
885 error(clazz, t, None, "Must never reference Google")
886
887
888def verify_bitset(clazz):
889 """Verifies that we avoid using heavy BitSet."""
890
891 for f in clazz.fields:
892 if f.typ == "java.util.BitSet":
893 error(clazz, f, None, "Field type must not be heavy BitSet")
894
895 for m in clazz.methods:
896 if m.typ == "java.util.BitSet":
897 error(clazz, m, None, "Return type must not be heavy BitSet")
898 for arg in m.args:
899 if arg == "java.util.BitSet":
900 error(clazz, m, None, "Argument type must not be heavy BitSet")
901
902
903def verify_manager(clazz):
904 """Verifies that FooManager is only obtained from Context."""
905
906 if not clazz.name.endswith("Manager"): return
907
908 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800909 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800910
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600911 for m in clazz.methods:
912 if m.typ == clazz.fullname:
913 error(clazz, m, None, "Managers must always be obtained from Context")
914
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800915
916def verify_boxed(clazz):
917 """Verifies that methods avoid boxed primitives."""
918
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800919 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 -0800920
921 for c in clazz.ctors:
922 for arg in c.args:
923 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800924 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800925
926 for f in clazz.fields:
927 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800928 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800929
930 for m in clazz.methods:
931 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800932 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800933 for arg in m.args:
934 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800935 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800936
937
938def verify_static_utils(clazz):
939 """Verifies that helper classes can't be constructed."""
940 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600941 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800942
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600943 # Only care about classes with default constructors
944 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
945 test = []
946 test.extend(clazz.fields)
947 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800948
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600949 if len(test) == 0: return
950 for t in test:
951 if "static" not in t.split:
952 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800953
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800954 error(clazz, None, None, "Fully-static utility classes must not have constructor")
955
956
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800957def verify_overload_args(clazz):
958 """Verifies that method overloads add new arguments at the end."""
959 if clazz.fullname.startswith("android.opengl"): return
960
961 overloads = collections.defaultdict(list)
962 for m in clazz.methods:
963 if "deprecated" in m.split: continue
964 overloads[m.name].append(m)
965
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800966 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800967 if len(methods) <= 1: continue
968
969 # Look for arguments common across all overloads
970 def cluster(args):
971 count = collections.defaultdict(int)
972 res = set()
973 for i in range(len(args)):
974 a = args[i]
975 res.add("%s#%d" % (a, count[a]))
976 count[a] += 1
977 return res
978
979 common_args = cluster(methods[0].args)
980 for m in methods:
981 common_args = common_args & cluster(m.args)
982
983 if len(common_args) == 0: continue
984
985 # Require that all common arguments are present at start of signature
986 locked_sig = None
987 for m in methods:
988 sig = m.args[0:len(common_args)]
989 if not common_args.issubset(cluster(sig)):
990 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
991 elif not locked_sig:
992 locked_sig = sig
993 elif locked_sig != sig:
994 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
995
996
997def verify_callback_handlers(clazz):
998 """Verifies that methods adding listener/callback have overload
999 for specifying delivery thread."""
1000
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001001 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001002 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001003 "animation",
1004 "view",
1005 "graphics",
1006 "transition",
1007 "widget",
1008 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001009 ]
1010 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001011 if s in clazz.pkg.name_path: return
1012 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001013
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001014 # Ignore UI classes which assume main thread
1015 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1016 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1017 if s in clazz.fullname: return
1018 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1019 for s in ["Loader"]:
1020 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001021
1022 found = {}
1023 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001024 examine = clazz.ctors + clazz.methods
1025 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001026 if m.name.startswith("unregister"): continue
1027 if m.name.startswith("remove"): continue
1028 if re.match("on[A-Z]+", m.name): continue
1029
1030 by_name[m.name].append(m)
1031
1032 for a in m.args:
1033 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1034 found[m.name] = m
1035
1036 for f in found.values():
1037 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001038 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001039 for m in by_name[f.name]:
1040 if "android.os.Handler" in m.args:
1041 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001042 if "java.util.concurrent.Executor" in m.args:
1043 takes_exec = True
1044 if not takes_exec:
1045 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001046
1047
1048def verify_context_first(clazz):
1049 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001050 examine = clazz.ctors + clazz.methods
1051 for m in examine:
1052 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001053 if "android.content.Context" in m.args[1:]:
1054 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001055 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1056 if "android.content.ContentResolver" in m.args[1:]:
1057 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001058
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001059
1060def verify_listener_last(clazz):
1061 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1062 examine = clazz.ctors + clazz.methods
1063 for m in examine:
1064 if "Listener" in m.name or "Callback" in m.name: continue
1065 found = False
1066 for a in m.args:
1067 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1068 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001069 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001070 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1071
1072
1073def verify_resource_names(clazz):
1074 """Verifies that resource names have consistent case."""
1075 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1076
1077 # Resources defined by files are foo_bar_baz
1078 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1079 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001080 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1081 if f.name.startswith("config_"):
1082 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1083
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001084 if re.match("[a-z1-9_]+$", f.name): continue
1085 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1086
1087 # Resources defined inside files are fooBarBaz
1088 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1089 for f in clazz.fields:
1090 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1091 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1092 if re.match("state_[a-z_]*$", f.name): continue
1093
1094 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1095 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1096
1097 # Styles are FooBar_Baz
1098 if clazz.name in ["style"]:
1099 for f in clazz.fields:
1100 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1101 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001102
1103
Jeff Sharkey331279b2016-02-29 16:02:02 -07001104def verify_files(clazz):
1105 """Verifies that methods accepting File also accept streams."""
1106
1107 has_file = set()
1108 has_stream = set()
1109
1110 test = []
1111 test.extend(clazz.ctors)
1112 test.extend(clazz.methods)
1113
1114 for m in test:
1115 if "java.io.File" in m.args:
1116 has_file.add(m)
1117 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:
1118 has_stream.add(m.name)
1119
1120 for m in has_file:
1121 if m.name not in has_stream:
1122 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1123
1124
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001125def verify_manager_list(clazz):
1126 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1127
1128 if not clazz.name.endswith("Manager"): return
1129
1130 for m in clazz.methods:
1131 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1132 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1133
1134
Jeff Sharkey26c80902016-12-21 13:41:17 -07001135def verify_abstract_inner(clazz):
1136 """Verifies that abstract inner classes are static."""
1137
1138 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1139 if " abstract " in clazz.raw and " static " not in clazz.raw:
1140 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1141
1142
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001143def verify_runtime_exceptions(clazz):
1144 """Verifies that runtime exceptions aren't listed in throws."""
1145
1146 banned = [
1147 "java.lang.NullPointerException",
1148 "java.lang.ClassCastException",
1149 "java.lang.IndexOutOfBoundsException",
1150 "java.lang.reflect.UndeclaredThrowableException",
1151 "java.lang.reflect.MalformedParametersException",
1152 "java.lang.reflect.MalformedParameterizedTypeException",
1153 "java.lang.invoke.WrongMethodTypeException",
1154 "java.lang.EnumConstantNotPresentException",
1155 "java.lang.IllegalMonitorStateException",
1156 "java.lang.SecurityException",
1157 "java.lang.UnsupportedOperationException",
1158 "java.lang.annotation.AnnotationTypeMismatchException",
1159 "java.lang.annotation.IncompleteAnnotationException",
1160 "java.lang.TypeNotPresentException",
1161 "java.lang.IllegalStateException",
1162 "java.lang.ArithmeticException",
1163 "java.lang.IllegalArgumentException",
1164 "java.lang.ArrayStoreException",
1165 "java.lang.NegativeArraySizeException",
1166 "java.util.MissingResourceException",
1167 "java.util.EmptyStackException",
1168 "java.util.concurrent.CompletionException",
1169 "java.util.concurrent.RejectedExecutionException",
1170 "java.util.IllformedLocaleException",
1171 "java.util.ConcurrentModificationException",
1172 "java.util.NoSuchElementException",
1173 "java.io.UncheckedIOException",
1174 "java.time.DateTimeException",
1175 "java.security.ProviderException",
1176 "java.nio.BufferUnderflowException",
1177 "java.nio.BufferOverflowException",
1178 ]
1179
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001180 examine = clazz.ctors + clazz.methods
1181 for m in examine:
1182 for t in m.throws:
1183 if t in banned:
1184 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001185
1186
1187def verify_error(clazz):
1188 """Verifies that we always use Exception instead of Error."""
1189 if not clazz.extends: return
1190 if clazz.extends.endswith("Error"):
1191 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1192 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1193 error(clazz, None, None, "Exceptions must be named FooException")
1194
1195
1196def verify_units(clazz):
1197 """Verifies that we use consistent naming for units."""
1198
1199 # If we find K, recommend replacing with V
1200 bad = {
1201 "Ns": "Nanos",
1202 "Ms": "Millis or Micros",
1203 "Sec": "Seconds", "Secs": "Seconds",
1204 "Hr": "Hours", "Hrs": "Hours",
1205 "Mo": "Months", "Mos": "Months",
1206 "Yr": "Years", "Yrs": "Years",
1207 "Byte": "Bytes", "Space": "Bytes",
1208 }
1209
1210 for m in clazz.methods:
1211 if m.typ not in ["short","int","long"]: continue
1212 for k, v in bad.iteritems():
1213 if m.name.endswith(k):
1214 error(clazz, m, None, "Expected method name units to be " + v)
1215 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1216 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1217 if m.name.endswith("Seconds"):
1218 error(clazz, m, None, "Returned time values must be in milliseconds")
1219
1220 for m in clazz.methods:
1221 typ = m.typ
1222 if typ == "void":
1223 if len(m.args) != 1: continue
1224 typ = m.args[0]
1225
1226 if m.name.endswith("Fraction") and typ != "float":
1227 error(clazz, m, None, "Fractions must use floats")
1228 if m.name.endswith("Percentage") and typ != "int":
1229 error(clazz, m, None, "Percentage must use ints")
1230
1231
1232def verify_closable(clazz):
1233 """Verifies that classes are AutoClosable."""
1234 if "implements java.lang.AutoCloseable" in clazz.raw: return
1235 if "implements java.io.Closeable" in clazz.raw: return
1236
1237 for m in clazz.methods:
1238 if len(m.args) > 0: continue
1239 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1240 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1241 return
1242
1243
Jake Wharton9e6738f2017-08-23 11:59:55 -04001244def verify_member_name_not_kotlin_keyword(clazz):
1245 """Prevent method names which are keywords in Kotlin."""
1246
1247 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1248 # This list does not include Java keywords as those are already impossible to use.
1249 keywords = [
1250 'as',
1251 'fun',
1252 'in',
1253 'is',
1254 'object',
1255 'typealias',
1256 'val',
1257 'var',
1258 'when',
1259 ]
1260
1261 for m in clazz.methods:
1262 if m.name in keywords:
1263 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1264 for f in clazz.fields:
1265 if f.name in keywords:
1266 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1267
1268
1269def verify_method_name_not_kotlin_operator(clazz):
1270 """Warn about method names which become operators in Kotlin."""
1271
1272 binary = set()
1273
1274 def unique_binary_op(m, op):
1275 if op in binary:
1276 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1277 binary.add(op)
1278
1279 for m in clazz.methods:
1280 if 'static' in m.split:
1281 continue
1282
1283 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1284 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1285 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1286
1287 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1288 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1289 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1290 # practical way of checking that relationship here.
1291 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1292
1293 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1294 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1295 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1296 unique_binary_op(m, m.name)
1297
1298 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1299 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1300 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1301
1302 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1303 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1304 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1305
1306 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1307 if m.name == 'invoke':
1308 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1309
1310 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1311 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1312 and len(m.args) == 1 \
1313 and m.typ == 'void':
1314 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1315 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1316
1317
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001318def verify_collections_over_arrays(clazz):
1319 """Warn that [] should be Collections."""
1320
1321 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1322 for m in clazz.methods:
1323 if m.typ.endswith("[]") and m.typ not in safe:
1324 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1325 for arg in m.args:
1326 if arg.endswith("[]") and arg not in safe:
1327 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1328
1329
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001330def verify_user_handle(clazz):
1331 """Methods taking UserHandle should be ForUser or AsUser."""
1332 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1333 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1334 if clazz.fullname == "android.content.pm.LauncherApps": return
1335 if clazz.fullname == "android.os.UserHandle": return
1336 if clazz.fullname == "android.os.UserManager": return
1337
1338 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001339 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001340
1341 has_arg = "android.os.UserHandle" in m.args
1342 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1343
1344 if clazz.fullname.endswith("Manager") and has_arg:
1345 warn(clazz, m, None, "When a method overload is needed to target a specific "
1346 "UserHandle, callers should be directed to use "
1347 "Context.createPackageContextAsUser() and re-obtain the relevant "
1348 "Manager, and no new API should be added")
1349 elif has_arg and not has_name:
1350 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1351 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001352
1353
1354def verify_params(clazz):
1355 """Parameter classes should be 'Params'."""
1356 if clazz.name.endswith("Params"): return
1357 if clazz.fullname == "android.app.ActivityOptions": return
1358 if clazz.fullname == "android.app.BroadcastOptions": return
1359 if clazz.fullname == "android.os.Bundle": return
1360 if clazz.fullname == "android.os.BaseBundle": return
1361 if clazz.fullname == "android.os.PersistableBundle": return
1362
1363 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1364 for b in bad:
1365 if clazz.name.endswith(b):
1366 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1367
1368
1369def verify_services(clazz):
1370 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1371 if clazz.fullname != "android.content.Context": return
1372
1373 for f in clazz.fields:
1374 if f.typ != "java.lang.String": continue
1375 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1376 if found:
1377 expected = found.group(1).lower()
1378 if f.value != expected:
1379 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1380
1381
1382def verify_tense(clazz):
1383 """Verify tenses of method names."""
1384 if clazz.fullname.startswith("android.opengl"): return
1385
1386 for m in clazz.methods:
1387 if m.name.endswith("Enable"):
1388 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1389
1390
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001391def verify_icu(clazz):
1392 """Verifies that richer ICU replacements are used."""
1393 better = {
1394 "java.util.TimeZone": "android.icu.util.TimeZone",
1395 "java.util.Calendar": "android.icu.util.Calendar",
1396 "java.util.Locale": "android.icu.util.ULocale",
1397 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1398 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1399 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1400 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1401 "java.lang.Character": "android.icu.lang.UCharacter",
1402 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1403 "java.text.Collator": "android.icu.text.Collator",
1404 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1405 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1406 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1407 "java.text.DateFormat": "android.icu.text.DateFormat",
1408 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1409 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1410 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1411 }
1412
1413 for m in clazz.ctors + clazz.methods:
1414 types = []
1415 types.extend(m.typ)
1416 types.extend(m.args)
1417 for arg in types:
1418 if arg in better:
1419 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1420
1421
1422def verify_clone(clazz):
1423 """Verify that clone() isn't implemented; see EJ page 61."""
1424 for m in clazz.methods:
1425 if m.name == "clone":
1426 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1427
1428
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001429def verify_pfd(clazz):
1430 """Verify that android APIs use PFD over FD."""
1431 examine = clazz.ctors + clazz.methods
1432 for m in examine:
1433 if m.typ == "java.io.FileDescriptor":
1434 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1435 if m.typ == "int":
1436 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1437 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1438 for arg in m.args:
1439 if arg == "java.io.FileDescriptor":
1440 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1441
1442 for f in clazz.fields:
1443 if f.typ == "java.io.FileDescriptor":
1444 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1445
1446
1447def verify_numbers(clazz):
1448 """Discourage small numbers types like short and byte."""
1449
1450 discouraged = ["short","byte"]
1451
1452 for c in clazz.ctors:
1453 for arg in c.args:
1454 if arg in discouraged:
1455 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1456
1457 for f in clazz.fields:
1458 if f.typ in discouraged:
1459 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1460
1461 for m in clazz.methods:
1462 if m.typ in discouraged:
1463 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1464 for arg in m.args:
1465 if arg in discouraged:
1466 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1467
1468
1469def verify_singleton(clazz):
1470 """Catch singleton objects with constructors."""
1471
1472 singleton = False
1473 for m in clazz.methods:
1474 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1475 singleton = True
1476
1477 if singleton:
1478 for c in clazz.ctors:
1479 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1480
1481
1482
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001483def is_interesting(clazz):
1484 """Test if given class is interesting from an Android PoV."""
1485
1486 if clazz.pkg.name.startswith("java"): return False
1487 if clazz.pkg.name.startswith("junit"): return False
1488 if clazz.pkg.name.startswith("org.apache"): return False
1489 if clazz.pkg.name.startswith("org.xml"): return False
1490 if clazz.pkg.name.startswith("org.json"): return False
1491 if clazz.pkg.name.startswith("org.w3c"): return False
1492 if clazz.pkg.name.startswith("android.icu."): return False
1493 return True
1494
1495
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001496def examine_clazz(clazz):
1497 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001498
1499 notice(clazz)
1500
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001501 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001502
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001503 verify_constants(clazz)
1504 verify_enums(clazz)
1505 verify_class_names(clazz)
1506 verify_method_names(clazz)
1507 verify_callbacks(clazz)
1508 verify_listeners(clazz)
1509 verify_actions(clazz)
1510 verify_extras(clazz)
1511 verify_equals(clazz)
1512 verify_parcelable(clazz)
1513 verify_protected(clazz)
1514 verify_fields(clazz)
1515 verify_register(clazz)
1516 verify_sync(clazz)
1517 verify_intent_builder(clazz)
1518 verify_helper_classes(clazz)
1519 verify_builder(clazz)
1520 verify_aidl(clazz)
1521 verify_internal(clazz)
1522 verify_layering(clazz)
1523 verify_boolean(clazz)
1524 verify_collections(clazz)
1525 verify_flags(clazz)
1526 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001527 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001528 verify_bitset(clazz)
1529 verify_manager(clazz)
1530 verify_boxed(clazz)
1531 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001532 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001533 verify_callback_handlers(clazz)
1534 verify_context_first(clazz)
1535 verify_listener_last(clazz)
1536 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001537 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001538 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001539 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001540 verify_runtime_exceptions(clazz)
1541 verify_error(clazz)
1542 verify_units(clazz)
1543 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001544 verify_member_name_not_kotlin_keyword(clazz)
1545 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001546 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001547 verify_user_handle(clazz)
1548 verify_params(clazz)
1549 verify_services(clazz)
1550 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001551 verify_icu(clazz)
1552 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001553 verify_pfd(clazz)
1554 verify_numbers(clazz)
1555 verify_singleton(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001556
1557
Adrian Roos6eb57b02018-12-13 22:08:29 +01001558def examine_stream(stream, base_stream=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001559 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001560 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001561 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001562 noticed = {}
Adrian Roos6eb57b02018-12-13 22:08:29 +01001563 _parse_stream(stream, examine_clazz, base_f=base_stream)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001564 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001565
1566
1567def examine_api(api):
1568 """Find all style issues in the given parsed API."""
1569 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001570 failures = {}
1571 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001572 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001573 return failures
1574
1575
Jeff Sharkey037458a2014-09-04 15:46:20 -07001576def verify_compat(cur, prev):
1577 """Find any incompatible API changes between two levels."""
1578 global failures
1579
1580 def class_exists(api, test):
1581 return test.fullname in api
1582
1583 def ctor_exists(api, clazz, test):
1584 for m in clazz.ctors:
1585 if m.ident == test.ident: return True
1586 return False
1587
1588 def all_methods(api, clazz):
1589 methods = list(clazz.methods)
1590 if clazz.extends is not None:
1591 methods.extend(all_methods(api, api[clazz.extends]))
1592 return methods
1593
1594 def method_exists(api, clazz, test):
1595 methods = all_methods(api, clazz)
1596 for m in methods:
1597 if m.ident == test.ident: return True
1598 return False
1599
1600 def field_exists(api, clazz, test):
1601 for f in clazz.fields:
1602 if f.ident == test.ident: return True
1603 return False
1604
1605 failures = {}
1606 for key in sorted(prev.keys()):
1607 prev_clazz = prev[key]
1608
1609 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001610 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001611 continue
1612
1613 cur_clazz = cur[key]
1614
1615 for test in prev_clazz.ctors:
1616 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001617 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001618
1619 methods = all_methods(prev, prev_clazz)
1620 for test in methods:
1621 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001622 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001623
1624 for test in prev_clazz.fields:
1625 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001626 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001627
1628 return failures
1629
1630
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001631def show_deprecations_at_birth(cur, prev):
1632 """Show API deprecations at birth."""
1633 global failures
1634
1635 # Remove all existing things so we're left with new
1636 for prev_clazz in prev.values():
1637 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001638 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001639
1640 sigs = { i.ident: i for i in prev_clazz.ctors }
1641 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
1642 sigs = { i.ident: i for i in prev_clazz.methods }
1643 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
1644 sigs = { i.ident: i for i in prev_clazz.fields }
1645 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
1646
1647 # Forget about class entirely when nothing new
1648 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
1649 del cur[prev_clazz.fullname]
1650
1651 for clazz in cur.values():
1652 if " deprecated " in clazz.raw and not clazz.fullname in prev:
1653 error(clazz, None, None, "Found API deprecation at birth")
1654
1655 for i in clazz.ctors + clazz.methods + clazz.fields:
1656 if " deprecated " in i.raw:
1657 error(clazz, i, None, "Found API deprecation at birth")
1658
1659 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
1660 format(reset=True)))
1661 for f in sorted(failures):
1662 print failures[f]
1663 print
1664
1665
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001666def show_stats(cur, prev):
1667 """Show API stats."""
1668
1669 stats = collections.defaultdict(int)
1670 for cur_clazz in cur.values():
1671 if not is_interesting(cur_clazz): continue
1672
1673 if cur_clazz.fullname not in prev:
1674 stats['new_classes'] += 1
1675 stats['new_ctors'] += len(cur_clazz.ctors)
1676 stats['new_methods'] += len(cur_clazz.methods)
1677 stats['new_fields'] += len(cur_clazz.fields)
1678 else:
1679 prev_clazz = prev[cur_clazz.fullname]
1680
1681 sigs = { i.ident: i for i in prev_clazz.ctors }
1682 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
1683 sigs = { i.ident: i for i in prev_clazz.methods }
1684 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
1685 sigs = { i.ident: i for i in prev_clazz.fields }
1686 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
1687
1688 if ctors + methods + fields > 0:
1689 stats['extend_classes'] += 1
1690 stats['extend_ctors'] += ctors
1691 stats['extend_methods'] += methods
1692 stats['extend_fields'] += fields
1693
1694 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
1695 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
1696
1697
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001698if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001699 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1700 patterns. It ignores lint messages from a previous API level, if provided.")
1701 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1702 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1703 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01001704 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
1705 help="The base current.txt to use when examining system-current.txt or"
1706 " test-current.txt")
1707 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
1708 help="The base previous.txt to use when examining system-previous.txt or"
1709 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001710 parser.add_argument("--no-color", action='store_const', const=True,
1711 help="Disable terminal colors")
1712 parser.add_argument("--allow-google", action='store_const', const=True,
1713 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001714 parser.add_argument("--show-noticed", action='store_const', const=True,
1715 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001716 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
1717 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001718 parser.add_argument("--show-stats", action='store_const', const=True,
1719 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001720 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001721
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001722 if args['no_color']:
1723 USE_COLOR = False
1724
1725 if args['allow_google']:
1726 ALLOW_GOOGLE = True
1727
1728 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01001729 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001730 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01001731 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001732
Jeff Sharkey8b141b92018-04-11 10:05:44 -06001733 if args['show_deprecations_at_birth']:
1734 with current_file as f:
1735 cur = _parse_stream(f)
1736 with previous_file as f:
1737 prev = _parse_stream(f)
1738 show_deprecations_at_birth(cur, prev)
1739 sys.exit()
1740
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001741 if args['show_stats']:
1742 with current_file as f:
1743 cur = _parse_stream(f)
1744 with previous_file as f:
1745 prev = _parse_stream(f)
1746 show_stats(cur, prev)
1747 sys.exit()
1748
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001749 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01001750 if base_current_file:
1751 with base_current_file as base_f:
1752 cur_fail, cur_noticed = examine_stream(f, base_f)
1753 else:
1754 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001755 if not previous_file is None:
1756 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01001757 if base_previous_file:
1758 with base_previous_file as base_f:
1759 prev_fail, prev_noticed = examine_stream(f, base_f)
1760 else:
1761 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001762
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001763 # ignore errors from previous API level
1764 for p in prev_fail:
1765 if p in cur_fail:
1766 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001767
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001768 # ignore classes unchanged from previous API level
1769 for k, v in prev_noticed.iteritems():
1770 if k in cur_noticed and v == cur_noticed[k]:
1771 del cur_noticed[k]
1772
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001773 """
1774 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001775 # look for compatibility issues
1776 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001777
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001778 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1779 for f in sorted(compat_fail):
1780 print compat_fail[f]
1781 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001782 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001783
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001784 if args['show_noticed'] and len(cur_noticed) != 0:
1785 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1786 for f in sorted(cur_noticed.keys()):
1787 print f
1788 print
1789
Jason Monk53b2a732017-11-10 15:43:17 -05001790 if len(cur_fail) != 0:
1791 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1792 for f in sorted(cur_fail):
1793 print cur_fail[f]
1794 print
1795 sys.exit(77)