blob: 399b0c63e113dd2713121c9db3e6d63eb284962a [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 = []
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700103 self.throws = []
104 target = self.args
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700105 for r in raw[2:]:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700106 if r == "throws": target = self.throws
107 else: target.append(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700108
Jeff Sharkey037458a2014-09-04 15:46:20 -0700109 # identity for compat purposes
110 ident = self.raw
111 ident = ident.replace(" deprecated ", " ")
112 ident = ident.replace(" synchronized ", " ")
113 ident = re.sub("<.+?>", "", ident)
114 if " throws " in ident:
115 ident = ident[:ident.index(" throws ")]
116 self.ident = ident
117
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700118 def __hash__(self):
119 return hash(self.raw)
120
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700121 def __repr__(self):
122 return self.raw
123
124
125class Class():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700126 def __init__(self, pkg, line, raw, blame):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700127 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700128 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700129 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700130 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700131 self.ctors = []
132 self.fields = []
133 self.methods = []
134
135 raw = raw.split()
136 self.split = list(raw)
137 if "class" in raw:
138 self.fullname = raw[raw.index("class")+1]
139 elif "interface" in raw:
140 self.fullname = raw[raw.index("interface")+1]
Jeff Sharkey037458a2014-09-04 15:46:20 -0700141 else:
142 raise ValueError("Funky class type %s" % (self.raw))
143
144 if "extends" in raw:
145 self.extends = raw[raw.index("extends")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800146 self.extends_path = self.extends.split(".")
Jeff Sharkey037458a2014-09-04 15:46:20 -0700147 else:
148 self.extends = None
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800149 self.extends_path = []
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700150
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700151 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800152 self.fullname_path = self.fullname.split(".")
153
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700154 self.name = self.fullname[self.fullname.rindex(".")+1:]
155
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700156 def __hash__(self):
157 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
158
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700159 def __repr__(self):
160 return self.raw
161
162
163class Package():
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700164 def __init__(self, line, raw, blame):
165 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700166 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700167 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700168
169 raw = raw.split()
170 self.name = raw[raw.index("package")+1]
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800171 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700172
173 def __repr__(self):
174 return self.raw
175
176
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800177def _parse_stream(f, clazz_cb=None):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700178 line = 0
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700179 api = {}
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700180 pkg = None
181 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700182 blame = None
183
184 re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800185 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700186 line += 1
187 raw = raw.rstrip()
188 match = re_blame.match(raw)
189 if match is not None:
190 blame = match.groups()[0:2]
191 raw = match.groups()[2]
192 else:
193 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700194
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700195 if raw.startswith("package"):
196 pkg = Package(line, raw, blame)
197 elif raw.startswith(" ") and raw.endswith("{"):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800198 # When provided with class callback, we treat as incremental
199 # parse and don't build up entire API
200 if clazz and clazz_cb:
201 clazz_cb(clazz)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700202 clazz = Class(pkg, line, raw, blame)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800203 if not clazz_cb:
204 api[clazz.fullname] = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700205 elif raw.startswith(" ctor"):
206 clazz.ctors.append(Method(clazz, line, raw, blame))
207 elif raw.startswith(" method"):
208 clazz.methods.append(Method(clazz, line, raw, blame))
209 elif raw.startswith(" field"):
210 clazz.fields.append(Field(clazz, line, raw, blame))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700211
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800212 # Handle last trailing class
213 if clazz and clazz_cb:
214 clazz_cb(clazz)
215
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700216 return api
217
218
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700219class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800220 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700221 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700222 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800223 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700224 self.msg = msg
225
226 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800227 self.head = "Error %s" % (rule) if rule else "Error"
228 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 -0700229 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800230 self.head = "Warning %s" % (rule) if rule else "Warning"
231 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 -0700232
233 self.line = clazz.line
234 blame = clazz.blame
235 if detail is not None:
236 dump += "\n in " + repr(detail)
237 self.line = detail.line
238 blame = detail.blame
239 dump += "\n in " + repr(clazz)
240 dump += "\n in " + repr(clazz.pkg)
241 dump += "\n at line " + repr(self.line)
242 if blame is not None:
243 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
244
245 self.dump = dump
246
247 def __repr__(self):
248 return self.dump
249
250
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700251failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700252
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800253def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700254 """Records an API failure to be processed later."""
255 global failures
256
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700257 sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg)
258 sig = sig.replace(" deprecated ", " ")
259
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800260 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700261
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700262
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800263def warn(clazz, detail, rule, msg):
264 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700265
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800266def error(clazz, detail, rule, msg):
267 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700268
269
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700270noticed = {}
271
272def notice(clazz):
273 global noticed
274
275 noticed[clazz.fullname] = hash(clazz)
276
277
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700278def verify_constants(clazz):
279 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700280 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600281 if clazz.fullname.startswith("android.os.Build"): return
282 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700283
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600284 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700285 for f in clazz.fields:
286 if "static" in f.split and "final" in f.split:
287 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800288 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600289 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700290 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
291 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600292 if f.typ in req and f.value is None:
293 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700294
295
296def verify_enums(clazz):
297 """Enums are bad, mmkay?"""
298 if "extends java.lang.Enum" in clazz.raw:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800299 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700300
301
302def verify_class_names(clazz):
303 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700304 if clazz.fullname.startswith("android.opengl"): return
305 if clazz.fullname.startswith("android.renderscript"): return
306 if re.match("android\.R\.[a-z]+", clazz.fullname): return
307
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700308 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800309 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700310 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800311 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700312
313
314def verify_method_names(clazz):
315 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700316 if clazz.fullname.startswith("android.opengl"): return
317 if clazz.fullname.startswith("android.renderscript"): return
318 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700319
320 for m in clazz.methods:
321 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800322 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700323 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800324 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700325
326
327def verify_callbacks(clazz):
328 """Verify Callback classes.
329 All callback classes must be abstract.
330 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700331 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700332
333 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800334 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700335 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800336 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700337
338 if clazz.name.endswith("Callback"):
339 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800340 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700341
342 for m in clazz.methods:
343 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800344 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700345
346
347def verify_listeners(clazz):
348 """Verify Listener classes.
349 All Listener classes must be interface.
350 All methods must follow onFoo() naming style.
351 If only a single method, it must match class name:
352 interface OnFooListener { void onFoo() }"""
353
354 if clazz.name.endswith("Listener"):
355 if " abstract class " in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800356 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700357
358 for m in clazz.methods:
359 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800360 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700361
362 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
363 m = clazz.methods[0]
364 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800365 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700366
367
368def verify_actions(clazz):
369 """Verify intent actions.
370 All action names must be named ACTION_FOO.
371 All action values must be scoped by package and match name:
372 package android.foo {
373 String ACTION_BAR = "android.foo.action.BAR";
374 }"""
375 for f in clazz.fields:
376 if f.value is None: continue
377 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700378 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600379 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700380
381 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
382 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
383 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800384 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700385 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700386 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700387 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700388 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700389 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700390 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
391 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700392 else:
393 prefix = clazz.pkg.name + ".action"
394 expected = prefix + "." + f.name[7:]
395 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700396 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700397
398
399def verify_extras(clazz):
400 """Verify intent extras.
401 All extra names must be named EXTRA_FOO.
402 All extra values must be scoped by package and match name:
403 package android.foo {
404 String EXTRA_BAR = "android.foo.extra.BAR";
405 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700406 if clazz.fullname == "android.app.Notification": return
407 if clazz.fullname == "android.appwidget.AppWidgetManager": return
408
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700409 for f in clazz.fields:
410 if f.value is None: continue
411 if f.name.startswith("ACTION_"): continue
412
413 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
414 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
415 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800416 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700417 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700418 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700419 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700420 elif clazz.pkg.name == "android.app.admin":
421 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700422 else:
423 prefix = clazz.pkg.name + ".extra"
424 expected = prefix + "." + f.name[6:]
425 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700426 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700427
428
429def verify_equals(clazz):
430 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e2016-12-21 13:46:33 -0700431 eq = False
432 hc = False
433 for m in clazz.methods:
434 if " static " in m.raw: continue
435 if "boolean equals(java.lang.Object)" in m.raw: eq = True
436 if "int hashCode()" in m.raw: hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700437 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800438 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700439
440
441def verify_parcelable(clazz):
442 """Verify that Parcelable objects aren't hiding required bits."""
443 if "implements android.os.Parcelable" in clazz.raw:
444 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
445 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
446 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
447
448 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800449 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700450
Joe LaPenna45380002017-04-20 12:49:48 -0700451 if ((" final class " not in clazz.raw) and
452 (" final deprecated class " not in clazz.raw)):
Jeff Sharkey331279b2016-02-29 16:02:02 -0700453 error(clazz, None, "FW8", "Parcelable classes must be final")
454
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700455 for c in clazz.ctors:
456 if c.args == ["android.os.Parcel"]:
457 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
458
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700459
460def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800461 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700462 for m in clazz.methods:
463 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800464 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700465 for f in clazz.fields:
466 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800467 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700468
469
470def verify_fields(clazz):
471 """Verify that all exposed fields are final.
472 Exposed fields must follow myName style.
473 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700474
475 IGNORE_BARE_FIELDS = [
476 "android.app.ActivityManager.RecentTaskInfo",
477 "android.app.Notification",
478 "android.content.pm.ActivityInfo",
479 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600480 "android.content.pm.ComponentInfo",
481 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700482 "android.content.pm.FeatureGroupInfo",
483 "android.content.pm.InstrumentationInfo",
484 "android.content.pm.PackageInfo",
485 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600486 "android.content.res.Configuration",
487 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700488 "android.os.Message",
489 "android.system.StructPollfd",
490 ]
491
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700492 for f in clazz.fields:
493 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700494 if clazz.fullname in IGNORE_BARE_FIELDS:
495 pass
496 elif clazz.fullname.endswith("LayoutParams"):
497 pass
498 elif clazz.fullname.startswith("android.util.Mutable"):
499 pass
500 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800501 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700502
503 if not "static" in f.split:
504 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800505 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700506
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700507 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800508 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700509
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700510 if re.match("[A-Z_]+", f.name):
511 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800512 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700513
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700514
515def verify_register(clazz):
516 """Verify parity of registration methods.
517 Callback objects use register/unregister methods.
518 Listener objects use add/remove methods."""
519 methods = [ m.name for m in clazz.methods ]
520 for m in clazz.methods:
521 if "Callback" in m.raw:
522 if m.name.startswith("register"):
523 other = "unregister" + m.name[8:]
524 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800525 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700526 if m.name.startswith("unregister"):
527 other = "register" + m.name[10:]
528 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800529 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700530
531 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800532 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700533
534 if "Listener" in m.raw:
535 if m.name.startswith("add"):
536 other = "remove" + m.name[3:]
537 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800538 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700539 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
540 other = "add" + m.name[6:]
541 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800542 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700543
544 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800545 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700546
547
548def verify_sync(clazz):
549 """Verify synchronized methods aren't exposed."""
550 for m in clazz.methods:
551 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800552 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700553
554
555def verify_intent_builder(clazz):
556 """Verify that Intent builders are createFooIntent() style."""
557 if clazz.name == "Intent": return
558
559 for m in clazz.methods:
560 if m.typ == "android.content.Intent":
561 if m.name.startswith("create") and m.name.endswith("Intent"):
562 pass
563 else:
Adam Powell539ea122015-04-10 13:01:37 -0700564 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700565
566
567def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700568 """Verify that helper classes are named consistently with what they extend.
569 All developer extendable methods should be named onFoo()."""
570 test_methods = False
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700571 if "extends android.app.Service" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700572 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700573 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800574 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700575
576 found = False
577 for f in clazz.fields:
578 if f.name == "SERVICE_INTERFACE":
579 found = True
580 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700581 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700582
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700583 if "extends android.content.ContentProvider" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700584 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700585 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800586 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700587
588 found = False
589 for f in clazz.fields:
590 if f.name == "PROVIDER_INTERFACE":
591 found = True
592 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700593 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700594
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700595 if "extends android.content.BroadcastReceiver" in clazz.raw:
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700596 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700597 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800598 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700599
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700600 if "extends android.app.Activity" in clazz.raw:
601 test_methods = True
602 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800603 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700604
605 if test_methods:
606 for m in clazz.methods:
607 if "final" in m.split: continue
608 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700609 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800610 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700611 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800612 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700613
614
615def verify_builder(clazz):
616 """Verify builder classes.
617 Methods should return the builder to enable chaining."""
618 if " extends " in clazz.raw: return
619 if not clazz.name.endswith("Builder"): return
620
621 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800622 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700623
624 has_build = False
625 for m in clazz.methods:
626 if m.name == "build":
627 has_build = True
628 continue
629
630 if m.name.startswith("get"): continue
631 if m.name.startswith("clear"): continue
632
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700633 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800634 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700635
636 if m.name.startswith("set"):
637 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800638 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700639
640 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800641 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700642
643
644def verify_aidl(clazz):
645 """Catch people exposing raw AIDL."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700646 if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800647 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700648
649
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700650def verify_internal(clazz):
651 """Catch people exposing internal classes."""
652 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800653 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700654
655
656def verify_layering(clazz):
657 """Catch package layering violations.
658 For example, something in android.os depending on android.app."""
659 ranking = [
660 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
661 "android.app",
662 "android.widget",
663 "android.view",
664 "android.animation",
665 "android.provider",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700666 ["android.content","android.graphics.drawable"],
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700667 "android.database",
668 "android.graphics",
669 "android.text",
670 "android.os",
671 "android.util"
672 ]
673
674 def rank(p):
675 for i in range(len(ranking)):
676 if isinstance(ranking[i], list):
677 for j in ranking[i]:
678 if p.startswith(j): return i
679 else:
680 if p.startswith(ranking[i]): return i
681
682 cr = rank(clazz.pkg.name)
683 if cr is None: return
684
685 for f in clazz.fields:
686 ir = rank(f.typ)
687 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800688 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700689
690 for m in clazz.methods:
691 ir = rank(m.typ)
692 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800693 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700694 for arg in m.args:
695 ir = rank(arg)
696 if ir and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800697 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -0700698
699
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800700def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800701 """Verifies that boolean accessors are named correctly.
702 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700703
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800704 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
705 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700706
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800707 gets = [ m for m in clazz.methods if is_get(m) ]
708 sets = [ m for m in clazz.methods if is_set(m) ]
709
710 def error_if_exists(methods, trigger, expected, actual):
711 for m in methods:
712 if m.name == actual:
713 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700714
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700715 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800716 if is_get(m):
717 if re.match("is[A-Z]", m.name):
718 target = m.name[2:]
719 expected = "setIs" + target
720 error_if_exists(sets, m.name, expected, "setHas" + target)
721 elif re.match("has[A-Z]", m.name):
722 target = m.name[3:]
723 expected = "setHas" + target
724 error_if_exists(sets, m.name, expected, "setIs" + target)
725 error_if_exists(sets, m.name, expected, "set" + target)
726 elif re.match("get[A-Z]", m.name):
727 target = m.name[3:]
728 expected = "set" + target
729 error_if_exists(sets, m.name, expected, "setIs" + target)
730 error_if_exists(sets, m.name, expected, "setHas" + target)
731
732 if is_set(m):
733 if re.match("set[A-Z]", m.name):
734 target = m.name[3:]
735 expected = "get" + target
736 error_if_exists(sets, m.name, expected, "is" + target)
737 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700738
739
740def verify_collections(clazz):
741 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700742 if clazz.fullname == "android.os.Bundle": return
743
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700744 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
745 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
746 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700747 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800748 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700749 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700750 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800751 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700752
753
754def verify_flags(clazz):
755 """Verifies that flags are non-overlapping."""
756 known = collections.defaultdict(int)
757 for f in clazz.fields:
758 if "FLAG_" in f.name:
759 try:
760 val = int(f.value)
761 except:
762 continue
763
764 scope = f.name[0:f.name.index("FLAG_")]
765 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800766 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700767 known[scope] |= val
768
769
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800770def verify_exception(clazz):
771 """Verifies that methods don't throw generic exceptions."""
772 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700773 for t in m.throws:
774 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
775 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800776
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700777 if t in ["android.os.RemoteException"]:
778 if clazz.name == "android.content.ContentProviderClient": continue
779 if clazz.name == "android.os.Binder": continue
780 if clazz.name == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -0700781
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700782 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
783
784 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
785 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -0700786
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800787
788def verify_google(clazz):
789 """Verifies that APIs never reference Google."""
790
791 if re.search("google", clazz.raw, re.IGNORECASE):
792 error(clazz, None, None, "Must never reference Google")
793
794 test = []
795 test.extend(clazz.ctors)
796 test.extend(clazz.fields)
797 test.extend(clazz.methods)
798
799 for t in test:
800 if re.search("google", t.raw, re.IGNORECASE):
801 error(clazz, t, None, "Must never reference Google")
802
803
804def verify_bitset(clazz):
805 """Verifies that we avoid using heavy BitSet."""
806
807 for f in clazz.fields:
808 if f.typ == "java.util.BitSet":
809 error(clazz, f, None, "Field type must not be heavy BitSet")
810
811 for m in clazz.methods:
812 if m.typ == "java.util.BitSet":
813 error(clazz, m, None, "Return type must not be heavy BitSet")
814 for arg in m.args:
815 if arg == "java.util.BitSet":
816 error(clazz, m, None, "Argument type must not be heavy BitSet")
817
818
819def verify_manager(clazz):
820 """Verifies that FooManager is only obtained from Context."""
821
822 if not clazz.name.endswith("Manager"): return
823
824 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800825 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800826
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600827 for m in clazz.methods:
828 if m.typ == clazz.fullname:
829 error(clazz, m, None, "Managers must always be obtained from Context")
830
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800831
832def verify_boxed(clazz):
833 """Verifies that methods avoid boxed primitives."""
834
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800835 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 -0800836
837 for c in clazz.ctors:
838 for arg in c.args:
839 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800840 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800841
842 for f in clazz.fields:
843 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800844 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800845
846 for m in clazz.methods:
847 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800848 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800849 for arg in m.args:
850 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800851 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800852
853
854def verify_static_utils(clazz):
855 """Verifies that helper classes can't be constructed."""
856 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600857 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800858
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600859 # Only care about classes with default constructors
860 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
861 test = []
862 test.extend(clazz.fields)
863 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800864
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600865 if len(test) == 0: return
866 for t in test:
867 if "static" not in t.split:
868 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800869
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800870 error(clazz, None, None, "Fully-static utility classes must not have constructor")
871
872
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800873def verify_overload_args(clazz):
874 """Verifies that method overloads add new arguments at the end."""
875 if clazz.fullname.startswith("android.opengl"): return
876
877 overloads = collections.defaultdict(list)
878 for m in clazz.methods:
879 if "deprecated" in m.split: continue
880 overloads[m.name].append(m)
881
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800882 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800883 if len(methods) <= 1: continue
884
885 # Look for arguments common across all overloads
886 def cluster(args):
887 count = collections.defaultdict(int)
888 res = set()
889 for i in range(len(args)):
890 a = args[i]
891 res.add("%s#%d" % (a, count[a]))
892 count[a] += 1
893 return res
894
895 common_args = cluster(methods[0].args)
896 for m in methods:
897 common_args = common_args & cluster(m.args)
898
899 if len(common_args) == 0: continue
900
901 # Require that all common arguments are present at start of signature
902 locked_sig = None
903 for m in methods:
904 sig = m.args[0:len(common_args)]
905 if not common_args.issubset(cluster(sig)):
906 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
907 elif not locked_sig:
908 locked_sig = sig
909 elif locked_sig != sig:
910 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
911
912
913def verify_callback_handlers(clazz):
914 """Verifies that methods adding listener/callback have overload
915 for specifying delivery thread."""
916
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800917 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800918 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800919 "animation",
920 "view",
921 "graphics",
922 "transition",
923 "widget",
924 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800925 ]
926 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800927 if s in clazz.pkg.name_path: return
928 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800929
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800930 # Ignore UI classes which assume main thread
931 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
932 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
933 if s in clazz.fullname: return
934 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
935 for s in ["Loader"]:
936 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800937
938 found = {}
939 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700940 examine = clazz.ctors + clazz.methods
941 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800942 if m.name.startswith("unregister"): continue
943 if m.name.startswith("remove"): continue
944 if re.match("on[A-Z]+", m.name): continue
945
946 by_name[m.name].append(m)
947
948 for a in m.args:
949 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
950 found[m.name] = m
951
952 for f in found.values():
953 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700954 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800955 for m in by_name[f.name]:
956 if "android.os.Handler" in m.args:
957 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -0700958 if "java.util.concurrent.Executor" in m.args:
959 takes_exec = True
960 if not takes_exec:
961 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800962
963
964def verify_context_first(clazz):
965 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800966 examine = clazz.ctors + clazz.methods
967 for m in examine:
968 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800969 if "android.content.Context" in m.args[1:]:
970 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600971 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
972 if "android.content.ContentResolver" in m.args[1:]:
973 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800974
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800975
976def verify_listener_last(clazz):
977 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
978 examine = clazz.ctors + clazz.methods
979 for m in examine:
980 if "Listener" in m.name or "Callback" in m.name: continue
981 found = False
982 for a in m.args:
983 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
984 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700985 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800986 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
987
988
989def verify_resource_names(clazz):
990 """Verifies that resource names have consistent case."""
991 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
992
993 # Resources defined by files are foo_bar_baz
994 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
995 for f in clazz.fields:
996 if re.match("[a-z1-9_]+$", f.name): continue
997 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
998
999 # Resources defined inside files are fooBarBaz
1000 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1001 for f in clazz.fields:
1002 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1003 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1004 if re.match("state_[a-z_]*$", f.name): continue
1005
1006 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1007 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1008
1009 # Styles are FooBar_Baz
1010 if clazz.name in ["style"]:
1011 for f in clazz.fields:
1012 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1013 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001014
1015
Jeff Sharkey331279b2016-02-29 16:02:02 -07001016def verify_files(clazz):
1017 """Verifies that methods accepting File also accept streams."""
1018
1019 has_file = set()
1020 has_stream = set()
1021
1022 test = []
1023 test.extend(clazz.ctors)
1024 test.extend(clazz.methods)
1025
1026 for m in test:
1027 if "java.io.File" in m.args:
1028 has_file.add(m)
1029 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:
1030 has_stream.add(m.name)
1031
1032 for m in has_file:
1033 if m.name not in has_stream:
1034 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1035
1036
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001037def verify_manager_list(clazz):
1038 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1039
1040 if not clazz.name.endswith("Manager"): return
1041
1042 for m in clazz.methods:
1043 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1044 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1045
1046
Jeff Sharkey26c80902016-12-21 13:41:17 -07001047def verify_abstract_inner(clazz):
1048 """Verifies that abstract inner classes are static."""
1049
1050 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
1051 if " abstract " in clazz.raw and " static " not in clazz.raw:
1052 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1053
1054
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001055def verify_runtime_exceptions(clazz):
1056 """Verifies that runtime exceptions aren't listed in throws."""
1057
1058 banned = [
1059 "java.lang.NullPointerException",
1060 "java.lang.ClassCastException",
1061 "java.lang.IndexOutOfBoundsException",
1062 "java.lang.reflect.UndeclaredThrowableException",
1063 "java.lang.reflect.MalformedParametersException",
1064 "java.lang.reflect.MalformedParameterizedTypeException",
1065 "java.lang.invoke.WrongMethodTypeException",
1066 "java.lang.EnumConstantNotPresentException",
1067 "java.lang.IllegalMonitorStateException",
1068 "java.lang.SecurityException",
1069 "java.lang.UnsupportedOperationException",
1070 "java.lang.annotation.AnnotationTypeMismatchException",
1071 "java.lang.annotation.IncompleteAnnotationException",
1072 "java.lang.TypeNotPresentException",
1073 "java.lang.IllegalStateException",
1074 "java.lang.ArithmeticException",
1075 "java.lang.IllegalArgumentException",
1076 "java.lang.ArrayStoreException",
1077 "java.lang.NegativeArraySizeException",
1078 "java.util.MissingResourceException",
1079 "java.util.EmptyStackException",
1080 "java.util.concurrent.CompletionException",
1081 "java.util.concurrent.RejectedExecutionException",
1082 "java.util.IllformedLocaleException",
1083 "java.util.ConcurrentModificationException",
1084 "java.util.NoSuchElementException",
1085 "java.io.UncheckedIOException",
1086 "java.time.DateTimeException",
1087 "java.security.ProviderException",
1088 "java.nio.BufferUnderflowException",
1089 "java.nio.BufferOverflowException",
1090 ]
1091
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001092 examine = clazz.ctors + clazz.methods
1093 for m in examine:
1094 for t in m.throws:
1095 if t in banned:
1096 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001097
1098
1099def verify_error(clazz):
1100 """Verifies that we always use Exception instead of Error."""
1101 if not clazz.extends: return
1102 if clazz.extends.endswith("Error"):
1103 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1104 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1105 error(clazz, None, None, "Exceptions must be named FooException")
1106
1107
1108def verify_units(clazz):
1109 """Verifies that we use consistent naming for units."""
1110
1111 # If we find K, recommend replacing with V
1112 bad = {
1113 "Ns": "Nanos",
1114 "Ms": "Millis or Micros",
1115 "Sec": "Seconds", "Secs": "Seconds",
1116 "Hr": "Hours", "Hrs": "Hours",
1117 "Mo": "Months", "Mos": "Months",
1118 "Yr": "Years", "Yrs": "Years",
1119 "Byte": "Bytes", "Space": "Bytes",
1120 }
1121
1122 for m in clazz.methods:
1123 if m.typ not in ["short","int","long"]: continue
1124 for k, v in bad.iteritems():
1125 if m.name.endswith(k):
1126 error(clazz, m, None, "Expected method name units to be " + v)
1127 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1128 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1129 if m.name.endswith("Seconds"):
1130 error(clazz, m, None, "Returned time values must be in milliseconds")
1131
1132 for m in clazz.methods:
1133 typ = m.typ
1134 if typ == "void":
1135 if len(m.args) != 1: continue
1136 typ = m.args[0]
1137
1138 if m.name.endswith("Fraction") and typ != "float":
1139 error(clazz, m, None, "Fractions must use floats")
1140 if m.name.endswith("Percentage") and typ != "int":
1141 error(clazz, m, None, "Percentage must use ints")
1142
1143
1144def verify_closable(clazz):
1145 """Verifies that classes are AutoClosable."""
1146 if "implements java.lang.AutoCloseable" in clazz.raw: return
1147 if "implements java.io.Closeable" in clazz.raw: return
1148
1149 for m in clazz.methods:
1150 if len(m.args) > 0: continue
1151 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1152 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1153 return
1154
1155
Jake Wharton9e6738f2017-08-23 11:59:55 -04001156def verify_member_name_not_kotlin_keyword(clazz):
1157 """Prevent method names which are keywords in Kotlin."""
1158
1159 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1160 # This list does not include Java keywords as those are already impossible to use.
1161 keywords = [
1162 'as',
1163 'fun',
1164 'in',
1165 'is',
1166 'object',
1167 'typealias',
1168 'val',
1169 'var',
1170 'when',
1171 ]
1172
1173 for m in clazz.methods:
1174 if m.name in keywords:
1175 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1176 for f in clazz.fields:
1177 if f.name in keywords:
1178 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1179
1180
1181def verify_method_name_not_kotlin_operator(clazz):
1182 """Warn about method names which become operators in Kotlin."""
1183
1184 binary = set()
1185
1186 def unique_binary_op(m, op):
1187 if op in binary:
1188 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1189 binary.add(op)
1190
1191 for m in clazz.methods:
1192 if 'static' in m.split:
1193 continue
1194
1195 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1196 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1197 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1198
1199 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1200 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1201 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1202 # practical way of checking that relationship here.
1203 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1204
1205 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1206 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1207 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1208 unique_binary_op(m, m.name)
1209
1210 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1211 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1212 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1213
1214 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1215 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1216 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1217
1218 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1219 if m.name == 'invoke':
1220 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1221
1222 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1223 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1224 and len(m.args) == 1 \
1225 and m.typ == 'void':
1226 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1227 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1228
1229
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001230def verify_collections_over_arrays(clazz):
1231 """Warn that [] should be Collections."""
1232
1233 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1234 for m in clazz.methods:
1235 if m.typ.endswith("[]") and m.typ not in safe:
1236 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1237 for arg in m.args:
1238 if arg.endswith("[]") and arg not in safe:
1239 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1240
1241
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001242def verify_user_handle(clazz):
1243 """Methods taking UserHandle should be ForUser or AsUser."""
1244 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1245 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1246 if clazz.fullname == "android.content.pm.LauncherApps": return
1247 if clazz.fullname == "android.os.UserHandle": return
1248 if clazz.fullname == "android.os.UserManager": return
1249
1250 for m in clazz.methods:
1251 if m.name.endswith("AsUser") or m.name.endswith("ForUser"): continue
1252 if re.match("on[A-Z]+", m.name): continue
1253 if "android.os.UserHandle" in m.args:
1254 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' or 'queryFooForUser'")
1255
1256
1257def verify_params(clazz):
1258 """Parameter classes should be 'Params'."""
1259 if clazz.name.endswith("Params"): return
1260 if clazz.fullname == "android.app.ActivityOptions": return
1261 if clazz.fullname == "android.app.BroadcastOptions": return
1262 if clazz.fullname == "android.os.Bundle": return
1263 if clazz.fullname == "android.os.BaseBundle": return
1264 if clazz.fullname == "android.os.PersistableBundle": return
1265
1266 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1267 for b in bad:
1268 if clazz.name.endswith(b):
1269 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1270
1271
1272def verify_services(clazz):
1273 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1274 if clazz.fullname != "android.content.Context": return
1275
1276 for f in clazz.fields:
1277 if f.typ != "java.lang.String": continue
1278 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1279 if found:
1280 expected = found.group(1).lower()
1281 if f.value != expected:
1282 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1283
1284
1285def verify_tense(clazz):
1286 """Verify tenses of method names."""
1287 if clazz.fullname.startswith("android.opengl"): return
1288
1289 for m in clazz.methods:
1290 if m.name.endswith("Enable"):
1291 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1292
1293
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001294def examine_clazz(clazz):
1295 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001296
1297 notice(clazz)
1298
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001299 if clazz.pkg.name.startswith("java"): return
1300 if clazz.pkg.name.startswith("junit"): return
1301 if clazz.pkg.name.startswith("org.apache"): return
1302 if clazz.pkg.name.startswith("org.xml"): return
1303 if clazz.pkg.name.startswith("org.json"): return
1304 if clazz.pkg.name.startswith("org.w3c"): return
Jeff Sharkey331279b2016-02-29 16:02:02 -07001305 if clazz.pkg.name.startswith("android.icu."): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001306
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001307 verify_constants(clazz)
1308 verify_enums(clazz)
1309 verify_class_names(clazz)
1310 verify_method_names(clazz)
1311 verify_callbacks(clazz)
1312 verify_listeners(clazz)
1313 verify_actions(clazz)
1314 verify_extras(clazz)
1315 verify_equals(clazz)
1316 verify_parcelable(clazz)
1317 verify_protected(clazz)
1318 verify_fields(clazz)
1319 verify_register(clazz)
1320 verify_sync(clazz)
1321 verify_intent_builder(clazz)
1322 verify_helper_classes(clazz)
1323 verify_builder(clazz)
1324 verify_aidl(clazz)
1325 verify_internal(clazz)
1326 verify_layering(clazz)
1327 verify_boolean(clazz)
1328 verify_collections(clazz)
1329 verify_flags(clazz)
1330 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001331 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001332 verify_bitset(clazz)
1333 verify_manager(clazz)
1334 verify_boxed(clazz)
1335 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001336 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001337 verify_callback_handlers(clazz)
1338 verify_context_first(clazz)
1339 verify_listener_last(clazz)
1340 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001341 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001342 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001343 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001344 verify_runtime_exceptions(clazz)
1345 verify_error(clazz)
1346 verify_units(clazz)
1347 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04001348 verify_member_name_not_kotlin_keyword(clazz)
1349 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001350 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001351 verify_user_handle(clazz)
1352 verify_params(clazz)
1353 verify_services(clazz)
1354 verify_tense(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001355
1356
1357def examine_stream(stream):
1358 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001359 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001360 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001361 noticed = {}
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001362 _parse_stream(stream, examine_clazz)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001363 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001364
1365
1366def examine_api(api):
1367 """Find all style issues in the given parsed API."""
1368 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001369 failures = {}
1370 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001371 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001372 return failures
1373
1374
Jeff Sharkey037458a2014-09-04 15:46:20 -07001375def verify_compat(cur, prev):
1376 """Find any incompatible API changes between two levels."""
1377 global failures
1378
1379 def class_exists(api, test):
1380 return test.fullname in api
1381
1382 def ctor_exists(api, clazz, test):
1383 for m in clazz.ctors:
1384 if m.ident == test.ident: return True
1385 return False
1386
1387 def all_methods(api, clazz):
1388 methods = list(clazz.methods)
1389 if clazz.extends is not None:
1390 methods.extend(all_methods(api, api[clazz.extends]))
1391 return methods
1392
1393 def method_exists(api, clazz, test):
1394 methods = all_methods(api, clazz)
1395 for m in methods:
1396 if m.ident == test.ident: return True
1397 return False
1398
1399 def field_exists(api, clazz, test):
1400 for f in clazz.fields:
1401 if f.ident == test.ident: return True
1402 return False
1403
1404 failures = {}
1405 for key in sorted(prev.keys()):
1406 prev_clazz = prev[key]
1407
1408 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001409 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001410 continue
1411
1412 cur_clazz = cur[key]
1413
1414 for test in prev_clazz.ctors:
1415 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001416 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001417
1418 methods = all_methods(prev, prev_clazz)
1419 for test in methods:
1420 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001421 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001422
1423 for test in prev_clazz.fields:
1424 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001425 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07001426
1427 return failures
1428
1429
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001430if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001431 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
1432 patterns. It ignores lint messages from a previous API level, if provided.")
1433 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
1434 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
1435 help="previous.txt")
1436 parser.add_argument("--no-color", action='store_const', const=True,
1437 help="Disable terminal colors")
1438 parser.add_argument("--allow-google", action='store_const', const=True,
1439 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001440 parser.add_argument("--show-noticed", action='store_const', const=True,
1441 help="Show API changes noticed")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001442 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001443
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001444 if args['no_color']:
1445 USE_COLOR = False
1446
1447 if args['allow_google']:
1448 ALLOW_GOOGLE = True
1449
1450 current_file = args['current.txt']
1451 previous_file = args['previous.txt']
1452
1453 with current_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001454 cur_fail, cur_noticed = examine_stream(f)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001455 if not previous_file is None:
1456 with previous_file as f:
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001457 prev_fail, prev_noticed = examine_stream(f)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001458
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001459 # ignore errors from previous API level
1460 for p in prev_fail:
1461 if p in cur_fail:
1462 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001463
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001464 # ignore classes unchanged from previous API level
1465 for k, v in prev_noticed.iteritems():
1466 if k in cur_noticed and v == cur_noticed[k]:
1467 del cur_noticed[k]
1468
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001469 """
1470 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001471 # look for compatibility issues
1472 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001473
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001474 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1475 for f in sorted(compat_fail):
1476 print compat_fail[f]
1477 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001478 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07001479
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001480 if args['show_noticed'] and len(cur_noticed) != 0:
1481 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1482 for f in sorted(cur_noticed.keys()):
1483 print f
1484 print
1485
Jason Monk53b2a732017-11-10 15:43:17 -05001486 if len(cur_fail) != 0:
1487 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
1488 for f in sorted(cur_fail):
1489 print cur_fail[f]
1490 print
1491 sys.exit(77)