blob: fd42033d839c64aa4cc7ba5605eff0e61763ba7c [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
53class Field():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070054 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070055 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070056 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070057 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070058 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -070059
60 raw = raw.split()
61 self.split = list(raw)
62
63 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
64 while r in raw: raw.remove(r)
65
66 self.typ = raw[0]
67 self.name = raw[1].strip(";")
68 if len(raw) >= 4 and raw[2] == "=":
69 self.value = raw[3].strip(';"')
70 else:
71 self.value = None
72
Jeff Sharkey037458a2014-09-04 15:46:20 -070073 self.ident = self.raw.replace(" deprecated ", " ")
74
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070075 def __hash__(self):
76 return hash(self.raw)
77
Jeff Sharkey8190f4882014-08-28 12:24:07 -070078 def __repr__(self):
79 return self.raw
80
81
82class Method():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070083 def __init__(self, clazz, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -070084 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -070085 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -070086 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -070087 self.blame = blame
88
89 # drop generics for now
90 raw = re.sub("<.+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070091
92 raw = re.split("[\s(),;]+", raw)
93 for r in ["", ";"]:
94 while r in raw: raw.remove(r)
95 self.split = list(raw)
96
Filip Pavlisdb234ef2016-11-29 19:06:17 +000097 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default"]:
Jeff Sharkey8190f4882014-08-28 12:24:07 -070098 while r in raw: raw.remove(r)
99
100 self.typ = raw[0]
101 self.name = raw[1]
102 self.args = []
103 for r in raw[2:]:
104 if r == "throws": break
105 self.args.append(r)
106
Jeff Sharkey037458a2014-09-04 15:46:20 -0700107 # identity for compat purposes
108 ident = self.raw
109 ident = ident.replace(" deprecated ", " ")
110 ident = ident.replace(" synchronized ", " ")
111 ident = re.sub("<.+?>", "", ident)
112 if " throws " in ident:
113 ident = ident[:ident.index(" throws ")]
114 self.ident = ident
115
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700116 def __hash__(self):
117 return hash(self.raw)
118
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700119 def __repr__(self):
120 return self.raw
121
122
123class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700124 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700125 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700126 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700127 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700128 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700129 self.ctors = []
130 self.fields = []
131 self.methods = []
132
133 raw = raw.split()
134 self.split = list(raw)
135 if "class" in raw:
136 self.fullname = raw[raw.index("class")+1]
137 elif "interface" in raw:
138 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700139 else:
140 raise ValueError("Funky class type %s" % (self.raw))
141
142 if "extends" in raw:
143 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800144 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700145 else:
146 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800147 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700148
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700149 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800150 self.fullname_path = self.fullname.split(".")
151
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700152 self.name = self.fullname[self.fullname.rindex(".")+1:]
153
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700154 def __hash__(self):
155 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
156
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700157 def __repr__(self):
158 return self.raw
159
160
161class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700162 def __init__(self, line, raw, blame):
163 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700164 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700165 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700166
167 raw = raw.split()
168 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800169 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700170
171 def __repr__(self):
172 return self.raw
173
174
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800175def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700176 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700177 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700178 pkg = None
179 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700180 blame = None
181
182 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800183 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700184 line += 1
185 raw = raw.rstrip()
186 match = re_blame.match(raw)
187 if match is not None:
188 blame = match.groups()[0:2]
189 raw = match.groups()[2]
190 else:
191 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700192
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700193 if raw.startswith("package"):
194 pkg = Package(line, raw, blame)
195 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800196 # When provided with class callback, we treat as incremental
197 # parse and don't build up entire API
198 if clazz and clazz_cb:
199 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700200 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800201 if not clazz_cb:
202 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700203 elif raw.startswith(" ctor"):
204 clazz.ctors.append(Method(clazz, line, raw, blame))
205 elif raw.startswith(" method"):
206 clazz.methods.append(Method(clazz, line, raw, blame))
207 elif raw.startswith(" field"):
208 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700209
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800210 # Handle last trailing class
211 if clazz and clazz_cb:
212 clazz_cb(clazz)
213
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700214 return api
215
216
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700217class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800218 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700219 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700220 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800221 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700222 self.msg = msg
223
224 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800225 self.head = "Error %s" % (rule) if rule else "Error"
226 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 -0700227 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800228 self.head = "Warning %s" % (rule) if rule else "Warning"
229 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 -0700230
231 self.line = clazz.line
232 blame = clazz.blame
233 if detail is not None:
234 dump += "\n in " + repr(detail)
235 self.line = detail.line
236 blame = detail.blame
237 dump += "\n in " + repr(clazz)
238 dump += "\n in " + repr(clazz.pkg)
239 dump += "\n at line " + repr(self.line)
240 if blame is not None:
241 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
242
243 self.dump = dump
244
245 def __repr__(self):
246 return self.dump
247
248
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700249failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700250
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800251def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700252 """Records an API failure to be processed later."""
253 global failures
254
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700255 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
256 sig = sig.replace(" deprecated ", " ")
257
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800258 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700259
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700260
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800261def warn(clazz, detail, rule, msg):
262 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700263
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800264def error(clazz, detail, rule, msg):
265 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700266
267
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700268noticed = {}
269
270def notice(clazz):
271 global noticed
272
273 noticed[clazz.fullname] = hash(clazz)
274
275
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700276def verify_constants(clazz):
277 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700278 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600279 if clazz.fullname.startswith("android.os.Build"): return
280 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700281
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600282 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700283 for f in clazz.fields:
284 if "static" in f.split and "final" in f.split:
285 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800286 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600287 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700288 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
289 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600290 if f.typ in req and f.value is None:
291 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700292
293
294def verify_enums(clazz):
295 """Enums are bad, mmkay?"""
296 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800297 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700298
299
300def verify_class_names(clazz):
301 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700302 if clazz.fullname.startswith("android.opengl"): return
303 if clazz.fullname.startswith("android.renderscript"): return
304 if re.match("android\.R\.[a-z]+", clazz.fullname): return
305
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700306 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800307 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700308 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800309 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700310
311
312def verify_method_names(clazz):
313 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700314 if clazz.fullname.startswith("android.opengl"): return
315 if clazz.fullname.startswith("android.renderscript"): return
316 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700317
318 for m in clazz.methods:
319 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800320 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700321 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800322 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700323
324
325def verify_callbacks(clazz):
326 """Verify Callback classes.
327 All callback classes must be abstract.
328 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700329 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700330
331 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800332 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700333 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800334 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700335
336 if clazz.name.endswith("Callback"):
337 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800338 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700339
340 for m in clazz.methods:
341 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800342 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700343
344
345def verify_listeners(clazz):
346 """Verify Listener classes.
347 All Listener classes must be interface.
348 All methods must follow onFoo() naming style.
349 If only a single method, it must match class name:
350 interface OnFooListener { void onFoo() }"""
351
352 if clazz.name.endswith("Listener"):
353 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800354 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700355
356 for m in clazz.methods:
357 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800358 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700359
360 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
361 m = clazz.methods[0]
362 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800363 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700364
365
366def verify_actions(clazz):
367 """Verify intent actions.
368 All action names must be named ACTION_FOO.
369 All action values must be scoped by package and match name:
370 package android.foo {
371 String ACTION_BAR = "android.foo.action.BAR";
372 }"""
373 for f in clazz.fields:
374 if f.value is None: continue
375 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700376 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600377 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700378
379 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
380 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
381 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800382 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700383 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700384 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700385 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700386 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700387 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700388 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
389 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700390 else:
391 prefix = clazz.pkg.name + ".action"
392 expected = prefix + "." + f.name[7:]
393 if f.value != expected:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800394 error(clazz, f, "C4", "Inconsistent action value; expected %s" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700395
396
397def verify_extras(clazz):
398 """Verify intent extras.
399 All extra names must be named EXTRA_FOO.
400 All extra values must be scoped by package and match name:
401 package android.foo {
402 String EXTRA_BAR = "android.foo.extra.BAR";
403 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700404 if clazz.fullname == "android.app.Notification": return
405 if clazz.fullname == "android.appwidget.AppWidgetManager": return
406
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700407 for f in clazz.fields:
408 if f.value is None: continue
409 if f.name.startswith("ACTION_"): continue
410
411 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
412 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
413 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800414 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700415 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700416 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700417 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700418 elif clazz.pkg.name == "android.app.admin":
419 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700420 else:
421 prefix = clazz.pkg.name + ".extra"
422 expected = prefix + "." + f.name[6:]
423 if f.value != expected:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800424 error(clazz, f, "C4", "Inconsistent extra value; expected %s" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700425
426
427def verify_equals(clazz):
428 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700429 eq = False
430 hc = False
431 for m in clazz.methods:
432 if " static " in m.raw: continue
433 if "boolean equals(java.lang.Object)" in m.raw: eq = True
434 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700435 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800436 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700437
438
439def verify_parcelable(clazz):
440 """Verify that Parcelable objects aren't hiding required bits."""
441 if "implements android.os.Parcelable" in clazz.raw:
442 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
443 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
444 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
445
446 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800447 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700448
Joe LaPenna45380002017-04-20 12:49:48 -0700449 if ((" final class " not in clazz.raw) and
450 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700451 error(clazz, None, "FW8", "Parcelable classes must be final")
452
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700453
454def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800455 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700456 for m in clazz.methods:
457 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800458 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700459 for f in clazz.fields:
460 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800461 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700462
463
464def verify_fields(clazz):
465 """Verify that all exposed fields are final.
466 Exposed fields must follow myName style.
467 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700468
469 IGNORE_BARE_FIELDS = [
470 "android.app.ActivityManager.RecentTaskInfo",
471 "android.app.Notification",
472 "android.content.pm.ActivityInfo",
473 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600474 "android.content.pm.ComponentInfo",
475 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700476 "android.content.pm.FeatureGroupInfo",
477 "android.content.pm.InstrumentationInfo",
478 "android.content.pm.PackageInfo",
479 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600480 "android.content.res.Configuration",
481 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700482 "android.os.Message",
483 "android.system.StructPollfd",
484 ]
485
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700486 for f in clazz.fields:
487 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700488 if clazz.fullname in IGNORE_BARE_FIELDS:
489 pass
490 elif clazz.fullname.endswith("LayoutParams"):
491 pass
492 elif clazz.fullname.startswith("android.util.Mutable"):
493 pass
494 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800495 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700496
497 if not "static" in f.split:
498 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800499 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700500
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700501 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800502 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700503
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700504 if re.match("[A-Z_]+", f.name):
505 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800506 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700507
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700508
509def verify_register(clazz):
510 """Verify parity of registration methods.
511 Callback objects use register/unregister methods.
512 Listener objects use add/remove methods."""
513 methods = [ m.name for m in clazz.methods ]
514 for m in clazz.methods:
515 if "Callback" in m.raw:
516 if m.name.startswith("register"):
517 other = "unregister" + m.name[8:]
518 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800519 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700520 if m.name.startswith("unregister"):
521 other = "register" + m.name[10:]
522 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800523 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700524
525 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800526 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700527
528 if "Listener" in m.raw:
529 if m.name.startswith("add"):
530 other = "remove" + m.name[3:]
531 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800532 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700533 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
534 other = "add" + m.name[6:]
535 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800536 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700537
538 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800539 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700540
541
542def verify_sync(clazz):
543 """Verify synchronized methods aren't exposed."""
544 for m in clazz.methods:
545 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800546 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700547
548
549def verify_intent_builder(clazz):
550 """Verify that Intent builders are createFooIntent() style."""
551 if clazz.name == "Intent": return
552
553 for m in clazz.methods:
554 if m.typ == "android.content.Intent":
555 if m.name.startswith("create") and m.name.endswith("Intent"):
556 pass
557 else:
Adam Powell539ea122015-04-10 13:01:37 -0700558 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700559
560
561def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700562 """Verify that helper classes are named consistently with what they extend.
563 All developer extendable methods should be named onFoo()."""
564 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700565 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700566 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700567 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800568 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700569
570 found = False
571 for f in clazz.fields:
572 if f.name == "SERVICE_INTERFACE":
573 found = True
574 if f.value != clazz.fullname:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800575 error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700576
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700577 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700578 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700579 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800580 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700581
582 found = False
583 for f in clazz.fields:
584 if f.name == "PROVIDER_INTERFACE":
585 found = True
586 if f.value != clazz.fullname:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800587 error(clazz, f, "C4", "Inconsistent interface constant; expected %s" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700588
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700589 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700590 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700591 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800592 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700593
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700594 if "extends android.app.Activity" in clazz.raw:
595 test_methods = True
596 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800597 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700598
599 if test_methods:
600 for m in clazz.methods:
601 if "final" in m.split: continue
602 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700603 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800604 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700605 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800606 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700607
608
609def verify_builder(clazz):
610 """Verify builder classes.
611 Methods should return the builder to enable chaining."""
612 if " extends " in clazz.raw: return
613 if not clazz.name.endswith("Builder"): return
614
615 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800616 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700617
618 has_build = False
619 for m in clazz.methods:
620 if m.name == "build":
621 has_build = True
622 continue
623
624 if m.name.startswith("get"): continue
625 if m.name.startswith("clear"): continue
626
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700627 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800628 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700629
630 if m.name.startswith("set"):
631 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800632 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700633
634 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800635 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700636
637
638def verify_aidl(clazz):
639 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700640 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800641 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700642
643
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700644def verify_internal(clazz):
645 """Catch people exposing internal classes."""
646 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800647 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700648
649
650def verify_layering(clazz):
651 """Catch package layering violations.
652 For example, something in android.os depending on android.app."""
653 ranking = [
654 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
655 "android.app",
656 "android.widget",
657 "android.view",
658 "android.animation",
659 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700660 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700661 "android.database",
662 "android.graphics",
663 "android.text",
664 "android.os",
665 "android.util"
666 ]
667
668 def rank(p):
669 for i in range(len(ranking)):
670 if isinstance(ranking[i], list):
671 for j in ranking[i]:
672 if p.startswith(j): return i
673 else:
674 if p.startswith(ranking[i]): return i
675
676 cr = rank(clazz.pkg.name)
677 if cr is None: return
678
679 for f in clazz.fields:
680 ir = rank(f.typ)
681 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800682 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700683
684 for m in clazz.methods:
685 ir = rank(m.typ)
686 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800687 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700688 for arg in m.args:
689 ir = rank(arg)
690 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800691 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700692
693
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800694def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800695 """Verifies that boolean accessors are named correctly.
696 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700697
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800698 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
699 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700700
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800701 gets = [ m for m in clazz.methods if is_get(m) ]
702 sets = [ m for m in clazz.methods if is_set(m) ]
703
704 def error_if_exists(methods, trigger, expected, actual):
705 for m in methods:
706 if m.name == actual:
707 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700708
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700709 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800710 if is_get(m):
711 if re.match("is[A-Z]", m.name):
712 target = m.name[2:]
713 expected = "setIs" + target
714 error_if_exists(sets, m.name, expected, "setHas" + target)
715 elif re.match("has[A-Z]", m.name):
716 target = m.name[3:]
717 expected = "setHas" + target
718 error_if_exists(sets, m.name, expected, "setIs" + target)
719 error_if_exists(sets, m.name, expected, "set" + target)
720 elif re.match("get[A-Z]", m.name):
721 target = m.name[3:]
722 expected = "set" + target
723 error_if_exists(sets, m.name, expected, "setIs" + target)
724 error_if_exists(sets, m.name, expected, "setHas" + target)
725
726 if is_set(m):
727 if re.match("set[A-Z]", m.name):
728 target = m.name[3:]
729 expected = "get" + target
730 error_if_exists(sets, m.name, expected, "is" + target)
731 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700732
733
734def verify_collections(clazz):
735 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700736 if clazz.fullname == "android.os.Bundle": return
737
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700738 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
739 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
740 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700741 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800742 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700743 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700744 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800745 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700746
747
748def verify_flags(clazz):
749 """Verifies that flags are non-overlapping."""
750 known = collections.defaultdict(int)
751 for f in clazz.fields:
752 if "FLAG_" in f.name:
753 try:
754 val = int(f.value)
755 except:
756 continue
757
758 scope = f.name[0:f.name.index("FLAG_")]
759 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800760 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700761 known[scope] |= val
762
763
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800764def verify_exception(clazz):
765 """Verifies that methods don't throw generic exceptions."""
766 for m in clazz.methods:
767 if "throws java.lang.Exception" in m.raw or "throws java.lang.Throwable" in m.raw or "throws java.lang.Error" in m.raw:
768 error(clazz, m, "S1", "Methods must not throw generic exceptions")
769
Jeff Sharkey331279b2016-02-29 16:02:02 -0700770 if "throws android.os.RemoteException" in m.raw:
771 if clazz.name == "android.content.ContentProviderClient": continue
772 if clazz.name == "android.os.Binder": continue
773 if clazz.name == "android.os.IBinder": continue
774
775 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
776
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800777
778def verify_google(clazz):
779 """Verifies that APIs never reference Google."""
780
781 if re.search("google", clazz.raw, re.IGNORECASE):
782 error(clazz, None, None, "Must never reference Google")
783
784 test = []
785 test.extend(clazz.ctors)
786 test.extend(clazz.fields)
787 test.extend(clazz.methods)
788
789 for t in test:
790 if re.search("google", t.raw, re.IGNORECASE):
791 error(clazz, t, None, "Must never reference Google")
792
793
794def verify_bitset(clazz):
795 """Verifies that we avoid using heavy BitSet."""
796
797 for f in clazz.fields:
798 if f.typ == "java.util.BitSet":
799 error(clazz, f, None, "Field type must not be heavy BitSet")
800
801 for m in clazz.methods:
802 if m.typ == "java.util.BitSet":
803 error(clazz, m, None, "Return type must not be heavy BitSet")
804 for arg in m.args:
805 if arg == "java.util.BitSet":
806 error(clazz, m, None, "Argument type must not be heavy BitSet")
807
808
809def verify_manager(clazz):
810 """Verifies that FooManager is only obtained from Context."""
811
812 if not clazz.name.endswith("Manager"): return
813
814 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800815 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800816
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600817 for m in clazz.methods:
818 if m.typ == clazz.fullname:
819 error(clazz, m, None, "Managers must always be obtained from Context")
820
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800821
822def verify_boxed(clazz):
823 """Verifies that methods avoid boxed primitives."""
824
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800825 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 -0800826
827 for c in clazz.ctors:
828 for arg in c.args:
829 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800830 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800831
832 for f in clazz.fields:
833 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800834 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800835
836 for m in clazz.methods:
837 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800838 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800839 for arg in m.args:
840 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800841 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800842
843
844def verify_static_utils(clazz):
845 """Verifies that helper classes can't be constructed."""
846 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600847 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800848
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600849 # Only care about classes with default constructors
850 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
851 test = []
852 test.extend(clazz.fields)
853 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800854
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600855 if len(test) == 0: return
856 for t in test:
857 if "static" not in t.split:
858 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800859
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800860 error(clazz, None, None, "Fully-static utility classes must not have constructor")
861
862
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800863def verify_overload_args(clazz):
864 """Verifies that method overloads add new arguments at the end."""
865 if clazz.fullname.startswith("android.opengl"): return
866
867 overloads = collections.defaultdict(list)
868 for m in clazz.methods:
869 if "deprecated" in m.split: continue
870 overloads[m.name].append(m)
871
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800872 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800873 if len(methods) <= 1: continue
874
875 # Look for arguments common across all overloads
876 def cluster(args):
877 count = collections.defaultdict(int)
878 res = set()
879 for i in range(len(args)):
880 a = args[i]
881 res.add("%s#%d" % (a, count[a]))
882 count[a] += 1
883 return res
884
885 common_args = cluster(methods[0].args)
886 for m in methods:
887 common_args = common_args & cluster(m.args)
888
889 if len(common_args) == 0: continue
890
891 # Require that all common arguments are present at start of signature
892 locked_sig = None
893 for m in methods:
894 sig = m.args[0:len(common_args)]
895 if not common_args.issubset(cluster(sig)):
896 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
897 elif not locked_sig:
898 locked_sig = sig
899 elif locked_sig != sig:
900 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
901
902
903def verify_callback_handlers(clazz):
904 """Verifies that methods adding listener/callback have overload
905 for specifying delivery thread."""
906
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800907 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800908 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800909 "animation",
910 "view",
911 "graphics",
912 "transition",
913 "widget",
914 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800915 ]
916 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800917 if s in clazz.pkg.name_path: return
918 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800919
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800920 # Ignore UI classes which assume main thread
921 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
922 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
923 if s in clazz.fullname: return
924 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
925 for s in ["Loader"]:
926 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800927
928 found = {}
929 by_name = collections.defaultdict(list)
930 for m in clazz.methods:
931 if m.name.startswith("unregister"): continue
932 if m.name.startswith("remove"): continue
933 if re.match("on[A-Z]+", m.name): continue
934
935 by_name[m.name].append(m)
936
937 for a in m.args:
938 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
939 found[m.name] = m
940
941 for f in found.values():
942 takes_handler = False
943 for m in by_name[f.name]:
944 if "android.os.Handler" in m.args:
945 takes_handler = True
946 if not takes_handler:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800947 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Handler")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800948
949
950def verify_context_first(clazz):
951 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800952 examine = clazz.ctors + clazz.methods
953 for m in examine:
954 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800955 if "android.content.Context" in m.args[1:]:
956 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600957 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
958 if "android.content.ContentResolver" in m.args[1:]:
959 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800960
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800961
962def verify_listener_last(clazz):
963 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
964 examine = clazz.ctors + clazz.methods
965 for m in examine:
966 if "Listener" in m.name or "Callback" in m.name: continue
967 found = False
968 for a in m.args:
969 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
970 found = True
971 elif found and a != "android.os.Handler":
972 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
973
974
975def verify_resource_names(clazz):
976 """Verifies that resource names have consistent case."""
977 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
978
979 # Resources defined by files are foo_bar_baz
980 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
981 for f in clazz.fields:
982 if re.match("[a-z1-9_]+$", f.name): continue
983 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
984
985 # Resources defined inside files are fooBarBaz
986 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
987 for f in clazz.fields:
988 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
989 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
990 if re.match("state_[a-z_]*$", f.name): continue
991
992 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
993 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
994
995 # Styles are FooBar_Baz
996 if clazz.name in ["style"]:
997 for f in clazz.fields:
998 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
999 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001000
1001
Jeff Sharkey331279b2016-02-29 16:02:02 -07001002def verify_files(clazz):
1003 """Verifies that methods accepting File also accept streams."""
1004
1005 has_file = set()
1006 has_stream = set()
1007
1008 test = []
1009 test.extend(clazz.ctors)
1010 test.extend(clazz.methods)
1011
1012 for m in test:
1013 if "java.io.File" in m.args:
1014 has_file.add(m)
1015 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:
1016 has_stream.add(m.name)
1017
1018 for m in has_file:
1019 if m.name not in has_stream:
1020 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1021
1022
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001023def verify_manager_list(clazz):
1024 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1025
1026 if not clazz.name.endswith("Manager"): return
1027
1028 for m in clazz.methods:
1029 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1030 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1031
1032
Jeff Sharkey26c80902016-12-21 13:41:17 -07001033def verify_abstract_inner(clazz):
1034 """Verifies that abstract inner classes are static."""
1035
1036 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1037 if " abstract " in clazz.raw and " static " not in clazz.raw:
1038 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1039
1040
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001041def verify_runtime_exceptions(clazz):
1042 """Verifies that runtime exceptions aren't listed in throws."""
1043
1044 banned = [
1045 "java.lang.NullPointerException",
1046 "java.lang.ClassCastException",
1047 "java.lang.IndexOutOfBoundsException",
1048 "java.lang.reflect.UndeclaredThrowableException",
1049 "java.lang.reflect.MalformedParametersException",
1050 "java.lang.reflect.MalformedParameterizedTypeException",
1051 "java.lang.invoke.WrongMethodTypeException",
1052 "java.lang.EnumConstantNotPresentException",
1053 "java.lang.IllegalMonitorStateException",
1054 "java.lang.SecurityException",
1055 "java.lang.UnsupportedOperationException",
1056 "java.lang.annotation.AnnotationTypeMismatchException",
1057 "java.lang.annotation.IncompleteAnnotationException",
1058 "java.lang.TypeNotPresentException",
1059 "java.lang.IllegalStateException",
1060 "java.lang.ArithmeticException",
1061 "java.lang.IllegalArgumentException",
1062 "java.lang.ArrayStoreException",
1063 "java.lang.NegativeArraySizeException",
1064 "java.util.MissingResourceException",
1065 "java.util.EmptyStackException",
1066 "java.util.concurrent.CompletionException",
1067 "java.util.concurrent.RejectedExecutionException",
1068 "java.util.IllformedLocaleException",
1069 "java.util.ConcurrentModificationException",
1070 "java.util.NoSuchElementException",
1071 "java.io.UncheckedIOException",
1072 "java.time.DateTimeException",
1073 "java.security.ProviderException",
1074 "java.nio.BufferUnderflowException",
1075 "java.nio.BufferOverflowException",
1076 ]
1077
1078 test = []
1079 test.extend(clazz.ctors)
1080 test.extend(clazz.methods)
1081
1082 for t in test:
1083 if " throws " not in t.raw: continue
1084 throws = t.raw[t.raw.index(" throws "):]
1085 for b in banned:
1086 if b in throws:
1087 error(clazz, t, None, "Methods must not mention RuntimeException subclasses in throws clauses")
1088
1089
1090def verify_error(clazz):
1091 """Verifies that we always use Exception instead of Error."""
1092 if not clazz.extends: return
1093 if clazz.extends.endswith("Error"):
1094 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1095 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1096 error(clazz, None, None, "Exceptions must be named FooException")
1097
1098
1099def verify_units(clazz):
1100 """Verifies that we use consistent naming for units."""
1101
1102 # If we find K, recommend replacing with V
1103 bad = {
1104 "Ns": "Nanos",
1105 "Ms": "Millis or Micros",
1106 "Sec": "Seconds", "Secs": "Seconds",
1107 "Hr": "Hours", "Hrs": "Hours",
1108 "Mo": "Months", "Mos": "Months",
1109 "Yr": "Years", "Yrs": "Years",
1110 "Byte": "Bytes", "Space": "Bytes",
1111 }
1112
1113 for m in clazz.methods:
1114 if m.typ not in ["short","int","long"]: continue
1115 for k, v in bad.iteritems():
1116 if m.name.endswith(k):
1117 error(clazz, m, None, "Expected method name units to be " + v)
1118 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1119 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1120 if m.name.endswith("Seconds"):
1121 error(clazz, m, None, "Returned time values must be in milliseconds")
1122
1123 for m in clazz.methods:
1124 typ = m.typ
1125 if typ == "void":
1126 if len(m.args) != 1: continue
1127 typ = m.args[0]
1128
1129 if m.name.endswith("Fraction") and typ != "float":
1130 error(clazz, m, None, "Fractions must use floats")
1131 if m.name.endswith("Percentage") and typ != "int":
1132 error(clazz, m, None, "Percentage must use ints")
1133
1134
1135def verify_closable(clazz):
1136 """Verifies that classes are AutoClosable."""
1137 if "implements java.lang.AutoCloseable" in clazz.raw: return
1138 if "implements java.io.Closeable" in clazz.raw: return
1139
1140 for m in clazz.methods:
1141 if len(m.args) > 0: continue
1142 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1143 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1144 return
1145
1146
Jake Wharton9e6738f2017-08-23 11:59:55 -04001147def verify_member_name_not_kotlin_keyword(clazz):
1148 """Prevent method names which are keywords in Kotlin."""
1149
1150 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1151 # This list does not include Java keywords as those are already impossible to use.
1152 keywords = [
1153 'as',
1154 'fun',
1155 'in',
1156 'is',
1157 'object',
1158 'typealias',
1159 'val',
1160 'var',
1161 'when',
1162 ]
1163
1164 for m in clazz.methods:
1165 if m.name in keywords:
1166 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1167 for f in clazz.fields:
1168 if f.name in keywords:
1169 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1170
1171
1172def verify_method_name_not_kotlin_operator(clazz):
1173 """Warn about method names which become operators in Kotlin."""
1174
1175 binary = set()
1176
1177 def unique_binary_op(m, op):
1178 if op in binary:
1179 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1180 binary.add(op)
1181
1182 for m in clazz.methods:
1183 if 'static' in m.split:
1184 continue
1185
1186 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1187 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1188 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1189
1190 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1191 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1192 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1193 # practical way of checking that relationship here.
1194 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1195
1196 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1197 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1198 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1199 unique_binary_op(m, m.name)
1200
1201 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1202 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1203 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1204
1205 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1206 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1207 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1208
1209 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1210 if m.name == 'invoke':
1211 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1212
1213 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1214 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1215 and len(m.args) == 1 \
1216 and m.typ == 'void':
1217 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1218 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1219
1220
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001221def examine_clazz(clazz):
1222 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001223
1224 notice(clazz)
1225
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001226 if clazz.pkg.name.startswith("java"): return
1227 if clazz.pkg.name.startswith("junit"): return
1228 if clazz.pkg.name.startswith("org.apache"): return
1229 if clazz.pkg.name.startswith("org.xml"): return
1230 if clazz.pkg.name.startswith("org.json"): return
1231 if clazz.pkg.name.startswith("org.w3c"): return
Jeff Sharkey331279b2016-02-29 16:02:02 -07001232 if clazz.pkg.name.startswith("android.icu."): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001233
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001234 verify_constants(clazz)
1235 verify_enums(clazz)
1236 verify_class_names(clazz)
1237 verify_method_names(clazz)
1238 verify_callbacks(clazz)
1239 verify_listeners(clazz)
1240 verify_actions(clazz)
1241 verify_extras(clazz)
1242 verify_equals(clazz)
1243 verify_parcelable(clazz)
1244 verify_protected(clazz)
1245 verify_fields(clazz)
1246 verify_register(clazz)
1247 verify_sync(clazz)
1248 verify_intent_builder(clazz)
1249 verify_helper_classes(clazz)
1250 verify_builder(clazz)
1251 verify_aidl(clazz)
1252 verify_internal(clazz)
1253 verify_layering(clazz)
1254 verify_boolean(clazz)
1255 verify_collections(clazz)
1256 verify_flags(clazz)
1257 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001258 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001259 verify_bitset(clazz)
1260 verify_manager(clazz)
1261 verify_boxed(clazz)
1262 verify_static_utils(clazz)
1263 verify_overload_args(clazz)
1264 verify_callback_handlers(clazz)
1265 verify_context_first(clazz)
1266 verify_listener_last(clazz)
1267 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001268 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001269 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001270 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001271 verify_runtime_exceptions(clazz)
1272 verify_error(clazz)
1273 verify_units(clazz)
1274 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001275 verify_member_name_not_kotlin_keyword(clazz)
1276 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001277
1278
1279def examine_stream(stream):
1280 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001281 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001282 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001283 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001284 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001285 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001286
1287
1288def examine_api(api):
1289 """Find all style issues in the given parsed API."""
1290 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001291 failures = {}
1292 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001293 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001294 return failures
1295
1296
Jeff Sharkey037458a2014-09-04 15:46:20 -07001297def verify_compat(cur, prev):
1298 """Find any incompatible API changes between two levels."""
1299 global failures
1300
1301 def class_exists(api, test):
1302 return test.fullname in api
1303
1304 def ctor_exists(api, clazz, test):
1305 for m in clazz.ctors:
1306 if m.ident == test.ident: return True
1307 return False
1308
1309 def all_methods(api, clazz):
1310 methods = list(clazz.methods)
1311 if clazz.extends is not None:
1312 methods.extend(all_methods(api, api[clazz.extends]))
1313 return methods
1314
1315 def method_exists(api, clazz, test):
1316 methods = all_methods(api, clazz)
1317 for m in methods:
1318 if m.ident == test.ident: return True
1319 return False
1320
1321 def field_exists(api, clazz, test):
1322 for f in clazz.fields:
1323 if f.ident == test.ident: return True
1324 return False
1325
1326 failures = {}
1327 for key in sorted(prev.keys()):
1328 prev_clazz = prev[key]
1329
1330 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001331 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001332 continue
1333
1334 cur_clazz = cur[key]
1335
1336 for test in prev_clazz.ctors:
1337 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001338 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001339
1340 methods = all_methods(prev, prev_clazz)
1341 for test in methods:
1342 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001343 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001344
1345 for test in prev_clazz.fields:
1346 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001347 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001348
1349 return failures
1350
1351
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001352if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001353 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1354 patterns. It ignores lint messages from a previous API level, if provided.")
1355 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1356 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1357 help="previous.txt")
1358 parser.add_argument("--no-color", action='store_const', const=True,
1359 help="Disable terminal colors")
1360 parser.add_argument("--allow-google", action='store_const', const=True,
1361 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001362 parser.add_argument("--show-noticed", action='store_const', const=True,
1363 help="Show API changes noticed")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001364 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001365
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001366 if args['no_color']:
1367 USE_COLOR = False
1368
1369 if args['allow_google']:
1370 ALLOW_GOOGLE = True
1371
1372 current_file = args['current.txt']
1373 previous_file = args['previous.txt']
1374
1375 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001376 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001377 if not previous_file is None:
1378 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001379 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001380
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001381 # ignore errors from previous API level
1382 for p in prev_fail:
1383 if p in cur_fail:
1384 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001385
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001386 # ignore classes unchanged from previous API level
1387 for k, v in prev_noticed.iteritems():
1388 if k in cur_noticed and v == cur_noticed[k]:
1389 del cur_noticed[k]
1390
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001391 """
1392 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001393 # look for compatibility issues
1394 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001395
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001396 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1397 for f in sorted(compat_fail):
1398 print compat_fail[f]
1399 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001400 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001401
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001402 if args['show_noticed'] and len(cur_noticed) != 0:
1403 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1404 for f in sorted(cur_noticed.keys()):
1405 print f
1406 print
1407
Jason Monk53b2a732017-11-10 15:43:17 -05001408 if len(cur_fail) != 0:
1409 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1410 for f in sorted(cur_fail):
1411 print cur_fail[f]
1412 print
1413 sys.exit(77)