blob: 52707c28286ef8ba3fd897024a8123fd491cc3f5 [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
Adrian Roos1f1b6a82019-01-05 20:09:38 +010029import re, sys, collections, traceback, argparse, itertools
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():
Adrian Roosb787c182019-01-03 18:54:33 +010054 def __init__(self, clazz, line, raw, blame, sig_format = 1):
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
Adrian Roosb787c182019-01-03 18:54:33 +010060 if sig_format == 2:
61 V2LineParser(raw).parse_into_field(self)
62 elif sig_format == 1:
63 # drop generics for now; may need multiple passes
64 raw = re.sub("<[^<]+?>", "", raw)
65 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -060066
Adrian Roosb787c182019-01-03 18:54:33 +010067 raw = raw.split()
68 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070069
Adrian Roosb787c182019-01-03 18:54:33 +010070 for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]:
71 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -070072
Adrian Roosb787c182019-01-03 18:54:33 +010073 # ignore annotations for now
74 raw = [ r for r in raw if not r.startswith("@") ]
Jeff Sharkey40d67f42018-07-17 13:29:40 -060075
Adrian Roosb787c182019-01-03 18:54:33 +010076 self.typ = raw[0]
77 self.name = raw[1].strip(";")
78 if len(raw) >= 4 and raw[2] == "=":
79 self.value = raw[3].strip(';"')
80 else:
81 self.value = None
Adrian Roos80545ef2019-02-27 16:45:00 +010082 self.annotations = []
Adrian Roosb787c182019-01-03 18:54:33 +010083
84 self.ident = "-".join((self.typ, self.name, self.value or ""))
Jeff Sharkey037458a2014-09-04 15:46:20 -070085
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -070086 def __hash__(self):
87 return hash(self.raw)
88
Jeff Sharkey8190f4882014-08-28 12:24:07 -070089 def __repr__(self):
90 return self.raw
91
Adrian Roos80545ef2019-02-27 16:45:00 +010092
93class Argument(object):
94
95 __slots__ = ["type", "annotations", "name", "default"]
96
97 def __init__(self, type):
98 self.type = type
99 self.annotations = []
100 self.name = None
101 self.default = None
102
103
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700104class Method():
Adrian Roosb787c182019-01-03 18:54:33 +0100105 def __init__(self, clazz, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700106 self.clazz = clazz
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700107 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700108 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700109 self.blame = blame
110
Adrian Roosb787c182019-01-03 18:54:33 +0100111 if sig_format == 2:
112 V2LineParser(raw).parse_into_method(self)
113 elif sig_format == 1:
114 # drop generics for now; may need multiple passes
115 raw = re.sub("<[^<]+?>", "", raw)
116 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700117
Adrian Roosb787c182019-01-03 18:54:33 +0100118 # handle each clause differently
119 raw_prefix, raw_args, _, raw_throws = re.match(r"(.*?)\((.*?)\)( throws )?(.*?);$", raw).groups()
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600120
Adrian Roosb787c182019-01-03 18:54:33 +0100121 # parse prefixes
122 raw = re.split("[\s]+", raw_prefix)
123 for r in ["", ";"]:
124 while r in raw: raw.remove(r)
125 self.split = list(raw)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700126
Adrian Roosb787c182019-01-03 18:54:33 +0100127 for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract", "default", "operator", "synchronized"]:
128 while r in raw: raw.remove(r)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700129
Adrian Roosb787c182019-01-03 18:54:33 +0100130 self.typ = raw[0]
131 self.name = raw[1]
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600132
Adrian Roosb787c182019-01-03 18:54:33 +0100133 # parse args
Adrian Roos80545ef2019-02-27 16:45:00 +0100134 self.detailed_args = []
Adrian Roosb787c182019-01-03 18:54:33 +0100135 for arg in re.split(",\s*", raw_args):
136 arg = re.split("\s", arg)
137 # ignore annotations for now
138 arg = [ a for a in arg if not a.startswith("@") ]
139 if len(arg[0]) > 0:
Adrian Roos80545ef2019-02-27 16:45:00 +0100140 self.detailed_args.append(Argument(arg[0]))
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600141
Adrian Roosb787c182019-01-03 18:54:33 +0100142 # parse throws
143 self.throws = []
144 for throw in re.split(",\s*", raw_throws):
145 self.throws.append(throw)
Adrian Roos80545ef2019-02-27 16:45:00 +0100146
147 self.annotations = []
Adrian Roosb787c182019-01-03 18:54:33 +0100148 else:
149 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600150
Adrian Roos80545ef2019-02-27 16:45:00 +0100151 self.args = map(lambda a: a.type, self.detailed_args)
Adrian Roosb787c182019-01-03 18:54:33 +0100152 self.ident = "-".join((self.typ, self.name, "-".join(self.args)))
153
154 def sig_matches(self, typ, name, args):
155 return typ == self.typ and name == self.name and args == self.args
Jeff Sharkey037458a2014-09-04 15:46:20 -0700156
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700157 def __hash__(self):
158 return hash(self.raw)
159
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700160 def __repr__(self):
161 return self.raw
162
163
164class Class():
Adrian Roosb787c182019-01-03 18:54:33 +0100165 def __init__(self, pkg, line, raw, blame, sig_format = 1):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700166 self.pkg = pkg
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700167 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700168 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700169 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700170 self.ctors = []
171 self.fields = []
172 self.methods = []
173
Adrian Roosb787c182019-01-03 18:54:33 +0100174 if sig_format == 2:
175 V2LineParser(raw).parse_into_class(self)
176 elif sig_format == 1:
177 # drop generics for now; may need multiple passes
178 raw = re.sub("<[^<]+?>", "", raw)
179 raw = re.sub("<[^<]+?>", "", raw)
Jeff Sharkey40d67f42018-07-17 13:29:40 -0600180
Adrian Roosb787c182019-01-03 18:54:33 +0100181 raw = raw.split()
182 self.split = list(raw)
183 if "class" in raw:
184 self.fullname = raw[raw.index("class")+1]
185 elif "interface" in raw:
186 self.fullname = raw[raw.index("interface")+1]
187 elif "@interface" in raw:
188 self.fullname = raw[raw.index("@interface")+1]
189 else:
190 raise ValueError("Funky class type %s" % (self.raw))
Jeff Sharkey037458a2014-09-04 15:46:20 -0700191
Adrian Roosb787c182019-01-03 18:54:33 +0100192 if "extends" in raw:
193 self.extends = raw[raw.index("extends")+1]
194 else:
195 self.extends = None
196
197 if "implements" in raw:
198 self.implements = raw[raw.index("implements")+1]
Adrian Roos02e18dd2019-02-28 12:41:48 +0100199 self.implements_all = [self.implements]
Adrian Roosb787c182019-01-03 18:54:33 +0100200 else:
201 self.implements = None
Adrian Roos02e18dd2019-02-28 12:41:48 +0100202 self.implements_all = []
Jeff Sharkey037458a2014-09-04 15:46:20 -0700203 else:
Adrian Roosb787c182019-01-03 18:54:33 +0100204 raise ValueError("Unknown signature format: " + sig_format)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700205
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700206 self.fullname = self.pkg.name + "." + self.fullname
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800207 self.fullname_path = self.fullname.split(".")
208
Adrian Roosb787c182019-01-03 18:54:33 +0100209 if self.extends is not None:
210 self.extends_path = self.extends.split(".")
211 else:
212 self.extends_path = []
213
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700214 self.name = self.fullname[self.fullname.rindex(".")+1:]
215
Adrian Roos6eb57b02018-12-13 22:08:29 +0100216 def merge_from(self, other):
217 self.ctors.extend(other.ctors)
218 self.fields.extend(other.fields)
219 self.methods.extend(other.methods)
220
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700221 def __hash__(self):
222 return hash((self.raw, tuple(self.ctors), tuple(self.fields), tuple(self.methods)))
223
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700224 def __repr__(self):
225 return self.raw
226
227
228class Package():
Adrian Roosd9871b12019-02-28 12:42:22 +0100229 NAME = re.compile("package(?: .*)? ([A-Za-z0-9.]+)")
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100230
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700231 def __init__(self, line, raw, blame):
232 self.line = line
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700233 self.raw = raw.strip(" {;")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700234 self.blame = blame
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700235
Adrian Roosb1faa0b2019-02-26 11:54:40 +0100236 self.name = Package.NAME.match(raw).group(1)
Jeff Sharkey047d7f02015-02-25 11:27:55 -0800237 self.name_path = self.name.split(".")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700238
239 def __repr__(self):
240 return self.raw
241
Adrian Roose5eeae72019-01-04 20:10:06 +0100242class V2Tokenizer(object):
243 __slots__ = ["raw"]
244
Adrian Rooscf82e042019-01-29 15:01:28 +0100245 SIGNATURE_PREFIX = "// Signature format: "
Adrian Roos5cdfb692019-01-05 22:04:55 +0100246 DELIMITER = re.compile(r'\s+|[()@<>;,={}/"!?]|\[\]|\.\.\.')
Adrian Roosb787c182019-01-03 18:54:33 +0100247 STRING_SPECIAL = re.compile(r'["\\]')
248
249 def __init__(self, raw):
250 self.raw = raw
Adrian Roosb787c182019-01-03 18:54:33 +0100251
Adrian Roose5eeae72019-01-04 20:10:06 +0100252 def tokenize(self):
253 tokens = []
254 current = 0
255 raw = self.raw
256 length = len(raw)
Adrian Roosb787c182019-01-03 18:54:33 +0100257
Adrian Roose5eeae72019-01-04 20:10:06 +0100258 while current < length:
259 while current < length:
260 start = current
261 match = V2Tokenizer.DELIMITER.search(raw, start)
262 if match is not None:
263 match_start = match.start()
264 if match_start == current:
265 end = match.end()
266 else:
267 end = match_start
268 else:
269 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100270
Adrian Roose5eeae72019-01-04 20:10:06 +0100271 token = raw[start:end]
272 current = end
Adrian Roosb787c182019-01-03 18:54:33 +0100273
Adrian Roose5eeae72019-01-04 20:10:06 +0100274 if token == "" or token[0] == " ":
275 continue
276 else:
277 break
Adrian Roosb787c182019-01-03 18:54:33 +0100278
Adrian Roose5eeae72019-01-04 20:10:06 +0100279 if token == "@":
280 if raw[start:start+11] == "@interface ":
281 current = start + 11
282 tokens.append("@interface")
283 continue
284 elif token == '/':
285 if raw[start:start+2] == "//":
286 current = length
287 continue
288 elif token == '"':
289 current, string_token = self.tokenize_string(raw, length, current)
290 tokens.append(token + string_token)
291 continue
Adrian Roosb787c182019-01-03 18:54:33 +0100292
Adrian Roose5eeae72019-01-04 20:10:06 +0100293 tokens.append(token)
Adrian Roosb787c182019-01-03 18:54:33 +0100294
Adrian Roose5eeae72019-01-04 20:10:06 +0100295 return tokens
Adrian Roosb787c182019-01-03 18:54:33 +0100296
Adrian Roose5eeae72019-01-04 20:10:06 +0100297 def tokenize_string(self, raw, length, current):
298 start = current
299 end = length
Adrian Roosb787c182019-01-03 18:54:33 +0100300 while start < end:
Adrian Roose5eeae72019-01-04 20:10:06 +0100301 match = V2Tokenizer.STRING_SPECIAL.search(raw, start)
Adrian Roosb787c182019-01-03 18:54:33 +0100302 if match:
303 if match.group() == '"':
304 end = match.end()
305 break
306 elif match.group() == '\\':
307 # ignore whatever is after the slash
308 start += 2
309 else:
310 raise ValueError("Unexpected match: `%s`" % (match.group()))
311 else:
Adrian Roose5eeae72019-01-04 20:10:06 +0100312 raise ValueError("Unexpected EOF tokenizing string: `%s`" % (raw[current - 1:],))
Adrian Roosb787c182019-01-03 18:54:33 +0100313
Adrian Roose5eeae72019-01-04 20:10:06 +0100314 token = raw[current:end]
315 return end, token
Adrian Roosb787c182019-01-03 18:54:33 +0100316
Adrian Roose5eeae72019-01-04 20:10:06 +0100317class V2LineParser(object):
318 __slots__ = ["tokenized", "current", "len"]
319
Adrian Roos258c5722019-01-21 15:43:15 +0100320 FIELD_KINDS = ("field", "property", "enum_constant")
Adrian Roosd1e38922019-01-14 15:44:15 +0100321 MODIFIERS = set("public protected internal private abstract default static final transient volatile synchronized native operator sealed strictfp infix inline suspend vararg".split())
Adrian Roosb787c182019-01-03 18:54:33 +0100322 JAVA_LANG_TYPES = set("AbstractMethodError AbstractStringBuilder Appendable ArithmeticException ArrayIndexOutOfBoundsException ArrayStoreException AssertionError AutoCloseable Boolean BootstrapMethodError Byte Character CharSequence Class ClassCastException ClassCircularityError ClassFormatError ClassLoader ClassNotFoundException Cloneable CloneNotSupportedException Comparable Compiler Deprecated Double Enum EnumConstantNotPresentException Error Exception ExceptionInInitializerError Float FunctionalInterface IllegalAccessError IllegalAccessException IllegalArgumentException IllegalMonitorStateException IllegalStateException IllegalThreadStateException IncompatibleClassChangeError IndexOutOfBoundsException InheritableThreadLocal InstantiationError InstantiationException Integer InternalError InterruptedException Iterable LinkageError Long Math NegativeArraySizeException NoClassDefFoundError NoSuchFieldError NoSuchFieldException NoSuchMethodError NoSuchMethodException NullPointerException Number NumberFormatException Object OutOfMemoryError Override Package package-info.java Process ProcessBuilder ProcessEnvironment ProcessImpl Readable ReflectiveOperationException Runnable Runtime RuntimeException RuntimePermission SafeVarargs SecurityException SecurityManager Short StackOverflowError StackTraceElement StrictMath String StringBuffer StringBuilder StringIndexOutOfBoundsException SuppressWarnings System Thread ThreadDeath ThreadGroup ThreadLocal Throwable TypeNotPresentException UNIXProcess UnknownError UnsatisfiedLinkError UnsupportedClassVersionError UnsupportedOperationException VerifyError VirtualMachineError Void".split())
323
324 def __init__(self, raw):
Adrian Roose5eeae72019-01-04 20:10:06 +0100325 self.tokenized = V2Tokenizer(raw).tokenize()
Adrian Roosb787c182019-01-03 18:54:33 +0100326 self.current = 0
Adrian Roose5eeae72019-01-04 20:10:06 +0100327 self.len = len(self.tokenized)
Adrian Roosb787c182019-01-03 18:54:33 +0100328
329 def parse_into_method(self, method):
330 method.split = []
331 kind = self.parse_one_of("ctor", "method")
332 method.split.append(kind)
Adrian Roos80545ef2019-02-27 16:45:00 +0100333 method.annotations = self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100334 method.split.extend(self.parse_modifiers())
335 self.parse_matching_paren("<", ">")
Adrian Roos80545ef2019-02-27 16:45:00 +0100336 if "@Deprecated" in method.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100337 method.split.append("deprecated")
338 if kind == "ctor":
339 method.typ = "ctor"
340 else:
341 method.typ = self.parse_type()
342 method.split.append(method.typ)
343 method.name = self.parse_name()
344 method.split.append(method.name)
345 self.parse_token("(")
Adrian Roos80545ef2019-02-27 16:45:00 +0100346 method.detailed_args = self.parse_args()
Adrian Roosb787c182019-01-03 18:54:33 +0100347 self.parse_token(")")
348 method.throws = self.parse_throws()
349 if "@interface" in method.clazz.split:
350 self.parse_annotation_default()
351 self.parse_token(";")
352 self.parse_eof()
353
354 def parse_into_class(self, clazz):
355 clazz.split = []
356 annotations = self.parse_annotations()
357 if "@Deprecated" in annotations:
358 clazz.split.append("deprecated")
359 clazz.split.extend(self.parse_modifiers())
360 kind = self.parse_one_of("class", "interface", "@interface", "enum")
361 if kind == "enum":
362 # enums are implicitly final
363 clazz.split.append("final")
364 clazz.split.append(kind)
365 clazz.fullname = self.parse_name()
366 self.parse_matching_paren("<", ">")
367 extends = self.parse_extends()
368 clazz.extends = extends[0] if extends else None
Adrian Roos02e18dd2019-02-28 12:41:48 +0100369 clazz.implements_all = self.parse_implements()
Adrian Roosb787c182019-01-03 18:54:33 +0100370 # The checks assume that interfaces are always found in implements, which isn't true for
371 # subinterfaces.
Adrian Roos02e18dd2019-02-28 12:41:48 +0100372 if not clazz.implements_all and "interface" in clazz.split:
373 clazz.implements_all = [clazz.extends]
374 clazz.implements = clazz.implements_all[0] if clazz.implements_all else None
Adrian Roosb787c182019-01-03 18:54:33 +0100375 self.parse_token("{")
376 self.parse_eof()
377
378 def parse_into_field(self, field):
Adrian Roos258c5722019-01-21 15:43:15 +0100379 kind = self.parse_one_of(*V2LineParser.FIELD_KINDS)
Adrian Roosb787c182019-01-03 18:54:33 +0100380 field.split = [kind]
Adrian Roos80545ef2019-02-27 16:45:00 +0100381 field.annotations = self.parse_annotations()
382 if "@Deprecated" in field.annotations:
Adrian Roosb787c182019-01-03 18:54:33 +0100383 field.split.append("deprecated")
384 field.split.extend(self.parse_modifiers())
385 field.typ = self.parse_type()
386 field.split.append(field.typ)
387 field.name = self.parse_name()
388 field.split.append(field.name)
389 if self.parse_if("="):
390 field.value = self.parse_value_stripped()
391 else:
392 field.value = None
393
394 self.parse_token(";")
395 self.parse_eof()
396
397 def lookahead(self):
398 return self.tokenized[self.current]
399
400 def parse_one_of(self, *options):
401 found = self.lookahead()
402 if found not in options:
403 raise ValueError("Parsing failed, expected one of `%s` but found `%s` in %s" % (options, found, repr(self.tokenized)))
404 return self.parse_token()
405
406 def parse_token(self, tok = None):
407 found = self.lookahead()
408 if tok is not None and found != tok:
409 raise ValueError("Parsing failed, expected `%s` but found `%s` in %s" % (tok, found, repr(self.tokenized)))
410 self.current += 1
411 return found
412
413 def eof(self):
Adrian Roose5eeae72019-01-04 20:10:06 +0100414 return self.current == self.len
Adrian Roosb787c182019-01-03 18:54:33 +0100415
416 def parse_eof(self):
417 if not self.eof():
418 raise ValueError("Parsing failed, expected EOF, but %s has not been parsed in %s" % (self.tokenized[self.current:], self.tokenized))
419
420 def parse_if(self, tok):
421 if not self.eof() and self.lookahead() == tok:
422 self.parse_token()
423 return True
424 return False
425
426 def parse_annotations(self):
427 ret = []
428 while self.lookahead() == "@":
429 ret.append(self.parse_annotation())
430 return ret
431
432 def parse_annotation(self):
433 ret = self.parse_token("@") + self.parse_token()
434 self.parse_matching_paren("(", ")")
435 return ret
436
437 def parse_matching_paren(self, open, close):
438 start = self.current
439 if not self.parse_if(open):
440 return
441 length = len(self.tokenized)
442 count = 1
443 while count > 0:
444 if self.current == length:
445 raise ValueError("Unexpected EOF looking for closing paren: `%s`" % (self.tokenized[start:],))
446 t = self.parse_token()
447 if t == open:
448 count += 1
449 elif t == close:
450 count -= 1
451 return self.tokenized[start:self.current]
452
453 def parse_modifiers(self):
454 ret = []
455 while self.lookahead() in V2LineParser.MODIFIERS:
456 ret.append(self.parse_token())
457 return ret
458
Adrian Roos5cdfb692019-01-05 22:04:55 +0100459 def parse_kotlin_nullability(self):
460 t = self.lookahead()
461 if t == "?" or t == "!":
462 return self.parse_token()
463 return None
464
Adrian Roosb787c182019-01-03 18:54:33 +0100465 def parse_type(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100466 self.parse_annotations()
Adrian Roosb787c182019-01-03 18:54:33 +0100467 type = self.parse_token()
Adrian Roosd1e38922019-01-14 15:44:15 +0100468 if type[-1] == '.':
469 self.parse_annotations()
470 type += self.parse_token()
Adrian Roosb787c182019-01-03 18:54:33 +0100471 if type in V2LineParser.JAVA_LANG_TYPES:
472 type = "java.lang." + type
473 self.parse_matching_paren("<", ">")
Adrian Roos5cdfb692019-01-05 22:04:55 +0100474 while True:
475 t = self.lookahead()
Adrian Roosd1e38922019-01-14 15:44:15 +0100476 if t == "@":
477 self.parse_annotation()
478 elif t == "[]":
Adrian Roos5cdfb692019-01-05 22:04:55 +0100479 type += self.parse_token()
480 elif self.parse_kotlin_nullability() is not None:
481 pass # discard nullability for now
482 else:
483 break
Adrian Roosb787c182019-01-03 18:54:33 +0100484 return type
485
486 def parse_arg_type(self):
487 type = self.parse_type()
488 if self.parse_if("..."):
489 type += "..."
Adrian Roos5cdfb692019-01-05 22:04:55 +0100490 self.parse_kotlin_nullability() # discard nullability for now
Adrian Roosb787c182019-01-03 18:54:33 +0100491 return type
492
493 def parse_name(self):
494 return self.parse_token()
495
496 def parse_args(self):
497 args = []
498 if self.lookahead() == ")":
499 return args
500
501 while True:
502 args.append(self.parse_arg())
503 if self.lookahead() == ")":
504 return args
505 self.parse_token(",")
506
507 def parse_arg(self):
Adrian Roosd1e38922019-01-14 15:44:15 +0100508 self.parse_if("vararg") # kotlin vararg
Adrian Roos80545ef2019-02-27 16:45:00 +0100509 annotations = self.parse_annotations()
510 arg = Argument(self.parse_arg_type())
511 arg.annotations = annotations
Adrian Roos5cdfb692019-01-05 22:04:55 +0100512 l = self.lookahead()
513 if l != "," and l != ")":
Adrian Roosd1e38922019-01-14 15:44:15 +0100514 if self.lookahead() != '=':
Adrian Roos80545ef2019-02-27 16:45:00 +0100515 arg.name = self.parse_token() # kotlin argument name
Adrian Roos5cdfb692019-01-05 22:04:55 +0100516 if self.parse_if('='): # kotlin default value
Adrian Roos80545ef2019-02-27 16:45:00 +0100517 arg.default = self.parse_expression()
518 return arg
Adrian Roosb787c182019-01-03 18:54:33 +0100519
Adrian Roosd1e38922019-01-14 15:44:15 +0100520 def parse_expression(self):
521 while not self.lookahead() in [')', ',', ';']:
522 (self.parse_matching_paren('(', ')') or
523 self.parse_matching_paren('{', '}') or
524 self.parse_token())
525
Adrian Roosb787c182019-01-03 18:54:33 +0100526 def parse_throws(self):
527 ret = []
528 if self.parse_if("throws"):
529 ret.append(self.parse_type())
530 while self.parse_if(","):
531 ret.append(self.parse_type())
532 return ret
533
534 def parse_extends(self):
535 if self.parse_if("extends"):
536 return self.parse_space_delimited_type_list()
537 return []
538
539 def parse_implements(self):
540 if self.parse_if("implements"):
541 return self.parse_space_delimited_type_list()
542 return []
543
544 def parse_space_delimited_type_list(self, terminals = ["implements", "{"]):
545 types = []
546 while True:
547 types.append(self.parse_type())
548 if self.lookahead() in terminals:
549 return types
550
551 def parse_annotation_default(self):
552 if self.parse_if("default"):
Adrian Roosd1e38922019-01-14 15:44:15 +0100553 self.parse_expression()
Adrian Roosb787c182019-01-03 18:54:33 +0100554
555 def parse_value(self):
556 if self.lookahead() == "{":
557 return " ".join(self.parse_matching_paren("{", "}"))
558 elif self.lookahead() == "(":
559 return " ".join(self.parse_matching_paren("(", ")"))
560 else:
561 return self.parse_token()
562
563 def parse_value_stripped(self):
564 value = self.parse_value()
565 if value[0] == '"':
566 return value[1:-1]
567 return value
568
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700569
Adrian Roos038a0292018-12-19 17:11:21 +0100570def _parse_stream(f, clazz_cb=None, base_f=None, out_classes_with_base=None,
571 in_classes_with_base=[]):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700572 api = {}
Adrian Roos038a0292018-12-19 17:11:21 +0100573 in_classes_with_base = _retry_iterator(in_classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +0100574
575 if base_f:
Adrian Roos038a0292018-12-19 17:11:21 +0100576 base_classes = _retry_iterator(_parse_stream_to_generator(base_f))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100577 else:
578 base_classes = []
579
Adrian Roos038a0292018-12-19 17:11:21 +0100580 def handle_class(clazz):
Adrian Roos6eb57b02018-12-13 22:08:29 +0100581 if clazz_cb:
582 clazz_cb(clazz)
583 else: # In callback mode, don't keep track of the full API
584 api[clazz.fullname] = clazz
585
Adrian Roos038a0292018-12-19 17:11:21 +0100586 def handle_missed_classes_with_base(clazz):
587 for c in _yield_until_matching_class(in_classes_with_base, clazz):
588 base_class = _skip_to_matching_class(base_classes, c)
589 if base_class:
590 handle_class(base_class)
591
592 for clazz in _parse_stream_to_generator(f):
593 # Before looking at clazz, let's see if there's some classes that were not present, but
594 # may have an entry in the base stream.
595 handle_missed_classes_with_base(clazz)
596
597 base_class = _skip_to_matching_class(base_classes, clazz)
598 if base_class:
599 clazz.merge_from(base_class)
600 if out_classes_with_base is not None:
601 out_classes_with_base.append(clazz)
602 handle_class(clazz)
603
604 handle_missed_classes_with_base(None)
605
Adrian Roos6eb57b02018-12-13 22:08:29 +0100606 return api
607
608def _parse_stream_to_generator(f):
609 line = 0
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700610 pkg = None
611 clazz = None
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700612 blame = None
Adrian Roosb787c182019-01-03 18:54:33 +0100613 sig_format = 1
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700614
Adrian Roos80545ef2019-02-27 16:45:00 +0100615 re_blame = re.compile(r"^(\^?[a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$")
Adrian Roos258c5722019-01-21 15:43:15 +0100616
617 field_prefixes = map(lambda kind: " %s" % (kind,), V2LineParser.FIELD_KINDS)
618 def startsWithFieldPrefix(raw):
619 for prefix in field_prefixes:
620 if raw.startswith(prefix):
621 return True
622 return False
623
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800624 for raw in f:
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700625 line += 1
626 raw = raw.rstrip()
627 match = re_blame.match(raw)
628 if match is not None:
629 blame = match.groups()[0:2]
Adrian Roos80545ef2019-02-27 16:45:00 +0100630 if blame[0].startswith("^"): # Outside of blame range
631 blame = None
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700632 raw = match.groups()[2]
633 else:
634 blame = None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700635
Adrian Roos80545ef2019-02-27 16:45:00 +0100636 if line == 1 and V2Tokenizer.SIGNATURE_PREFIX in raw:
Adrian Rooscf82e042019-01-29 15:01:28 +0100637 sig_format_string = raw[len(V2Tokenizer.SIGNATURE_PREFIX):]
638 if sig_format_string in ["2.0", "3.0"]:
639 sig_format = 2
640 else:
641 raise ValueError("Unknown format: %s" % (sig_format_string,))
Adrian Roosb787c182019-01-03 18:54:33 +0100642 elif raw.startswith("package"):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700643 pkg = Package(line, raw, blame)
644 elif raw.startswith(" ") and raw.endswith("{"):
Adrian Roosb787c182019-01-03 18:54:33 +0100645 clazz = Class(pkg, line, raw, blame, sig_format=sig_format)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700646 elif raw.startswith(" ctor"):
Adrian Roosb787c182019-01-03 18:54:33 +0100647 clazz.ctors.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700648 elif raw.startswith(" method"):
Adrian Roosb787c182019-01-03 18:54:33 +0100649 clazz.methods.append(Method(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos258c5722019-01-21 15:43:15 +0100650 elif startsWithFieldPrefix(raw):
Adrian Roosb787c182019-01-03 18:54:33 +0100651 clazz.fields.append(Field(clazz, line, raw, blame, sig_format=sig_format))
Adrian Roos6eb57b02018-12-13 22:08:29 +0100652 elif raw.startswith(" }") and clazz:
Adrian Roos038a0292018-12-19 17:11:21 +0100653 yield clazz
Jeff Sharkeya18a2e32015-02-22 15:54:32 -0800654
Adrian Roos5ed42b62018-12-19 17:10:22 +0100655def _retry_iterator(it):
656 """Wraps an iterator, such that calling send(True) on it will redeliver the same element"""
657 for e in it:
658 while True:
659 retry = yield e
660 if not retry:
661 break
662 # send() was called, asking us to redeliver clazz on next(). Still need to yield
663 # a dummy value to the send() first though.
664 if (yield "Returning clazz on next()"):
665 raise TypeError("send() must be followed by next(), not send()")
666
Adrian Roos038a0292018-12-19 17:11:21 +0100667def _skip_to_matching_class(classes, needle):
668 """Takes a classes iterator and consumes entries until it returns the class we're looking for
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700669
Adrian Roos6eb57b02018-12-13 22:08:29 +0100670 This relies on classes being sorted by package and class name."""
671
672 for clazz in classes:
673 if clazz.pkg.name < needle.pkg.name:
674 # We haven't reached the right package yet
675 continue
Adrian Roos5ed42b62018-12-19 17:10:22 +0100676 if clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
677 # We're in the right package, but not the right class yet
Adrian Roos6eb57b02018-12-13 22:08:29 +0100678 continue
679 if clazz.fullname == needle.fullname:
680 return clazz
681 # We ran past the right class. Send it back into the generator, then report failure.
682 classes.send(clazz)
683 return None
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700684
Adrian Roos038a0292018-12-19 17:11:21 +0100685def _yield_until_matching_class(classes, needle):
686 """Takes a class iterator and yields entries it until it reaches the class we're looking for.
687
688 This relies on classes being sorted by package and class name."""
689
690 for clazz in classes:
691 if needle is None:
692 yield clazz
693 elif clazz.pkg.name < needle.pkg.name:
694 # We haven't reached the right package yet
695 yield clazz
696 elif clazz.pkg.name == needle.pkg.name and clazz.fullname < needle.fullname:
697 # We're in the right package, but not the right class yet
698 yield clazz
699 elif clazz.fullname == needle.fullname:
700 # Class found, abort.
701 return
702 else:
703 # We ran past the right class. Send it back into the iterator, then abort.
704 classes.send(clazz)
705 return
706
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700707class Failure():
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800708 def __init__(self, sig, clazz, detail, error, rule, msg):
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700709 self.sig = sig
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700710 self.error = error
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800711 self.rule = rule
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700712 self.msg = msg
713
714 if error:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800715 self.head = "Error %s" % (rule) if rule else "Error"
716 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 -0700717 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800718 self.head = "Warning %s" % (rule) if rule else "Warning"
719 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 -0700720
721 self.line = clazz.line
722 blame = clazz.blame
723 if detail is not None:
724 dump += "\n in " + repr(detail)
725 self.line = detail.line
726 blame = detail.blame
727 dump += "\n in " + repr(clazz)
728 dump += "\n in " + repr(clazz.pkg)
729 dump += "\n at line " + repr(self.line)
730 if blame is not None:
731 dump += "\n last modified by %s in %s" % (blame[1], blame[0])
732
733 self.dump = dump
734
735 def __repr__(self):
736 return self.dump
737
738
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700739failures = {}
Jeff Sharkey294f0de2014-08-29 17:41:43 -0700740
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800741def _fail(clazz, detail, error, rule, msg):
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700742 """Records an API failure to be processed later."""
743 global failures
744
Adrian Roosb787c182019-01-03 18:54:33 +0100745 sig = "%s-%s-%s" % (clazz.fullname, detail.ident if detail else None, msg)
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700746 sig = sig.replace(" deprecated ", " ")
747
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800748 failures[sig] = Failure(sig, clazz, detail, error, rule, msg)
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -0700749
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700750
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800751def warn(clazz, detail, rule, msg):
752 _fail(clazz, detail, False, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700753
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800754def error(clazz, detail, rule, msg):
755 _fail(clazz, detail, True, rule, msg)
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700756
757
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -0700758noticed = {}
759
760def notice(clazz):
761 global noticed
762
763 noticed[clazz.fullname] = hash(clazz)
764
765
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700766def verify_constants(clazz):
767 """All static final constants must be FOO_NAME style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700768 if re.match("android\.R\.[a-z]+", clazz.fullname): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600769 if clazz.fullname.startswith("android.os.Build"): return
770 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700771
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600772 req = ["java.lang.String","byte","short","int","long","float","double","boolean","char"]
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700773 for f in clazz.fields:
774 if "static" in f.split and "final" in f.split:
775 if re.match("[A-Z0-9_]+", f.name) is None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800776 error(clazz, f, "C2", "Constant field names must be FOO_NAME")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600777 if f.typ != "java.lang.String":
Jeff Sharkey331279b2016-02-29 16:02:02 -0700778 if f.name.startswith("MIN_") or f.name.startswith("MAX_"):
779 warn(clazz, f, "C8", "If min/max could change in future, make them dynamic methods")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600780 if f.typ in req and f.value is None:
781 error(clazz, f, None, "All constants must be defined at compile time")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700782
783
784def verify_enums(clazz):
785 """Enums are bad, mmkay?"""
Adrian Roosb787c182019-01-03 18:54:33 +0100786 if clazz.extends == "java.lang.Enum" or "enum" in clazz.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800787 error(clazz, None, "F5", "Enums are not allowed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700788
789
790def verify_class_names(clazz):
791 """Try catching malformed class names like myMtp or MTPUser."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700792 if clazz.fullname.startswith("android.opengl"): return
793 if clazz.fullname.startswith("android.renderscript"): return
794 if re.match("android\.R\.[a-z]+", clazz.fullname): return
795
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700796 if re.search("[A-Z]{2,}", clazz.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800797 warn(clazz, None, "S1", "Class names with acronyms should be Mtp not MTP")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700798 if re.match("[^A-Z]", clazz.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800799 error(clazz, None, "S1", "Class must start with uppercase char")
Jeff Sharkeyee21f692018-02-16 13:29:29 -0700800 if clazz.name.endswith("Impl"):
801 error(clazz, None, None, "Don't expose your implementation details")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700802
803
804def verify_method_names(clazz):
805 """Try catching malformed method names, like Foo() or getMTU()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700806 if clazz.fullname.startswith("android.opengl"): return
807 if clazz.fullname.startswith("android.renderscript"): return
808 if clazz.fullname == "android.system.OsConstants": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700809
810 for m in clazz.methods:
811 if re.search("[A-Z]{2,}", m.name) is not None:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800812 warn(clazz, m, "S1", "Method names with acronyms should be getMtu() instead of getMTU()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700813 if re.match("[^a-z]", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800814 error(clazz, m, "S1", "Method name must start with lowercase char")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700815
816
817def verify_callbacks(clazz):
818 """Verify Callback classes.
819 All callback classes must be abstract.
820 All methods must follow onFoo() naming style."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700821 if clazz.fullname == "android.speech.tts.SynthesisCallback": return
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700822
823 if clazz.name.endswith("Callbacks"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800824 error(clazz, None, "L1", "Callback class names should be singular")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700825 if clazz.name.endswith("Observer"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800826 warn(clazz, None, "L1", "Class should be named FooCallback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700827
828 if clazz.name.endswith("Callback"):
829 if "interface" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800830 error(clazz, None, "CL3", "Callbacks must be abstract class to enable extension in future API levels")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700831
832 for m in clazz.methods:
833 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800834 error(clazz, m, "L1", "Callback method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700835
836
837def verify_listeners(clazz):
838 """Verify Listener classes.
839 All Listener classes must be interface.
840 All methods must follow onFoo() naming style.
841 If only a single method, it must match class name:
842 interface OnFooListener { void onFoo() }"""
843
844 if clazz.name.endswith("Listener"):
Adrian Roosb787c182019-01-03 18:54:33 +0100845 if "abstract" in clazz.split and "class" in clazz.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800846 error(clazz, None, "L1", "Listeners should be an interface, or otherwise renamed Callback")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700847
848 for m in clazz.methods:
849 if not re.match("on[A-Z][a-z]*", m.name):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800850 error(clazz, m, "L1", "Listener method names must be onFoo() style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700851
852 if len(clazz.methods) == 1 and clazz.name.startswith("On"):
853 m = clazz.methods[0]
854 if (m.name + "Listener").lower() != clazz.name.lower():
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800855 error(clazz, m, "L1", "Single listener method name must match class name")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700856
857
858def verify_actions(clazz):
859 """Verify intent actions.
860 All action names must be named ACTION_FOO.
861 All action values must be scoped by package and match name:
862 package android.foo {
863 String ACTION_BAR = "android.foo.action.BAR";
864 }"""
865 for f in clazz.fields:
866 if f.value is None: continue
867 if f.name.startswith("EXTRA_"): continue
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700868 if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600869 if "INTERACTION" in f.name: continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700870
871 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
872 if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower():
873 if not f.name.startswith("ACTION_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800874 error(clazz, f, "C3", "Intent action constant name must be ACTION_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700875 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700876 if clazz.fullname == "android.content.Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700877 prefix = "android.intent.action"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700878 elif clazz.fullname == "android.provider.Settings":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700879 prefix = "android.settings"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700880 elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver":
881 prefix = "android.app.action"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700882 else:
883 prefix = clazz.pkg.name + ".action"
884 expected = prefix + "." + f.name[7:]
885 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700886 error(clazz, f, "C4", "Inconsistent action value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700887
888
889def verify_extras(clazz):
890 """Verify intent extras.
891 All extra names must be named EXTRA_FOO.
892 All extra values must be scoped by package and match name:
893 package android.foo {
894 String EXTRA_BAR = "android.foo.extra.BAR";
895 }"""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700896 if clazz.fullname == "android.app.Notification": return
897 if clazz.fullname == "android.appwidget.AppWidgetManager": return
898
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700899 for f in clazz.fields:
900 if f.value is None: continue
901 if f.name.startswith("ACTION_"): continue
902
903 if "static" in f.split and "final" in f.split and f.typ == "java.lang.String":
904 if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower():
905 if not f.name.startswith("EXTRA_"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800906 error(clazz, f, "C3", "Intent extra must be EXTRA_FOO")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700907 else:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700908 if clazz.pkg.name == "android.content" and clazz.name == "Intent":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700909 prefix = "android.intent.extra"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700910 elif clazz.pkg.name == "android.app.admin":
911 prefix = "android.app.extra"
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700912 else:
913 prefix = clazz.pkg.name + ".extra"
914 expected = prefix + "." + f.name[6:]
915 if f.value != expected:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700916 error(clazz, f, "C4", "Inconsistent extra value; expected '%s'" % (expected))
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700917
918
919def verify_equals(clazz):
920 """Verify that equals() and hashCode() must be overridden together."""
Jeff Sharkey40d623e62016-12-21 13:46:33 -0700921 eq = False
922 hc = False
923 for m in clazz.methods:
Adrian Roosb787c182019-01-03 18:54:33 +0100924 if "static" in m.split: continue
925 if m.sig_matches("boolean", "equals", ["java.lang.Object"]): eq = True
926 if m.sig_matches("int", "hashCode", []): hc = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700927 if eq != hc:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800928 error(clazz, None, "M8", "Must override both equals and hashCode; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700929
930
931def verify_parcelable(clazz):
932 """Verify that Parcelable objects aren't hiding required bits."""
Adrian Roosb787c182019-01-03 18:54:33 +0100933 if clazz.implements == "android.os.Parcelable":
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700934 creator = [ i for i in clazz.fields if i.name == "CREATOR" ]
935 write = [ i for i in clazz.methods if i.name == "writeToParcel" ]
936 describe = [ i for i in clazz.methods if i.name == "describeContents" ]
937
938 if len(creator) == 0 or len(write) == 0 or len(describe) == 0:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800939 error(clazz, None, "FW3", "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700940
Adrian Roosb787c182019-01-03 18:54:33 +0100941 if "final" not in clazz.split:
Jeff Sharkey331279b2016-02-29 16:02:02 -0700942 error(clazz, None, "FW8", "Parcelable classes must be final")
943
Jeff Sharkey336dd0b2018-01-12 12:12:27 -0700944 for c in clazz.ctors:
945 if c.args == ["android.os.Parcel"]:
946 error(clazz, c, "FW3", "Parcelable inflation is exposed through CREATOR, not raw constructors")
947
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700948
949def verify_protected(clazz):
Jeff Sharkeyb46a9692015-02-17 17:19:41 -0800950 """Verify that no protected methods or fields are allowed."""
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700951 for m in clazz.methods:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -0600952 if m.name == "finalize": continue
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700953 if "protected" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800954 error(clazz, m, "M7", "Protected methods not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700955 for f in clazz.fields:
956 if "protected" in f.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800957 error(clazz, f, "M7", "Protected fields not allowed; must be public")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700958
959
960def verify_fields(clazz):
961 """Verify that all exposed fields are final.
962 Exposed fields must follow myName style.
963 Catch internal mFoo objects being exposed."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700964
965 IGNORE_BARE_FIELDS = [
966 "android.app.ActivityManager.RecentTaskInfo",
967 "android.app.Notification",
968 "android.content.pm.ActivityInfo",
969 "android.content.pm.ApplicationInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600970 "android.content.pm.ComponentInfo",
971 "android.content.pm.ResolveInfo",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700972 "android.content.pm.FeatureGroupInfo",
973 "android.content.pm.InstrumentationInfo",
974 "android.content.pm.PackageInfo",
975 "android.content.pm.PackageItemInfo",
Jeff Sharkey24bda1d2017-04-19 16:04:44 -0600976 "android.content.res.Configuration",
977 "android.graphics.BitmapFactory.Options",
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700978 "android.os.Message",
979 "android.system.StructPollfd",
980 ]
981
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700982 for f in clazz.fields:
983 if not "final" in f.split:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700984 if clazz.fullname in IGNORE_BARE_FIELDS:
985 pass
986 elif clazz.fullname.endswith("LayoutParams"):
987 pass
988 elif clazz.fullname.startswith("android.util.Mutable"):
989 pass
990 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -0800991 error(clazz, f, "F2", "Bare fields must be marked final, or add accessors if mutable")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700992
Adrian Roosd1e38922019-01-14 15:44:15 +0100993 if "static" not in f.split and "property" not in f.split:
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700994 if not re.match("[a-z]([a-zA-Z]+)?", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800995 error(clazz, f, "S1", "Non-static fields must be named using myField style")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700996
Jeff Sharkey1498f9c2014-09-04 12:45:33 -0700997 if re.match("[ms][A-Z]", f.name):
Jeff Sharkey90b547b2015-02-18 16:45:54 -0800998 error(clazz, f, "F1", "Internal objects must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -0700999
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001000 if re.match("[A-Z_]+", f.name):
1001 if "static" not in f.split or "final" not in f.split:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001002 error(clazz, f, "C2", "Constants must be marked static final")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001003
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001004
1005def verify_register(clazz):
1006 """Verify parity of registration methods.
1007 Callback objects use register/unregister methods.
1008 Listener objects use add/remove methods."""
1009 methods = [ m.name for m in clazz.methods ]
1010 for m in clazz.methods:
1011 if "Callback" in m.raw:
1012 if m.name.startswith("register"):
1013 other = "unregister" + m.name[8:]
1014 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001015 error(clazz, m, "L2", "Missing unregister method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001016 if m.name.startswith("unregister"):
1017 other = "register" + m.name[10:]
1018 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001019 error(clazz, m, "L2", "Missing register method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001020
1021 if m.name.startswith("add") or m.name.startswith("remove"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001022 error(clazz, m, "L3", "Callback methods should be named register/unregister")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001023
1024 if "Listener" in m.raw:
1025 if m.name.startswith("add"):
1026 other = "remove" + m.name[3:]
1027 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001028 error(clazz, m, "L2", "Missing remove method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001029 if m.name.startswith("remove") and not m.name.startswith("removeAll"):
1030 other = "add" + m.name[6:]
1031 if other not in methods:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001032 error(clazz, m, "L2", "Missing add method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001033
1034 if m.name.startswith("register") or m.name.startswith("unregister"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001035 error(clazz, m, "L3", "Listener methods should be named add/remove")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001036
1037
1038def verify_sync(clazz):
1039 """Verify synchronized methods aren't exposed."""
1040 for m in clazz.methods:
1041 if "synchronized" in m.split:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001042 error(clazz, m, "M5", "Internal locks must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001043
1044
1045def verify_intent_builder(clazz):
1046 """Verify that Intent builders are createFooIntent() style."""
1047 if clazz.name == "Intent": return
1048
1049 for m in clazz.methods:
1050 if m.typ == "android.content.Intent":
1051 if m.name.startswith("create") and m.name.endswith("Intent"):
1052 pass
1053 else:
Adam Powell539ea122015-04-10 13:01:37 -07001054 warn(clazz, m, "FW1", "Methods creating an Intent should be named createFooIntent()")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001055
1056
1057def verify_helper_classes(clazz):
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001058 """Verify that helper classes are named consistently with what they extend.
1059 All developer extendable methods should be named onFoo()."""
1060 test_methods = False
Adrian Roosb787c182019-01-03 18:54:33 +01001061 if clazz.extends == "android.app.Service":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001062 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001063 if not clazz.name.endswith("Service"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001064 error(clazz, None, "CL4", "Inconsistent class name; should be FooService")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001065
1066 found = False
1067 for f in clazz.fields:
1068 if f.name == "SERVICE_INTERFACE":
1069 found = True
1070 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001071 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001072
Adrian Roosb787c182019-01-03 18:54:33 +01001073 if clazz.extends == "android.content.ContentProvider":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001074 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001075 if not clazz.name.endswith("Provider"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001076 error(clazz, None, "CL4", "Inconsistent class name; should be FooProvider")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001077
1078 found = False
1079 for f in clazz.fields:
1080 if f.name == "PROVIDER_INTERFACE":
1081 found = True
1082 if f.value != clazz.fullname:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001083 error(clazz, f, "C4", "Inconsistent interface constant; expected '%s'" % (clazz.fullname))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001084
Adrian Roosb787c182019-01-03 18:54:33 +01001085 if clazz.extends == "android.content.BroadcastReceiver":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001086 test_methods = True
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001087 if not clazz.name.endswith("Receiver"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001088 error(clazz, None, "CL4", "Inconsistent class name; should be FooReceiver")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001089
Adrian Roosb787c182019-01-03 18:54:33 +01001090 if clazz.extends == "android.app.Activity":
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001091 test_methods = True
1092 if not clazz.name.endswith("Activity"):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001093 error(clazz, None, "CL4", "Inconsistent class name; should be FooActivity")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001094
1095 if test_methods:
1096 for m in clazz.methods:
1097 if "final" in m.split: continue
1098 if not re.match("on[A-Z]", m.name):
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001099 if "abstract" in m.split:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001100 warn(clazz, m, None, "Methods implemented by developers should be named onFoo()")
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001101 else:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001102 warn(clazz, m, None, "If implemented by developer, should be named onFoo(); otherwise consider marking final")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001103
1104
1105def verify_builder(clazz):
1106 """Verify builder classes.
1107 Methods should return the builder to enable chaining."""
Adrian Roosb787c182019-01-03 18:54:33 +01001108 if clazz.extends: return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001109 if not clazz.name.endswith("Builder"): return
1110
1111 if clazz.name != "Builder":
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001112 warn(clazz, None, None, "Builder should be defined as inner class")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001113
1114 has_build = False
1115 for m in clazz.methods:
1116 if m.name == "build":
1117 has_build = True
1118 continue
1119
1120 if m.name.startswith("get"): continue
1121 if m.name.startswith("clear"): continue
1122
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001123 if m.name.startswith("with"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001124 warn(clazz, m, None, "Builder methods names should use setFoo() style")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001125
1126 if m.name.startswith("set"):
1127 if not m.typ.endswith(clazz.fullname):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001128 warn(clazz, m, "M4", "Methods must return the builder object")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001129
1130 if not has_build:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001131 warn(clazz, None, None, "Missing build() method")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001132
Adrian Roosdeb0ff22019-02-27 23:58:13 +01001133 if "final" not in clazz.split:
1134 error(clazz, None, None, "Builder should be final")
1135
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001136
1137def verify_aidl(clazz):
1138 """Catch people exposing raw AIDL."""
Adrian Roosb787c182019-01-03 18:54:33 +01001139 if clazz.extends == "android.os.Binder" or clazz.implements == "android.os.IInterface":
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001140 error(clazz, None, None, "Raw AIDL interfaces must not be exposed")
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001141
1142
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001143def verify_internal(clazz):
1144 """Catch people exposing internal classes."""
1145 if clazz.pkg.name.startswith("com.android"):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001146 error(clazz, None, None, "Internal classes must not be exposed")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001147
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001148def layering_build_ranking(ranking_list):
1149 r = {}
1150 for rank, ps in enumerate(ranking_list):
1151 if not isinstance(ps, list):
1152 ps = [ps]
1153 for p in ps:
1154 rs = r
1155 for n in p.split('.'):
1156 if n not in rs:
1157 rs[n] = {}
1158 rs = rs[n]
1159 rs['-rank'] = rank
1160 return r
1161
1162LAYERING_PACKAGE_RANKING = layering_build_ranking([
1163 ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"],
1164 "android.app",
1165 "android.widget",
1166 "android.view",
1167 "android.animation",
1168 "android.provider",
1169 ["android.content","android.graphics.drawable"],
1170 "android.database",
1171 "android.text",
1172 "android.graphics",
1173 "android.os",
1174 "android.util"
1175])
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001176
1177def verify_layering(clazz):
1178 """Catch package layering violations.
1179 For example, something in android.os depending on android.app."""
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001180
1181 def rank(p):
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001182 r = None
1183 l = LAYERING_PACKAGE_RANKING
1184 for n in p.split('.'):
1185 if n in l:
1186 l = l[n]
1187 if '-rank' in l:
1188 r = l['-rank']
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001189 else:
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001190 break
1191 return r
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001192
1193 cr = rank(clazz.pkg.name)
1194 if cr is None: return
1195
1196 for f in clazz.fields:
1197 ir = rank(f.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001198 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001199 warn(clazz, f, "FW6", "Field type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001200
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001201 for m in itertools.chain(clazz.methods, clazz.ctors):
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001202 ir = rank(m.typ)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001203 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001204 warn(clazz, m, "FW6", "Method return type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001205 for arg in m.args:
1206 ir = rank(arg)
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001207 if ir is not None and ir < cr:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001208 warn(clazz, m, "FW6", "Method argument type violates package layering")
Jeff Sharkey932a07c2014-08-28 16:16:02 -07001209
1210
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001211def verify_boolean(clazz):
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001212 """Verifies that boolean accessors are named correctly.
1213 For example, hasFoo() and setHasFoo()."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001214
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001215 def is_get(m): return len(m.args) == 0 and m.typ == "boolean"
1216 def is_set(m): return len(m.args) == 1 and m.args[0] == "boolean"
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001217
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001218 gets = [ m for m in clazz.methods if is_get(m) ]
1219 sets = [ m for m in clazz.methods if is_set(m) ]
1220
1221 def error_if_exists(methods, trigger, expected, actual):
1222 for m in methods:
1223 if m.name == actual:
1224 error(clazz, m, "M6", "Symmetric method for %s must be named %s" % (trigger, expected))
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001225
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001226 for m in clazz.methods:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001227 if is_get(m):
1228 if re.match("is[A-Z]", m.name):
1229 target = m.name[2:]
1230 expected = "setIs" + target
1231 error_if_exists(sets, m.name, expected, "setHas" + target)
1232 elif re.match("has[A-Z]", m.name):
1233 target = m.name[3:]
1234 expected = "setHas" + target
1235 error_if_exists(sets, m.name, expected, "setIs" + target)
1236 error_if_exists(sets, m.name, expected, "set" + target)
1237 elif re.match("get[A-Z]", m.name):
1238 target = m.name[3:]
1239 expected = "set" + target
1240 error_if_exists(sets, m.name, expected, "setIs" + target)
1241 error_if_exists(sets, m.name, expected, "setHas" + target)
1242
1243 if is_set(m):
1244 if re.match("set[A-Z]", m.name):
1245 target = m.name[3:]
1246 expected = "get" + target
1247 error_if_exists(sets, m.name, expected, "is" + target)
1248 error_if_exists(sets, m.name, expected, "has" + target)
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001249
1250
1251def verify_collections(clazz):
1252 """Verifies that collection types are interfaces."""
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001253 if clazz.fullname == "android.os.Bundle": return
Adrian Roos02e18dd2019-02-28 12:41:48 +01001254 if clazz.fullname == "android.os.Parcel": return
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001255
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001256 bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack",
1257 "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"]
1258 for m in clazz.methods:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001259 if m.typ in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001260 error(clazz, m, "CL2", "Return type is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001261 for arg in m.args:
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07001262 if arg in bad:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001263 error(clazz, m, "CL2", "Argument is concrete collection; must be higher-level interface")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001264
1265
1266def verify_flags(clazz):
1267 """Verifies that flags are non-overlapping."""
1268 known = collections.defaultdict(int)
1269 for f in clazz.fields:
1270 if "FLAG_" in f.name:
1271 try:
1272 val = int(f.value)
1273 except:
1274 continue
1275
1276 scope = f.name[0:f.name.index("FLAG_")]
1277 if val & known[scope]:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001278 warn(clazz, f, "C1", "Found overlapping flag constant value")
Jeff Sharkey294f0de2014-08-29 17:41:43 -07001279 known[scope] |= val
1280
1281
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001282def verify_exception(clazz):
1283 """Verifies that methods don't throw generic exceptions."""
1284 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001285 for t in m.throws:
1286 if t in ["java.lang.Exception", "java.lang.Throwable", "java.lang.Error"]:
1287 error(clazz, m, "S1", "Methods must not throw generic exceptions")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001288
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001289 if t in ["android.os.RemoteException"]:
Adrian Roos02e18dd2019-02-28 12:41:48 +01001290 if clazz.fullname == "android.content.ContentProviderClient": continue
1291 if clazz.fullname == "android.os.Binder": continue
1292 if clazz.fullname == "android.os.IBinder": continue
Jeff Sharkey331279b2016-02-29 16:02:02 -07001293
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001294 error(clazz, m, "FW9", "Methods calling into system server should rethrow RemoteException as RuntimeException")
1295
1296 if len(m.args) == 0 and t in ["java.lang.IllegalArgumentException", "java.lang.NullPointerException"]:
1297 warn(clazz, m, "S1", "Methods taking no arguments should throw IllegalStateException")
Jeff Sharkey331279b2016-02-29 16:02:02 -07001298
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001299GOOGLE_IGNORECASE = re.compile("google", re.IGNORECASE)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001300
1301def verify_google(clazz):
1302 """Verifies that APIs never reference Google."""
1303
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001304 if GOOGLE_IGNORECASE.search(clazz.raw) is not None:
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001305 error(clazz, None, None, "Must never reference Google")
1306
Adrian Roos1f1b6a82019-01-05 20:09:38 +01001307 for test in clazz.ctors, clazz.fields, clazz.methods:
1308 for t in test:
1309 if GOOGLE_IGNORECASE.search(t.raw) is not None:
1310 error(clazz, t, None, "Must never reference Google")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001311
1312
1313def verify_bitset(clazz):
1314 """Verifies that we avoid using heavy BitSet."""
1315
1316 for f in clazz.fields:
1317 if f.typ == "java.util.BitSet":
1318 error(clazz, f, None, "Field type must not be heavy BitSet")
1319
1320 for m in clazz.methods:
1321 if m.typ == "java.util.BitSet":
1322 error(clazz, m, None, "Return type must not be heavy BitSet")
1323 for arg in m.args:
1324 if arg == "java.util.BitSet":
1325 error(clazz, m, None, "Argument type must not be heavy BitSet")
1326
1327
1328def verify_manager(clazz):
1329 """Verifies that FooManager is only obtained from Context."""
1330
1331 if not clazz.name.endswith("Manager"): return
1332
1333 for c in clazz.ctors:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001334 error(clazz, c, None, "Managers must always be obtained from Context; no direct constructors")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001335
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001336 for m in clazz.methods:
1337 if m.typ == clazz.fullname:
1338 error(clazz, m, None, "Managers must always be obtained from Context")
1339
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001340
1341def verify_boxed(clazz):
1342 """Verifies that methods avoid boxed primitives."""
1343
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001344 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 -08001345
1346 for c in clazz.ctors:
1347 for arg in c.args:
1348 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001349 error(clazz, c, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001350
1351 for f in clazz.fields:
1352 if f.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001353 error(clazz, f, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001354
1355 for m in clazz.methods:
1356 if m.typ in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001357 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001358 for arg in m.args:
1359 if arg in boxed:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001360 error(clazz, m, "M11", "Must avoid boxed primitives")
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001361
1362
1363def verify_static_utils(clazz):
1364 """Verifies that helper classes can't be constructed."""
1365 if clazz.fullname.startswith("android.opengl"): return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001366 if clazz.fullname.startswith("android.R"): return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001367
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001368 # Only care about classes with default constructors
1369 if len(clazz.ctors) == 1 and len(clazz.ctors[0].args) == 0:
1370 test = []
1371 test.extend(clazz.fields)
1372 test.extend(clazz.methods)
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001373
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001374 if len(test) == 0: return
1375 for t in test:
1376 if "static" not in t.split:
1377 return
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001378
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08001379 error(clazz, None, None, "Fully-static utility classes must not have constructor")
1380
1381
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001382def verify_overload_args(clazz):
1383 """Verifies that method overloads add new arguments at the end."""
1384 if clazz.fullname.startswith("android.opengl"): return
1385
1386 overloads = collections.defaultdict(list)
1387 for m in clazz.methods:
1388 if "deprecated" in m.split: continue
1389 overloads[m.name].append(m)
1390
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001391 for name, methods in overloads.items():
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001392 if len(methods) <= 1: continue
1393
1394 # Look for arguments common across all overloads
1395 def cluster(args):
1396 count = collections.defaultdict(int)
1397 res = set()
1398 for i in range(len(args)):
1399 a = args[i]
1400 res.add("%s#%d" % (a, count[a]))
1401 count[a] += 1
1402 return res
1403
1404 common_args = cluster(methods[0].args)
1405 for m in methods:
1406 common_args = common_args & cluster(m.args)
1407
1408 if len(common_args) == 0: continue
1409
1410 # Require that all common arguments are present at start of signature
1411 locked_sig = None
1412 for m in methods:
1413 sig = m.args[0:len(common_args)]
1414 if not common_args.issubset(cluster(sig)):
1415 warn(clazz, m, "M2", "Expected common arguments [%s] at beginning of overloaded method" % (", ".join(common_args)))
1416 elif not locked_sig:
1417 locked_sig = sig
1418 elif locked_sig != sig:
1419 error(clazz, m, "M2", "Expected consistent argument ordering between overloads: %s..." % (", ".join(locked_sig)))
1420
1421
1422def verify_callback_handlers(clazz):
1423 """Verifies that methods adding listener/callback have overload
1424 for specifying delivery thread."""
1425
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001426 # Ignore UI packages which assume main thread
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001427 skip = [
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001428 "animation",
1429 "view",
1430 "graphics",
1431 "transition",
1432 "widget",
1433 "webkit",
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001434 ]
1435 for s in skip:
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001436 if s in clazz.pkg.name_path: return
1437 if s in clazz.extends_path: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001438
Jeff Sharkey047d7f02015-02-25 11:27:55 -08001439 # Ignore UI classes which assume main thread
1440 if "app" in clazz.pkg.name_path or "app" in clazz.extends_path:
1441 for s in ["ActionBar","Dialog","Application","Activity","Fragment","Loader"]:
1442 if s in clazz.fullname: return
1443 if "content" in clazz.pkg.name_path or "content" in clazz.extends_path:
1444 for s in ["Loader"]:
1445 if s in clazz.fullname: return
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001446
1447 found = {}
1448 by_name = collections.defaultdict(list)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001449 examine = clazz.ctors + clazz.methods
1450 for m in examine:
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001451 if m.name.startswith("unregister"): continue
1452 if m.name.startswith("remove"): continue
1453 if re.match("on[A-Z]+", m.name): continue
1454
1455 by_name[m.name].append(m)
1456
1457 for a in m.args:
1458 if a.endswith("Listener") or a.endswith("Callback") or a.endswith("Callbacks"):
1459 found[m.name] = m
1460
1461 for f in found.values():
1462 takes_handler = False
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001463 takes_exec = False
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001464 for m in by_name[f.name]:
1465 if "android.os.Handler" in m.args:
1466 takes_handler = True
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001467 if "java.util.concurrent.Executor" in m.args:
1468 takes_exec = True
1469 if not takes_exec:
1470 warn(clazz, f, "L1", "Registration methods should have overload that accepts delivery Executor")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001471
1472
1473def verify_context_first(clazz):
1474 """Verifies that methods accepting a Context keep it the first argument."""
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001475 examine = clazz.ctors + clazz.methods
1476 for m in examine:
1477 if len(m.args) > 1 and m.args[0] != "android.content.Context":
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001478 if "android.content.Context" in m.args[1:]:
1479 error(clazz, m, "M3", "Context is distinct, so it must be the first argument")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001480 if len(m.args) > 1 and m.args[0] != "android.content.ContentResolver":
1481 if "android.content.ContentResolver" in m.args[1:]:
1482 error(clazz, m, "M3", "ContentResolver is distinct, so it must be the first argument")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001483
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001484
1485def verify_listener_last(clazz):
1486 """Verifies that methods accepting a Listener or Callback keep them as last arguments."""
1487 examine = clazz.ctors + clazz.methods
1488 for m in examine:
1489 if "Listener" in m.name or "Callback" in m.name: continue
1490 found = False
1491 for a in m.args:
1492 if a.endswith("Callback") or a.endswith("Callbacks") or a.endswith("Listener"):
1493 found = True
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001494 elif found:
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001495 warn(clazz, m, "M3", "Listeners should always be at end of argument list")
1496
1497
1498def verify_resource_names(clazz):
1499 """Verifies that resource names have consistent case."""
1500 if not re.match("android\.R\.[a-z]+", clazz.fullname): return
1501
1502 # Resources defined by files are foo_bar_baz
1503 if clazz.name in ["anim","animator","color","dimen","drawable","interpolator","layout","transition","menu","mipmap","string","plurals","raw","xml"]:
1504 for f in clazz.fields:
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001505 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1506 if f.name.startswith("config_"):
1507 error(clazz, f, None, "Expected config name to be config_fooBarBaz style")
1508
Jeff Sharkey90b547b2015-02-18 16:45:54 -08001509 if re.match("[a-z1-9_]+$", f.name): continue
1510 error(clazz, f, None, "Expected resource name in this class to be foo_bar_baz style")
1511
1512 # Resources defined inside files are fooBarBaz
1513 if clazz.name in ["array","attr","id","bool","fraction","integer"]:
1514 for f in clazz.fields:
1515 if re.match("config_[a-z][a-zA-Z1-9]*$", f.name): continue
1516 if re.match("layout_[a-z][a-zA-Z1-9]*$", f.name): continue
1517 if re.match("state_[a-z_]*$", f.name): continue
1518
1519 if re.match("[a-z][a-zA-Z1-9]*$", f.name): continue
1520 error(clazz, f, "C7", "Expected resource name in this class to be fooBarBaz style")
1521
1522 # Styles are FooBar_Baz
1523 if clazz.name in ["style"]:
1524 for f in clazz.fields:
1525 if re.match("[A-Z][A-Za-z1-9]+(_[A-Z][A-Za-z1-9]+?)*$", f.name): continue
1526 error(clazz, f, "C7", "Expected resource name in this class to be FooBar_Baz style")
Jeff Sharkeyb46a9692015-02-17 17:19:41 -08001527
1528
Jeff Sharkey331279b2016-02-29 16:02:02 -07001529def verify_files(clazz):
1530 """Verifies that methods accepting File also accept streams."""
1531
1532 has_file = set()
1533 has_stream = set()
1534
1535 test = []
1536 test.extend(clazz.ctors)
1537 test.extend(clazz.methods)
1538
1539 for m in test:
1540 if "java.io.File" in m.args:
1541 has_file.add(m)
1542 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:
1543 has_stream.add(m.name)
1544
1545 for m in has_file:
1546 if m.name not in has_stream:
1547 warn(clazz, m, "M10", "Methods accepting File should also accept FileDescriptor or streams")
1548
1549
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001550def verify_manager_list(clazz):
1551 """Verifies that managers return List<? extends Parcelable> instead of arrays."""
1552
1553 if not clazz.name.endswith("Manager"): return
1554
1555 for m in clazz.methods:
1556 if m.typ.startswith("android.") and m.typ.endswith("[]"):
1557 warn(clazz, m, None, "Methods should return List<? extends Parcelable> instead of Parcelable[] to support ParceledListSlice under the hood")
1558
1559
Jeff Sharkey26c80902016-12-21 13:41:17 -07001560def verify_abstract_inner(clazz):
1561 """Verifies that abstract inner classes are static."""
1562
1563 if re.match(".+?\.[A-Z][^\.]+\.[A-Z]", clazz.fullname):
Adrian Roosb787c182019-01-03 18:54:33 +01001564 if "abstract" in clazz.split and "static" not in clazz.split:
Jeff Sharkey26c80902016-12-21 13:41:17 -07001565 warn(clazz, None, None, "Abstract inner classes should be static to improve testability")
1566
1567
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001568def verify_runtime_exceptions(clazz):
1569 """Verifies that runtime exceptions aren't listed in throws."""
1570
1571 banned = [
1572 "java.lang.NullPointerException",
1573 "java.lang.ClassCastException",
1574 "java.lang.IndexOutOfBoundsException",
1575 "java.lang.reflect.UndeclaredThrowableException",
1576 "java.lang.reflect.MalformedParametersException",
1577 "java.lang.reflect.MalformedParameterizedTypeException",
1578 "java.lang.invoke.WrongMethodTypeException",
1579 "java.lang.EnumConstantNotPresentException",
1580 "java.lang.IllegalMonitorStateException",
1581 "java.lang.SecurityException",
1582 "java.lang.UnsupportedOperationException",
1583 "java.lang.annotation.AnnotationTypeMismatchException",
1584 "java.lang.annotation.IncompleteAnnotationException",
1585 "java.lang.TypeNotPresentException",
1586 "java.lang.IllegalStateException",
1587 "java.lang.ArithmeticException",
1588 "java.lang.IllegalArgumentException",
1589 "java.lang.ArrayStoreException",
1590 "java.lang.NegativeArraySizeException",
1591 "java.util.MissingResourceException",
1592 "java.util.EmptyStackException",
1593 "java.util.concurrent.CompletionException",
1594 "java.util.concurrent.RejectedExecutionException",
1595 "java.util.IllformedLocaleException",
1596 "java.util.ConcurrentModificationException",
1597 "java.util.NoSuchElementException",
1598 "java.io.UncheckedIOException",
1599 "java.time.DateTimeException",
1600 "java.security.ProviderException",
1601 "java.nio.BufferUnderflowException",
1602 "java.nio.BufferOverflowException",
1603 ]
1604
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001605 examine = clazz.ctors + clazz.methods
1606 for m in examine:
1607 for t in m.throws:
1608 if t in banned:
1609 error(clazz, m, None, "Methods must not mention RuntimeException subclasses in throws clauses")
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001610
1611
1612def verify_error(clazz):
1613 """Verifies that we always use Exception instead of Error."""
1614 if not clazz.extends: return
1615 if clazz.extends.endswith("Error"):
1616 error(clazz, None, None, "Trouble must be reported through an Exception, not Error")
1617 if clazz.extends.endswith("Exception") and not clazz.name.endswith("Exception"):
1618 error(clazz, None, None, "Exceptions must be named FooException")
1619
1620
1621def verify_units(clazz):
1622 """Verifies that we use consistent naming for units."""
1623
1624 # If we find K, recommend replacing with V
1625 bad = {
1626 "Ns": "Nanos",
1627 "Ms": "Millis or Micros",
1628 "Sec": "Seconds", "Secs": "Seconds",
1629 "Hr": "Hours", "Hrs": "Hours",
1630 "Mo": "Months", "Mos": "Months",
1631 "Yr": "Years", "Yrs": "Years",
1632 "Byte": "Bytes", "Space": "Bytes",
1633 }
1634
1635 for m in clazz.methods:
1636 if m.typ not in ["short","int","long"]: continue
1637 for k, v in bad.iteritems():
1638 if m.name.endswith(k):
1639 error(clazz, m, None, "Expected method name units to be " + v)
1640 if m.name.endswith("Nanos") or m.name.endswith("Micros"):
1641 warn(clazz, m, None, "Returned time values are strongly encouraged to be in milliseconds unless you need the extra precision")
1642 if m.name.endswith("Seconds"):
1643 error(clazz, m, None, "Returned time values must be in milliseconds")
1644
1645 for m in clazz.methods:
1646 typ = m.typ
1647 if typ == "void":
1648 if len(m.args) != 1: continue
1649 typ = m.args[0]
1650
1651 if m.name.endswith("Fraction") and typ != "float":
1652 error(clazz, m, None, "Fractions must use floats")
1653 if m.name.endswith("Percentage") and typ != "int":
1654 error(clazz, m, None, "Percentage must use ints")
1655
1656
1657def verify_closable(clazz):
1658 """Verifies that classes are AutoClosable."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001659 if "java.lang.AutoCloseable" in clazz.implements_all: return
1660 if "java.io.Closeable" in clazz.implements_all: return
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06001661
1662 for m in clazz.methods:
1663 if len(m.args) > 0: continue
1664 if m.name in ["close","release","destroy","finish","finalize","disconnect","shutdown","stop","free","quit"]:
1665 warn(clazz, m, None, "Classes that release resources should implement AutoClosable and CloseGuard")
1666 return
1667
1668
Jake Wharton9e6738f2017-08-23 11:59:55 -04001669def verify_member_name_not_kotlin_keyword(clazz):
1670 """Prevent method names which are keywords in Kotlin."""
1671
1672 # https://kotlinlang.org/docs/reference/keyword-reference.html#hard-keywords
1673 # This list does not include Java keywords as those are already impossible to use.
1674 keywords = [
1675 'as',
1676 'fun',
1677 'in',
1678 'is',
1679 'object',
1680 'typealias',
1681 'val',
1682 'var',
1683 'when',
1684 ]
1685
1686 for m in clazz.methods:
1687 if m.name in keywords:
1688 error(clazz, m, None, "Method name must not be a Kotlin keyword")
1689 for f in clazz.fields:
1690 if f.name in keywords:
1691 error(clazz, f, None, "Field name must not be a Kotlin keyword")
1692
1693
1694def verify_method_name_not_kotlin_operator(clazz):
1695 """Warn about method names which become operators in Kotlin."""
1696
1697 binary = set()
1698
1699 def unique_binary_op(m, op):
1700 if op in binary:
1701 error(clazz, m, None, "Only one of '{0}' and '{0}Assign' methods should be present for Kotlin".format(op))
1702 binary.add(op)
1703
1704 for m in clazz.methods:
Adrian Roosd1e38922019-01-14 15:44:15 +01001705 if 'static' in m.split or 'operator' in m.split:
Jake Wharton9e6738f2017-08-23 11:59:55 -04001706 continue
1707
1708 # https://kotlinlang.org/docs/reference/operator-overloading.html#unary-prefix-operators
1709 if m.name in ['unaryPlus', 'unaryMinus', 'not'] and len(m.args) == 0:
1710 warn(clazz, m, None, "Method can be invoked as a unary operator from Kotlin")
1711
1712 # https://kotlinlang.org/docs/reference/operator-overloading.html#increments-and-decrements
1713 if m.name in ['inc', 'dec'] and len(m.args) == 0 and m.typ != 'void':
1714 # This only applies if the return type is the same or a subtype of the enclosing class, but we have no
1715 # practical way of checking that relationship here.
1716 warn(clazz, m, None, "Method can be invoked as a pre/postfix inc/decrement operator from Kotlin")
1717
1718 # https://kotlinlang.org/docs/reference/operator-overloading.html#arithmetic
1719 if m.name in ['plus', 'minus', 'times', 'div', 'rem', 'mod', 'rangeTo'] and len(m.args) == 1:
1720 warn(clazz, m, None, "Method can be invoked as a binary operator from Kotlin")
1721 unique_binary_op(m, m.name)
1722
1723 # https://kotlinlang.org/docs/reference/operator-overloading.html#in
1724 if m.name == 'contains' and len(m.args) == 1 and m.typ == 'boolean':
1725 warn(clazz, m, None, "Method can be invoked as a 'in' operator from Kotlin")
1726
1727 # https://kotlinlang.org/docs/reference/operator-overloading.html#indexed
1728 if (m.name == 'get' and len(m.args) > 0) or (m.name == 'set' and len(m.args) > 1):
1729 warn(clazz, m, None, "Method can be invoked with an indexing operator from Kotlin")
1730
1731 # https://kotlinlang.org/docs/reference/operator-overloading.html#invoke
1732 if m.name == 'invoke':
1733 warn(clazz, m, None, "Method can be invoked with function call syntax from Kotlin")
1734
1735 # https://kotlinlang.org/docs/reference/operator-overloading.html#assignments
1736 if m.name in ['plusAssign', 'minusAssign', 'timesAssign', 'divAssign', 'remAssign', 'modAssign'] \
1737 and len(m.args) == 1 \
1738 and m.typ == 'void':
1739 warn(clazz, m, None, "Method can be invoked as a compound assignment operator from Kotlin")
1740 unique_binary_op(m, m.name[:-6]) # Remove 'Assign' suffix
1741
1742
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001743def verify_collections_over_arrays(clazz):
1744 """Warn that [] should be Collections."""
1745
Adrian Roosb787c182019-01-03 18:54:33 +01001746 if "@interface" in clazz.split:
1747 return
1748
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001749 safe = ["java.lang.String[]","byte[]","short[]","int[]","long[]","float[]","double[]","boolean[]","char[]"]
1750 for m in clazz.methods:
1751 if m.typ.endswith("[]") and m.typ not in safe:
1752 warn(clazz, m, None, "Method should return Collection<> (or subclass) instead of raw array")
1753 for arg in m.args:
1754 if arg.endswith("[]") and arg not in safe:
1755 warn(clazz, m, None, "Method argument should be Collection<> (or subclass) instead of raw array")
1756
1757
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001758def verify_user_handle(clazz):
1759 """Methods taking UserHandle should be ForUser or AsUser."""
1760 if clazz.name.endswith("Listener") or clazz.name.endswith("Callback") or clazz.name.endswith("Callbacks"): return
1761 if clazz.fullname == "android.app.admin.DeviceAdminReceiver": return
1762 if clazz.fullname == "android.content.pm.LauncherApps": return
1763 if clazz.fullname == "android.os.UserHandle": return
1764 if clazz.fullname == "android.os.UserManager": return
1765
1766 for m in clazz.methods:
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001767 if re.match("on[A-Z]+", m.name): continue
Jeff Sharkeya8e5df02018-11-27 17:33:42 -07001768
1769 has_arg = "android.os.UserHandle" in m.args
1770 has_name = m.name.endswith("AsUser") or m.name.endswith("ForUser")
1771
1772 if clazz.fullname.endswith("Manager") and has_arg:
1773 warn(clazz, m, None, "When a method overload is needed to target a specific "
1774 "UserHandle, callers should be directed to use "
1775 "Context.createPackageContextAsUser() and re-obtain the relevant "
1776 "Manager, and no new API should be added")
1777 elif has_arg and not has_name:
1778 warn(clazz, m, None, "Method taking UserHandle should be named 'doFooAsUser' "
1779 "or 'queryFooForUser'")
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07001780
1781
1782def verify_params(clazz):
1783 """Parameter classes should be 'Params'."""
1784 if clazz.name.endswith("Params"): return
1785 if clazz.fullname == "android.app.ActivityOptions": return
1786 if clazz.fullname == "android.app.BroadcastOptions": return
1787 if clazz.fullname == "android.os.Bundle": return
1788 if clazz.fullname == "android.os.BaseBundle": return
1789 if clazz.fullname == "android.os.PersistableBundle": return
1790
1791 bad = ["Param","Parameter","Parameters","Args","Arg","Argument","Arguments","Options","Bundle"]
1792 for b in bad:
1793 if clazz.name.endswith(b):
1794 error(clazz, None, None, "Classes holding a set of parameters should be called 'FooParams'")
1795
1796
1797def verify_services(clazz):
1798 """Service name should be FOO_BAR_SERVICE = 'foo_bar'."""
1799 if clazz.fullname != "android.content.Context": return
1800
1801 for f in clazz.fields:
1802 if f.typ != "java.lang.String": continue
1803 found = re.match(r"([A-Z_]+)_SERVICE", f.name)
1804 if found:
1805 expected = found.group(1).lower()
1806 if f.value != expected:
1807 error(clazz, f, "C4", "Inconsistent service value; expected '%s'" % (expected))
1808
1809
1810def verify_tense(clazz):
1811 """Verify tenses of method names."""
1812 if clazz.fullname.startswith("android.opengl"): return
1813
1814 for m in clazz.methods:
1815 if m.name.endswith("Enable"):
1816 warn(clazz, m, None, "Unexpected tense; probably meant 'enabled'")
1817
1818
Jeff Sharkeyee21f692018-02-16 13:29:29 -07001819def verify_icu(clazz):
1820 """Verifies that richer ICU replacements are used."""
1821 better = {
1822 "java.util.TimeZone": "android.icu.util.TimeZone",
1823 "java.util.Calendar": "android.icu.util.Calendar",
1824 "java.util.Locale": "android.icu.util.ULocale",
1825 "java.util.ResourceBundle": "android.icu.util.UResourceBundle",
1826 "java.util.SimpleTimeZone": "android.icu.util.SimpleTimeZone",
1827 "java.util.StringTokenizer": "android.icu.util.StringTokenizer",
1828 "java.util.GregorianCalendar": "android.icu.util.GregorianCalendar",
1829 "java.lang.Character": "android.icu.lang.UCharacter",
1830 "java.text.BreakIterator": "android.icu.text.BreakIterator",
1831 "java.text.Collator": "android.icu.text.Collator",
1832 "java.text.DecimalFormatSymbols": "android.icu.text.DecimalFormatSymbols",
1833 "java.text.NumberFormat": "android.icu.text.NumberFormat",
1834 "java.text.DateFormatSymbols": "android.icu.text.DateFormatSymbols",
1835 "java.text.DateFormat": "android.icu.text.DateFormat",
1836 "java.text.SimpleDateFormat": "android.icu.text.SimpleDateFormat",
1837 "java.text.MessageFormat": "android.icu.text.MessageFormat",
1838 "java.text.DecimalFormat": "android.icu.text.DecimalFormat",
1839 }
1840
1841 for m in clazz.ctors + clazz.methods:
1842 types = []
1843 types.extend(m.typ)
1844 types.extend(m.args)
1845 for arg in types:
1846 if arg in better:
1847 warn(clazz, m, None, "Type %s should be replaced with richer ICU type %s" % (arg, better[arg]))
1848
1849
1850def verify_clone(clazz):
1851 """Verify that clone() isn't implemented; see EJ page 61."""
1852 for m in clazz.methods:
1853 if m.name == "clone":
1854 error(clazz, m, None, "Provide an explicit copy constructor instead of implementing clone()")
1855
1856
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001857def verify_pfd(clazz):
1858 """Verify that android APIs use PFD over FD."""
Adrian Roos02e18dd2019-02-28 12:41:48 +01001859 if clazz.fullname == "android.os.FileUtils" or clazz.fullname == "android.system.Os":
1860 return
1861
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001862 examine = clazz.ctors + clazz.methods
1863 for m in examine:
1864 if m.typ == "java.io.FileDescriptor":
1865 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1866 if m.typ == "int":
1867 if "Fd" in m.name or "FD" in m.name or "FileDescriptor" in m.name:
1868 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1869 for arg in m.args:
1870 if arg == "java.io.FileDescriptor":
1871 error(clazz, m, "FW11", "Must use ParcelFileDescriptor")
1872
1873 for f in clazz.fields:
1874 if f.typ == "java.io.FileDescriptor":
1875 error(clazz, f, "FW11", "Must use ParcelFileDescriptor")
1876
1877
1878def verify_numbers(clazz):
1879 """Discourage small numbers types like short and byte."""
1880
1881 discouraged = ["short","byte"]
1882
1883 for c in clazz.ctors:
1884 for arg in c.args:
1885 if arg in discouraged:
1886 warn(clazz, c, "FW12", "Should avoid odd sized primitives; use int instead")
1887
1888 for f in clazz.fields:
1889 if f.typ in discouraged:
1890 warn(clazz, f, "FW12", "Should avoid odd sized primitives; use int instead")
1891
1892 for m in clazz.methods:
1893 if m.typ in discouraged:
1894 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1895 for arg in m.args:
1896 if arg in discouraged:
1897 warn(clazz, m, "FW12", "Should avoid odd sized primitives; use int instead")
1898
Adrian Roos80545ef2019-02-27 16:45:00 +01001899PRIMITIVES = {"void", "int", "float", "boolean", "short", "char", "byte", "long", "double"}
1900
1901def verify_nullability(clazz):
1902 """Catches missing nullability annotations"""
1903
1904 for f in clazz.fields:
1905 if f.value is not None and 'static' in f.split and 'final' in f.split:
1906 continue # Nullability of constants can be inferred.
1907 if f.typ not in PRIMITIVES and not has_nullability(f.annotations):
1908 error(clazz, f, "M12", "Field must be marked either @NonNull or @Nullable")
1909
1910 for c in clazz.ctors:
1911 verify_nullability_args(clazz, c)
1912
1913 for m in clazz.methods:
1914 if m.name == "writeToParcel" or m.name == "onReceive":
1915 continue # Parcelable.writeToParcel() and BroadcastReceiver.onReceive() are not yet annotated
1916
1917 if m.typ not in PRIMITIVES and not has_nullability(m.annotations):
1918 error(clazz, m, "M12", "Return value must be marked either @NonNull or @Nullable")
1919 verify_nullability_args(clazz, m)
1920
1921def verify_nullability_args(clazz, m):
1922 for i, arg in enumerate(m.detailed_args):
1923 if arg.type not in PRIMITIVES and not has_nullability(arg.annotations):
1924 error(clazz, m, "M12", "Argument %d must be marked either @NonNull or @Nullable" % (i+1,))
1925
1926def has_nullability(annotations):
1927 return "@NonNull" in annotations or "@Nullable" in annotations
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06001928
1929def verify_singleton(clazz):
1930 """Catch singleton objects with constructors."""
1931
1932 singleton = False
1933 for m in clazz.methods:
1934 if m.name.startswith("get") and m.name.endswith("Instance") and " static " in m.raw:
1935 singleton = True
1936
1937 if singleton:
1938 for c in clazz.ctors:
1939 error(clazz, c, None, "Singleton classes should use getInstance() methods")
1940
1941
1942
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001943def is_interesting(clazz):
1944 """Test if given class is interesting from an Android PoV."""
1945
1946 if clazz.pkg.name.startswith("java"): return False
1947 if clazz.pkg.name.startswith("junit"): return False
1948 if clazz.pkg.name.startswith("org.apache"): return False
1949 if clazz.pkg.name.startswith("org.xml"): return False
1950 if clazz.pkg.name.startswith("org.json"): return False
1951 if clazz.pkg.name.startswith("org.w3c"): return False
1952 if clazz.pkg.name.startswith("android.icu."): return False
1953 return True
1954
1955
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001956def examine_clazz(clazz):
1957 """Find all style issues in the given class."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07001958
1959 notice(clazz)
1960
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06001961 if not is_interesting(clazz): return
Jeff Sharkey8190f4882014-08-28 12:24:07 -07001962
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001963 verify_constants(clazz)
1964 verify_enums(clazz)
1965 verify_class_names(clazz)
1966 verify_method_names(clazz)
1967 verify_callbacks(clazz)
1968 verify_listeners(clazz)
1969 verify_actions(clazz)
1970 verify_extras(clazz)
1971 verify_equals(clazz)
1972 verify_parcelable(clazz)
1973 verify_protected(clazz)
1974 verify_fields(clazz)
1975 verify_register(clazz)
1976 verify_sync(clazz)
1977 verify_intent_builder(clazz)
1978 verify_helper_classes(clazz)
1979 verify_builder(clazz)
1980 verify_aidl(clazz)
1981 verify_internal(clazz)
1982 verify_layering(clazz)
1983 verify_boolean(clazz)
1984 verify_collections(clazz)
1985 verify_flags(clazz)
1986 verify_exception(clazz)
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07001987 if not ALLOW_GOOGLE: verify_google(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001988 verify_bitset(clazz)
1989 verify_manager(clazz)
1990 verify_boxed(clazz)
1991 verify_static_utils(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07001992 # verify_overload_args(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08001993 verify_callback_handlers(clazz)
1994 verify_context_first(clazz)
1995 verify_listener_last(clazz)
1996 verify_resource_names(clazz)
Jeff Sharkey331279b2016-02-29 16:02:02 -07001997 verify_files(clazz)
Jeff Sharkey70168dd2016-03-30 21:47:16 -06001998 verify_manager_list(clazz)
Jeff Sharkey26c80902016-12-21 13:41:17 -07001999 verify_abstract_inner(clazz)
Jeff Sharkey24bda1d2017-04-19 16:04:44 -06002000 verify_runtime_exceptions(clazz)
2001 verify_error(clazz)
2002 verify_units(clazz)
2003 verify_closable(clazz)
Jake Wharton9e6738f2017-08-23 11:59:55 -04002004 verify_member_name_not_kotlin_keyword(clazz)
2005 verify_method_name_not_kotlin_operator(clazz)
Jeff Sharkeybedb3fc2017-12-05 09:42:28 -07002006 verify_collections_over_arrays(clazz)
Jeff Sharkey336dd0b2018-01-12 12:12:27 -07002007 verify_user_handle(clazz)
2008 verify_params(clazz)
2009 verify_services(clazz)
2010 verify_tense(clazz)
Jeff Sharkeyee21f692018-02-16 13:29:29 -07002011 verify_icu(clazz)
2012 verify_clone(clazz)
Jeff Sharkeyeff9e222018-09-27 16:29:25 -06002013 verify_pfd(clazz)
2014 verify_numbers(clazz)
2015 verify_singleton(clazz)
Adrian Roos80545ef2019-02-27 16:45:00 +01002016 verify_nullability(clazz)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002017
2018
Adrian Roos038a0292018-12-19 17:11:21 +01002019def examine_stream(stream, base_stream=None, in_classes_with_base=[], out_classes_with_base=None):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002020 """Find all style issues in the given API stream."""
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002021 global failures, noticed
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002022 failures = {}
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002023 noticed = {}
Adrian Roos038a0292018-12-19 17:11:21 +01002024 _parse_stream(stream, examine_clazz, base_f=base_stream,
2025 in_classes_with_base=in_classes_with_base,
2026 out_classes_with_base=out_classes_with_base)
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002027 return (failures, noticed)
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002028
2029
2030def examine_api(api):
2031 """Find all style issues in the given parsed API."""
2032 global failures
Jeff Sharkey1498f9c2014-09-04 12:45:33 -07002033 failures = {}
2034 for key in sorted(api.keys()):
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002035 examine_clazz(api[key])
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002036 return failures
2037
2038
Jeff Sharkey037458a2014-09-04 15:46:20 -07002039def verify_compat(cur, prev):
2040 """Find any incompatible API changes between two levels."""
2041 global failures
2042
2043 def class_exists(api, test):
2044 return test.fullname in api
2045
2046 def ctor_exists(api, clazz, test):
2047 for m in clazz.ctors:
2048 if m.ident == test.ident: return True
2049 return False
2050
2051 def all_methods(api, clazz):
2052 methods = list(clazz.methods)
2053 if clazz.extends is not None:
2054 methods.extend(all_methods(api, api[clazz.extends]))
2055 return methods
2056
2057 def method_exists(api, clazz, test):
2058 methods = all_methods(api, clazz)
2059 for m in methods:
2060 if m.ident == test.ident: return True
2061 return False
2062
2063 def field_exists(api, clazz, test):
2064 for f in clazz.fields:
2065 if f.ident == test.ident: return True
2066 return False
2067
2068 failures = {}
2069 for key in sorted(prev.keys()):
2070 prev_clazz = prev[key]
2071
2072 if not class_exists(cur, prev_clazz):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002073 error(prev_clazz, None, None, "Class removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002074 continue
2075
2076 cur_clazz = cur[key]
2077
2078 for test in prev_clazz.ctors:
2079 if not ctor_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002080 error(prev_clazz, prev_ctor, None, "Constructor removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002081
2082 methods = all_methods(prev, prev_clazz)
2083 for test in methods:
2084 if not method_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002085 error(prev_clazz, test, None, "Method removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002086
2087 for test in prev_clazz.fields:
2088 if not field_exists(cur, cur_clazz, test):
Jeff Sharkey9f64d5c62015-02-14 17:03:47 -08002089 error(prev_clazz, test, None, "Field removed or incompatible change")
Jeff Sharkey037458a2014-09-04 15:46:20 -07002090
2091 return failures
2092
2093
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002094def show_deprecations_at_birth(cur, prev):
2095 """Show API deprecations at birth."""
2096 global failures
2097
2098 # Remove all existing things so we're left with new
2099 for prev_clazz in prev.values():
2100 cur_clazz = cur[prev_clazz.fullname]
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002101 if not is_interesting(cur_clazz): continue
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002102
2103 sigs = { i.ident: i for i in prev_clazz.ctors }
2104 cur_clazz.ctors = [ i for i in cur_clazz.ctors if i.ident not in sigs ]
2105 sigs = { i.ident: i for i in prev_clazz.methods }
2106 cur_clazz.methods = [ i for i in cur_clazz.methods if i.ident not in sigs ]
2107 sigs = { i.ident: i for i in prev_clazz.fields }
2108 cur_clazz.fields = [ i for i in cur_clazz.fields if i.ident not in sigs ]
2109
2110 # Forget about class entirely when nothing new
2111 if len(cur_clazz.ctors) == 0 and len(cur_clazz.methods) == 0 and len(cur_clazz.fields) == 0:
2112 del cur[prev_clazz.fullname]
2113
2114 for clazz in cur.values():
Adrian Roosb787c182019-01-03 18:54:33 +01002115 if "deprecated" in clazz.split and not clazz.fullname in prev:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002116 error(clazz, None, None, "Found API deprecation at birth")
2117
2118 for i in clazz.ctors + clazz.methods + clazz.fields:
Adrian Roosb787c182019-01-03 18:54:33 +01002119 if "deprecated" in i.split:
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002120 error(clazz, i, None, "Found API deprecation at birth")
2121
2122 print "%s Deprecated at birth %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True),
2123 format(reset=True)))
2124 for f in sorted(failures):
2125 print failures[f]
2126 print
2127
2128
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002129def show_stats(cur, prev):
2130 """Show API stats."""
2131
2132 stats = collections.defaultdict(int)
2133 for cur_clazz in cur.values():
2134 if not is_interesting(cur_clazz): continue
2135
2136 if cur_clazz.fullname not in prev:
2137 stats['new_classes'] += 1
2138 stats['new_ctors'] += len(cur_clazz.ctors)
2139 stats['new_methods'] += len(cur_clazz.methods)
2140 stats['new_fields'] += len(cur_clazz.fields)
2141 else:
2142 prev_clazz = prev[cur_clazz.fullname]
2143
2144 sigs = { i.ident: i for i in prev_clazz.ctors }
2145 ctors = len([ i for i in cur_clazz.ctors if i.ident not in sigs ])
2146 sigs = { i.ident: i for i in prev_clazz.methods }
2147 methods = len([ i for i in cur_clazz.methods if i.ident not in sigs ])
2148 sigs = { i.ident: i for i in prev_clazz.fields }
2149 fields = len([ i for i in cur_clazz.fields if i.ident not in sigs ])
2150
2151 if ctors + methods + fields > 0:
2152 stats['extend_classes'] += 1
2153 stats['extend_ctors'] += ctors
2154 stats['extend_methods'] += methods
2155 stats['extend_fields'] += fields
2156
2157 print "#", "".join([ k.ljust(20) for k in sorted(stats.keys()) ])
2158 print " ", "".join([ str(stats[k]).ljust(20) for k in sorted(stats.keys()) ])
2159
2160
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002161if __name__ == "__main__":
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002162 parser = argparse.ArgumentParser(description="Enforces common Android public API design \
2163 patterns. It ignores lint messages from a previous API level, if provided.")
2164 parser.add_argument("current.txt", type=argparse.FileType('r'), help="current.txt")
2165 parser.add_argument("previous.txt", nargs='?', type=argparse.FileType('r'), default=None,
2166 help="previous.txt")
Adrian Roos6eb57b02018-12-13 22:08:29 +01002167 parser.add_argument("--base-current", nargs='?', type=argparse.FileType('r'), default=None,
2168 help="The base current.txt to use when examining system-current.txt or"
2169 " test-current.txt")
2170 parser.add_argument("--base-previous", nargs='?', type=argparse.FileType('r'), default=None,
2171 help="The base previous.txt to use when examining system-previous.txt or"
2172 " test-previous.txt")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002173 parser.add_argument("--no-color", action='store_const', const=True,
2174 help="Disable terminal colors")
2175 parser.add_argument("--allow-google", action='store_const', const=True,
2176 help="Allow references to Google")
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002177 parser.add_argument("--show-noticed", action='store_const', const=True,
2178 help="Show API changes noticed")
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002179 parser.add_argument("--show-deprecations-at-birth", action='store_const', const=True,
2180 help="Show API deprecations at birth")
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002181 parser.add_argument("--show-stats", action='store_const', const=True,
2182 help="Show API stats")
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002183 args = vars(parser.parse_args())
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002184
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002185 if args['no_color']:
2186 USE_COLOR = False
2187
2188 if args['allow_google']:
2189 ALLOW_GOOGLE = True
2190
2191 current_file = args['current.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002192 base_current_file = args['base_current']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002193 previous_file = args['previous.txt']
Adrian Roos6eb57b02018-12-13 22:08:29 +01002194 base_previous_file = args['base_previous']
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002195
Jeff Sharkey8b141b92018-04-11 10:05:44 -06002196 if args['show_deprecations_at_birth']:
2197 with current_file as f:
2198 cur = _parse_stream(f)
2199 with previous_file as f:
2200 prev = _parse_stream(f)
2201 show_deprecations_at_birth(cur, prev)
2202 sys.exit()
2203
Jeff Sharkeyfe5ee6e2018-04-20 11:26:16 -06002204 if args['show_stats']:
2205 with current_file as f:
2206 cur = _parse_stream(f)
2207 with previous_file as f:
2208 prev = _parse_stream(f)
2209 show_stats(cur, prev)
2210 sys.exit()
2211
Adrian Roos038a0292018-12-19 17:11:21 +01002212 classes_with_base = []
2213
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002214 with current_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002215 if base_current_file:
2216 with base_current_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002217 cur_fail, cur_noticed = examine_stream(f, base_f,
2218 out_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002219 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002220 cur_fail, cur_noticed = examine_stream(f, out_classes_with_base=classes_with_base)
2221
Adam Metcalf1c7e70a2015-03-27 16:11:23 -07002222 if not previous_file is None:
2223 with previous_file as f:
Adrian Roos6eb57b02018-12-13 22:08:29 +01002224 if base_previous_file:
2225 with base_previous_file as base_f:
Adrian Roos038a0292018-12-19 17:11:21 +01002226 prev_fail, prev_noticed = examine_stream(f, base_f,
2227 in_classes_with_base=classes_with_base)
Adrian Roos6eb57b02018-12-13 22:08:29 +01002228 else:
Adrian Roos038a0292018-12-19 17:11:21 +01002229 prev_fail, prev_noticed = examine_stream(f, in_classes_with_base=classes_with_base)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002230
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002231 # ignore errors from previous API level
2232 for p in prev_fail:
2233 if p in cur_fail:
2234 del cur_fail[p]
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002235
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002236 # ignore classes unchanged from previous API level
2237 for k, v in prev_noticed.iteritems():
2238 if k in cur_noticed and v == cur_noticed[k]:
2239 del cur_noticed[k]
2240
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002241 """
2242 # NOTE: disabled because of memory pressure
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002243 # look for compatibility issues
2244 compat_fail = verify_compat(cur, prev)
Jeff Sharkey8190f4882014-08-28 12:24:07 -07002245
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002246 print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2247 for f in sorted(compat_fail):
2248 print compat_fail[f]
2249 print
Jeff Sharkeya18a2e32015-02-22 15:54:32 -08002250 """
Jeff Sharkeyed6aaf02015-01-30 13:31:45 -07002251
Jeff Sharkeyc6c6c342017-11-13 09:13:37 -07002252 if args['show_noticed'] and len(cur_noticed) != 0:
2253 print "%s API changes noticed %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2254 for f in sorted(cur_noticed.keys()):
2255 print f
2256 print
2257
Jason Monk53b2a732017-11-10 15:43:17 -05002258 if len(cur_fail) != 0:
2259 print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True)))
2260 for f in sorted(cur_fail):
2261 print cur_fail[f]
2262 print
2263 sys.exit(77)