blob: 0cbcf9bd9e6c9a59ebee7561b2cf70e05ade5af2 [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'
Randy Rossi9e3b5c52019-04-30 20:58:5810AUTOMATION_IDL = 'extensions/common/api/automation.idl'
dmazzoni832058d12016-08-31 21:55:3111
Dominic Mazzoni90501482018-09-05 22:43:3112AX_JS_FILE = 'chrome/browser/resources/accessibility/accessibility.js'
13AX_MODE_HEADER = 'ui/accessibility/ax_mode.h'
Doug Turner63f3c7b2017-07-29 05:10:0114
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
Dominic Mazzonidcef1b732018-01-26 17:57:0462 enums[enum_name].append(enum_value)
dmazzoni832058d12016-08-31 21:55:3163
64 return enums
65
66def CheckMatchingEnum(ax_enums,
67 ax_enum_name,
68 automation_enums,
69 automation_enum_name,
70 errs,
David Tsenge8aa563c2019-02-14 02:42:0771 output_api,
Dominic Mazzonid1d89d72020-10-05 07:20:1672 strict_ordering=False,
73 allow_extra_destination_enums=False):
dmazzoni832058d12016-08-31 21:55:3174 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]
David Tsenge8aa563c2019-02-14 02:42:0785 if strict_ordering and len(src) != len(dst):
86 errs.append(output_api.PresubmitError(
87 'Expected %s to have the same number of items as %s' % (
88 automation_enum_name, ax_enum_name)))
89 return
90
91 if strict_ordering:
92 for index, value in enumerate(src):
93 lower_value = InitialLowerCamelCase(value)
94 if lower_value != dst[index]:
95 errs.append(output_api.PresubmitError(
96 ('At index %s in enums, unexpected ordering around %s.%s ' +
97 'and %s.%s in %s and %s') % (
98 index, ax_enum_name, lower_value,
99 automation_enum_name, dst[index],
100 AX_MOJOM, AUTOMATION_IDL)))
101 return
102 return
103
dmazzoni832058d12016-08-31 21:55:31104 for value in src:
Aaron Leventhal3b8e2582017-07-29 18:59:51105 lower_value = InitialLowerCamelCase(value)
106 if lower_value in dst:
107 dst.remove(lower_value) # Any remaining at end are extra and a mismatch.
108 else:
dmazzoni832058d12016-08-31 21:55:31109 errs.append(output_api.PresubmitError(
110 'Found %s.%s in %s, but did not find %s.%s in %s' % (
Dominic Mazzonidcef1b732018-01-26 17:57:04111 ax_enum_name, value, AX_MOJOM,
dmazzoni832058d12016-08-31 21:55:31112 automation_enum_name, InitialLowerCamelCase(value),
113 AUTOMATION_IDL)))
Aaron Leventhal3b8e2582017-07-29 18:59:51114 # Should be no remaining items
Dominic Mazzonid1d89d72020-10-05 07:20:16115 if not allow_extra_destination_enums:
116 for value in dst:
117 errs.append(output_api.PresubmitError(
118 'Found %s.%s in %s, but did not find %s.%s in %s' % (
119 automation_enum_name, value, AUTOMATION_IDL,
120 ax_enum_name, InitialLowerCamelCase(value),
121 AX_MOJOM)))
dmazzoni832058d12016-08-31 21:55:31122
123def CheckEnumsMatch(input_api, output_api):
124 repo_root = input_api.change.RepositoryRoot()
Dominic Mazzonidcef1b732018-01-26 17:57:04125 ax_enums = GetEnumsFromFile(os.path.join(repo_root, AX_MOJOM))
dmazzoni832058d12016-08-31 21:55:31126 automation_enums = GetEnumsFromFile(os.path.join(repo_root, AUTOMATION_IDL))
David Tseng4a25f752017-08-22 19:56:11127
128 # Focused state only exists in automation.
129 automation_enums['StateType'].remove('focused')
Katie Dektarbd851852017-09-29 04:44:25130 # Offscreen state only exists in automation.
131 automation_enums['StateType'].remove('offscreen')
David Tseng4a25f752017-08-22 19:56:11132
dmazzoni832058d12016-08-31 21:55:31133 errs = []
Dominic Mazzonidcef1b732018-01-26 17:57:04134 CheckMatchingEnum(ax_enums, 'Role', automation_enums, 'RoleType', errs,
dmazzoni832058d12016-08-31 21:55:31135 output_api)
Dominic Mazzonidcef1b732018-01-26 17:57:04136 CheckMatchingEnum(ax_enums, 'State', automation_enums, 'StateType', errs,
David Tsenge8aa563c2019-02-14 02:42:07137 output_api, strict_ordering=True)
David Tseng710bbab2018-04-09 23:47:38138 CheckMatchingEnum(ax_enums, 'Action', automation_enums, 'ActionType', errs,
David Tsenge8aa563c2019-02-14 02:42:07139 output_api, strict_ordering=True)
Dominic Mazzonidcef1b732018-01-26 17:57:04140 CheckMatchingEnum(ax_enums, 'Event', automation_enums, 'EventType', errs,
Dominic Mazzonid1d89d72020-10-05 07:20:16141 output_api, allow_extra_destination_enums=True)
Dominic Mazzonidcef1b732018-01-26 17:57:04142 CheckMatchingEnum(ax_enums, 'NameFrom', automation_enums, 'NameFromType',
dmazzoni59642422017-01-25 23:17:46143 errs, output_api)
David Tseng2038f7412019-12-04 05:12:38144 CheckMatchingEnum(ax_enums, 'DescriptionFrom', automation_enums,
145 'DescriptionFromType', errs, output_api)
Dominic Mazzonidcef1b732018-01-26 17:57:04146 CheckMatchingEnum(ax_enums, 'Restriction', automation_enums,
Aaron Leventhalf36960a2017-07-19 13:07:46147 'Restriction', errs, output_api)
David Tsengca14de92018-03-14 20:30:29148 CheckMatchingEnum(ax_enums, 'DefaultActionVerb', automation_enums,
149 'DefaultActionVerb', errs, output_api)
David Tseng2038f7412019-12-04 05:12:38150 CheckMatchingEnum(ax_enums, 'MarkerType', automation_enums,
151 'MarkerType', errs, output_api)
David Tseng2d099ea2020-05-14 18:56:09152 CheckMatchingEnum(ax_enums, 'Command', automation_enums,
Nektarios Paisios8b5613b02020-09-23 17:30:29153 'IntentCommandType', errs, output_api)
154 CheckMatchingEnum(ax_enums, 'InputEventType', automation_enums,
155 'IntentInputEventType', errs, output_api)
David Tseng2d099ea2020-05-14 18:56:09156 CheckMatchingEnum(ax_enums, 'TextBoundary', automation_enums,
Nektarios Paisios8b5613b02020-09-23 17:30:29157 'IntentTextBoundaryType', errs, output_api)
David Tseng2d099ea2020-05-14 18:56:09158 CheckMatchingEnum(ax_enums, 'MoveDirection', automation_enums,
Nektarios Paisios8b5613b02020-09-23 17:30:29159 'IntentMoveDirectionType', errs, output_api)
David Tseng62f95522020-08-24 18:18:19160 CheckMatchingEnum(ax_enums, 'SortDirection', automation_enums,
161 'SortDirectionType', errs, output_api)
dmazzoni832058d12016-08-31 21:55:31162 return errs
163
Doug Turner63f3c7b2017-07-29 05:10:01164# Given a full path to c++ header, return an array of the first static
165# constexpr defined. (Note there can be more than one defined in a C++
166# header)
167def GetConstexprFromFile(fullpath):
168 values = []
169 for line in open(fullpath).readlines():
170 # Strip out comments
171 line = re.sub('//.*', '', line)
172
173 # Look for lines of the form "static constexpr <type> NAME "
174 m = re.search('static constexpr [\w]+ ([\w]+)', line)
175 if m:
Dominic Mazzonid9fda172019-03-07 17:12:07176 value = m.group(1)
177 # Skip first/last sentinels
178 if value == 'kFirstModeFlag' or value == 'kLastModeFlag':
179 continue
180 values.append(value)
Doug Turner63f3c7b2017-07-29 05:10:01181
182 return values
183
184# Given a full path to js file, return the AXMode consts
185# defined
186def GetAccessibilityModesFromFile(fullpath):
187 values = []
188 inside = False
189 for line in open(fullpath).readlines():
190 # Strip out comments
191 line = re.sub('//.*', '', line)
192
193 # Look for the block of code that defines AXMode
194 m = re.search('const AXMode = {', line)
195 if m:
196 inside = True
197 continue
198
199 # Look for a "}" character signifying the end of an enum
200 if line.find('};') >= 0:
201 return values
202
203 if not inside:
204 continue
205
206 m = re.search('([\w]+):', line)
207 if m:
208 values.append(m.group(1))
209 continue
210
211 # getters
212 m = re.search('get ([\w]+)\(\)', line)
213 if m:
214 values.append(m.group(1))
215 return values
216
217# Make sure that the modes defined in the C++ header match those defined in
218# the js file. Note that this doesn't guarantee that the values are the same,
219# but does make sure if we add or remove we can signal to the developer that
220# they should be aware that this dependency exists.
221def CheckModesMatch(input_api, output_api):
222 errs = []
223 repo_root = input_api.change.RepositoryRoot()
224
225 ax_modes_in_header = GetConstexprFromFile(
226 os.path.join(repo_root,AX_MODE_HEADER))
227 ax_modes_in_js = GetAccessibilityModesFromFile(
228 os.path.join(repo_root, AX_JS_FILE))
229
230 for value in ax_modes_in_header:
231 if value not in ax_modes_in_js:
232 errs.append(output_api.PresubmitError(
233 'Found %s in %s, but did not find %s in %s' % (
234 value, AX_MODE_HEADER, value, AX_JS_FILE)))
235 return errs
236
dmazzoni832058d12016-08-31 21:55:31237def CheckChangeOnUpload(input_api, output_api):
Doug Turner63f3c7b2017-07-29 05:10:01238 errs = []
Doug Turner4430d79c2017-08-07 21:00:40239 for path in input_api.LocalPaths():
240 path = path.replace('\\', '/')
Dominic Mazzonidcef1b732018-01-26 17:57:04241 if AX_MOJOM == path:
Doug Turner4430d79c2017-08-07 21:00:40242 errs.extend(CheckEnumsMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01243
Doug Turner4430d79c2017-08-07 21:00:40244 if AX_MODE_HEADER == path:
245 errs.extend(CheckModesMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01246
247 return errs
dmazzoni832058d12016-08-31 21:55:31248
249def CheckChangeOnCommit(input_api, output_api):
Doug Turner63f3c7b2017-07-29 05:10:01250 errs = []
Doug Turner4430d79c2017-08-07 21:00:40251 for path in input_api.LocalPaths():
252 path = path.replace('\\', '/')
Dominic Mazzonidcef1b732018-01-26 17:57:04253 if AX_MOJOM == path:
Doug Turner4430d79c2017-08-07 21:00:40254 errs.extend(CheckEnumsMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01255
Doug Turner4430d79c2017-08-07 21:00:40256 if AX_MODE_HEADER == path:
257 errs.extend(CheckModesMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01258
259 return errs