blob: 500b1cc607e3ba2b65b4fb1f407627c30dedbd94 [file] [log] [blame]
dmazzoni832058d12016-08-31 21:55:311# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Presubmit script for ui/accessibility."""
6
Dominic Mazzonidcef1b732018-01-26 17:57:047import os, re, json
dmazzoni832058d12016-08-31 21:55:318
Dominic Mazzonidcef1b732018-01-26 17:57:049AX_MOJOM = 'ui/accessibility/ax_enums.mojom'
dmazzoni832058d12016-08-31 21:55:3110AUTOMATION_IDL = 'chrome/common/extensions/api/automation.idl'
11
Doug Turner63f3c7b2017-07-29 05:10:0112AX_JS_FILE = 'content/browser/resources/accessibility/accessibility.js'
13AX_MODE_HEADER = 'ui/accessibility/ax_modes.h'
14
dmazzoni832058d12016-08-31 21:55:3115def InitialLowerCamelCase(unix_name):
16 words = unix_name.split('_')
17 return words[0] + ''.join(word.capitalize() for word in words[1:])
18
Dominic Mazzonidcef1b732018-01-26 17:57:0419def CamelToLowerHacker(str):
20 out = ''
21 for i in range(len(str)):
22 if str[i] >= 'A' and str[i] <= 'Z' and out:
23 out += '_'
24 out += str[i]
25 return out.lower()
26
27# Given a full path to an IDL or MOJOM file containing enum definitions,
dmazzoni832058d12016-08-31 21:55:3128# parse the file for enums and return a dict mapping the enum name
29# to a list of values for that enum.
30def GetEnumsFromFile(fullpath):
31 enum_name = None
32 enums = {}
33 for line in open(fullpath).readlines():
34 # Strip out comments
35 line = re.sub('//.*', '', line)
36
37 # Look for lines of the form "enum ENUM_NAME {" and get the enum_name
38 m = re.search('enum ([\w]+) {', line)
39 if m:
40 enum_name = m.group(1)
41 continue
42
43 # Look for a "}" character signifying the end of an enum
44 if line.find('}') >= 0:
45 enum_name = None
46 continue
47
48 if not enum_name:
49 continue
50
51 # If we're inside an enum definition, add the first string consisting of
52 # alphanumerics plus underscore ("\w") to the list of values for that enum.
53 m = re.search('([\w]+)', line)
54 if m:
55 enums.setdefault(enum_name, [])
Dominic Mazzonidcef1b732018-01-26 17:57:0456 enum_value = m.group(1)
57 if (enum_value[0] == 'k' and
58 enum_value[1] == enum_value[1].upper()):
59 enum_value = CamelToLowerHacker(enum_value[1:])
60 if enum_value == 'none' or enum_value == 'last':
61 continue
62 if enum_value == 'active_descendant_changed':
63 enum_value = 'activedescendantchanged'
64 enums[enum_name].append(enum_value)
dmazzoni832058d12016-08-31 21:55:3165
66 return enums
67
68def CheckMatchingEnum(ax_enums,
69 ax_enum_name,
70 automation_enums,
71 automation_enum_name,
72 errs,
73 output_api):
74 if ax_enum_name not in ax_enums:
75 errs.append(output_api.PresubmitError(
Dominic Mazzonidcef1b732018-01-26 17:57:0476 'Expected %s to have an enum named %s' % (AX_MOJOM, ax_enum_name)))
dmazzoni832058d12016-08-31 21:55:3177 return
78 if automation_enum_name not in automation_enums:
79 errs.append(output_api.PresubmitError(
80 'Expected %s to have an enum named %s' % (
81 AUTOMATION_IDL, automation_enum_name)))
82 return
83 src = ax_enums[ax_enum_name]
84 dst = automation_enums[automation_enum_name]
85 for value in src:
Aaron Leventhal3b8e2582017-07-29 18:59:5186 lower_value = InitialLowerCamelCase(value)
87 if lower_value in dst:
88 dst.remove(lower_value) # Any remaining at end are extra and a mismatch.
89 else:
dmazzoni832058d12016-08-31 21:55:3190 errs.append(output_api.PresubmitError(
91 'Found %s.%s in %s, but did not find %s.%s in %s' % (
Dominic Mazzonidcef1b732018-01-26 17:57:0492 ax_enum_name, value, AX_MOJOM,
dmazzoni832058d12016-08-31 21:55:3193 automation_enum_name, InitialLowerCamelCase(value),
94 AUTOMATION_IDL)))
Aaron Leventhal3b8e2582017-07-29 18:59:5195 # Should be no remaining items
96 for value in dst:
97 errs.append(output_api.PresubmitError(
98 'Found %s.%s in %s, but did not find %s.%s in %s' % (
99 automation_enum_name, value, AUTOMATION_IDL,
100 ax_enum_name, InitialLowerCamelCase(value),
Dominic Mazzonidcef1b732018-01-26 17:57:04101 AX_MOJOM)))
dmazzoni832058d12016-08-31 21:55:31102
103def CheckEnumsMatch(input_api, output_api):
104 repo_root = input_api.change.RepositoryRoot()
Dominic Mazzonidcef1b732018-01-26 17:57:04105 ax_enums = GetEnumsFromFile(os.path.join(repo_root, AX_MOJOM))
dmazzoni832058d12016-08-31 21:55:31106 automation_enums = GetEnumsFromFile(os.path.join(repo_root, AUTOMATION_IDL))
David Tseng4a25f752017-08-22 19:56:11107
108 # Focused state only exists in automation.
109 automation_enums['StateType'].remove('focused')
Katie Dektarbd851852017-09-29 04:44:25110 # Offscreen state only exists in automation.
111 automation_enums['StateType'].remove('offscreen')
David Tseng4a25f752017-08-22 19:56:11112
dmazzoni832058d12016-08-31 21:55:31113 errs = []
Dominic Mazzonidcef1b732018-01-26 17:57:04114 CheckMatchingEnum(ax_enums, 'Role', automation_enums, 'RoleType', errs,
dmazzoni832058d12016-08-31 21:55:31115 output_api)
Dominic Mazzonidcef1b732018-01-26 17:57:04116 CheckMatchingEnum(ax_enums, 'State', automation_enums, 'StateType', errs,
dmazzoni832058d12016-08-31 21:55:31117 output_api)
Dominic Mazzonidcef1b732018-01-26 17:57:04118 CheckMatchingEnum(ax_enums, 'Event', automation_enums, 'EventType', errs,
dmazzoni832058d12016-08-31 21:55:31119 output_api)
Dominic Mazzonidcef1b732018-01-26 17:57:04120 CheckMatchingEnum(ax_enums, 'NameFrom', automation_enums, 'NameFromType',
dmazzoni59642422017-01-25 23:17:46121 errs, output_api)
Dominic Mazzonidcef1b732018-01-26 17:57:04122 CheckMatchingEnum(ax_enums, 'Restriction', automation_enums,
Aaron Leventhalf36960a2017-07-19 13:07:46123 'Restriction', errs, output_api)
dmazzoni832058d12016-08-31 21:55:31124 return errs
125
Doug Turner63f3c7b2017-07-29 05:10:01126# Given a full path to c++ header, return an array of the first static
127# constexpr defined. (Note there can be more than one defined in a C++
128# header)
129def GetConstexprFromFile(fullpath):
130 values = []
131 for line in open(fullpath).readlines():
132 # Strip out comments
133 line = re.sub('//.*', '', line)
134
135 # Look for lines of the form "static constexpr <type> NAME "
136 m = re.search('static constexpr [\w]+ ([\w]+)', line)
137 if m:
138 values.append(m.group(1))
139
140 return values
141
142# Given a full path to js file, return the AXMode consts
143# defined
144def GetAccessibilityModesFromFile(fullpath):
145 values = []
146 inside = False
147 for line in open(fullpath).readlines():
148 # Strip out comments
149 line = re.sub('//.*', '', line)
150
151 # Look for the block of code that defines AXMode
152 m = re.search('const AXMode = {', line)
153 if m:
154 inside = True
155 continue
156
157 # Look for a "}" character signifying the end of an enum
158 if line.find('};') >= 0:
159 return values
160
161 if not inside:
162 continue
163
164 m = re.search('([\w]+):', line)
165 if m:
166 values.append(m.group(1))
167 continue
168
169 # getters
170 m = re.search('get ([\w]+)\(\)', line)
171 if m:
172 values.append(m.group(1))
173 return values
174
175# Make sure that the modes defined in the C++ header match those defined in
176# the js file. Note that this doesn't guarantee that the values are the same,
177# but does make sure if we add or remove we can signal to the developer that
178# they should be aware that this dependency exists.
179def CheckModesMatch(input_api, output_api):
180 errs = []
181 repo_root = input_api.change.RepositoryRoot()
182
183 ax_modes_in_header = GetConstexprFromFile(
184 os.path.join(repo_root,AX_MODE_HEADER))
185 ax_modes_in_js = GetAccessibilityModesFromFile(
186 os.path.join(repo_root, AX_JS_FILE))
187
188 for value in ax_modes_in_header:
189 if value not in ax_modes_in_js:
190 errs.append(output_api.PresubmitError(
191 'Found %s in %s, but did not find %s in %s' % (
192 value, AX_MODE_HEADER, value, AX_JS_FILE)))
193 return errs
194
dmazzoni832058d12016-08-31 21:55:31195def CheckChangeOnUpload(input_api, output_api):
Doug Turner63f3c7b2017-07-29 05:10:01196 errs = []
Doug Turner4430d79c2017-08-07 21:00:40197 for path in input_api.LocalPaths():
198 path = path.replace('\\', '/')
Dominic Mazzonidcef1b732018-01-26 17:57:04199 if AX_MOJOM == path:
Doug Turner4430d79c2017-08-07 21:00:40200 errs.extend(CheckEnumsMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01201
Doug Turner4430d79c2017-08-07 21:00:40202 if AX_MODE_HEADER == path:
203 errs.extend(CheckModesMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01204
205 return errs
dmazzoni832058d12016-08-31 21:55:31206
207def CheckChangeOnCommit(input_api, output_api):
Doug Turner63f3c7b2017-07-29 05:10:01208 errs = []
Doug Turner4430d79c2017-08-07 21:00:40209 for path in input_api.LocalPaths():
210 path = path.replace('\\', '/')
Dominic Mazzonidcef1b732018-01-26 17:57:04211 if AX_MOJOM == path:
Doug Turner4430d79c2017-08-07 21:00:40212 errs.extend(CheckEnumsMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01213
Doug Turner4430d79c2017-08-07 21:00:40214 if AX_MODE_HEADER == path:
215 errs.extend(CheckModesMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01216
217 return errs
Dominic Mazzonidcef1b732018-01-26 17:57:04218
219# Run this script directly to dump its keys, for debugging.
220if __name__ == '__main__':
221 print json.dumps(GetEnumsFromFile(AX_MOJOM), sort_keys=True, indent=4)