blob: f953d4f98a6351010cd27aecfc1c5238bab9b592 [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
7import os, re
8
9AX_IDL = 'ui/accessibility/ax_enums.idl'
10AUTOMATION_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
19# Given a full path to an IDL file containing enum definitions,
20# parse the file for enums and return a dict mapping the enum name
21# to a list of values for that enum.
22def GetEnumsFromFile(fullpath):
23 enum_name = None
24 enums = {}
25 for line in open(fullpath).readlines():
26 # Strip out comments
27 line = re.sub('//.*', '', line)
28
29 # Look for lines of the form "enum ENUM_NAME {" and get the enum_name
30 m = re.search('enum ([\w]+) {', line)
31 if m:
32 enum_name = m.group(1)
33 continue
34
35 # Look for a "}" character signifying the end of an enum
36 if line.find('}') >= 0:
37 enum_name = None
38 continue
39
40 if not enum_name:
41 continue
42
43 # If we're inside an enum definition, add the first string consisting of
44 # alphanumerics plus underscore ("\w") to the list of values for that enum.
45 m = re.search('([\w]+)', line)
46 if m:
47 enums.setdefault(enum_name, [])
48 enums[enum_name].append(m.group(1))
49
50 return enums
51
52def CheckMatchingEnum(ax_enums,
53 ax_enum_name,
54 automation_enums,
55 automation_enum_name,
56 errs,
57 output_api):
58 if ax_enum_name not in ax_enums:
59 errs.append(output_api.PresubmitError(
60 'Expected %s to have an enum named %s' % (AX_IDL, ax_enum_name)))
61 return
62 if automation_enum_name not in automation_enums:
63 errs.append(output_api.PresubmitError(
64 'Expected %s to have an enum named %s' % (
65 AUTOMATION_IDL, automation_enum_name)))
66 return
67 src = ax_enums[ax_enum_name]
68 dst = automation_enums[automation_enum_name]
69 for value in src:
Aaron Leventhal3b8e2582017-07-29 18:59:5170 lower_value = InitialLowerCamelCase(value)
71 if lower_value in dst:
72 dst.remove(lower_value) # Any remaining at end are extra and a mismatch.
73 else:
dmazzoni832058d12016-08-31 21:55:3174 errs.append(output_api.PresubmitError(
75 'Found %s.%s in %s, but did not find %s.%s in %s' % (
76 ax_enum_name, value, AX_IDL,
77 automation_enum_name, InitialLowerCamelCase(value),
78 AUTOMATION_IDL)))
Aaron Leventhal3b8e2582017-07-29 18:59:5179 # Should be no remaining items
80 for value in dst:
81 errs.append(output_api.PresubmitError(
82 'Found %s.%s in %s, but did not find %s.%s in %s' % (
83 automation_enum_name, value, AUTOMATION_IDL,
84 ax_enum_name, InitialLowerCamelCase(value),
85 AX_IDL)))
dmazzoni832058d12016-08-31 21:55:3186
87def CheckEnumsMatch(input_api, output_api):
88 repo_root = input_api.change.RepositoryRoot()
89 ax_enums = GetEnumsFromFile(os.path.join(repo_root, AX_IDL))
90 automation_enums = GetEnumsFromFile(os.path.join(repo_root, AUTOMATION_IDL))
David Tseng4a25f752017-08-22 19:56:1191
92 # Focused state only exists in automation.
93 automation_enums['StateType'].remove('focused')
Katie Dektarbd851852017-09-29 04:44:2594 # Offscreen state only exists in automation.
95 automation_enums['StateType'].remove('offscreen')
David Tseng4a25f752017-08-22 19:56:1196
dmazzoni832058d12016-08-31 21:55:3197 errs = []
98 CheckMatchingEnum(ax_enums, 'AXRole', automation_enums, 'RoleType', errs,
99 output_api)
100 CheckMatchingEnum(ax_enums, 'AXState', automation_enums, 'StateType', errs,
101 output_api)
102 CheckMatchingEnum(ax_enums, 'AXEvent', automation_enums, 'EventType', errs,
103 output_api)
dmazzoni59642422017-01-25 23:17:46104 CheckMatchingEnum(ax_enums, 'AXNameFrom', automation_enums, 'NameFromType',
105 errs, output_api)
Aaron Leventhalf36960a2017-07-19 13:07:46106 CheckMatchingEnum(ax_enums, 'AXRestriction', automation_enums,
107 'Restriction', errs, output_api)
dmazzoni832058d12016-08-31 21:55:31108 return errs
109
Doug Turner63f3c7b2017-07-29 05:10:01110# Given a full path to c++ header, return an array of the first static
111# constexpr defined. (Note there can be more than one defined in a C++
112# header)
113def GetConstexprFromFile(fullpath):
114 values = []
115 for line in open(fullpath).readlines():
116 # Strip out comments
117 line = re.sub('//.*', '', line)
118
119 # Look for lines of the form "static constexpr <type> NAME "
120 m = re.search('static constexpr [\w]+ ([\w]+)', line)
121 if m:
122 values.append(m.group(1))
123
124 return values
125
126# Given a full path to js file, return the AXMode consts
127# defined
128def GetAccessibilityModesFromFile(fullpath):
129 values = []
130 inside = False
131 for line in open(fullpath).readlines():
132 # Strip out comments
133 line = re.sub('//.*', '', line)
134
135 # Look for the block of code that defines AXMode
136 m = re.search('const AXMode = {', line)
137 if m:
138 inside = True
139 continue
140
141 # Look for a "}" character signifying the end of an enum
142 if line.find('};') >= 0:
143 return values
144
145 if not inside:
146 continue
147
148 m = re.search('([\w]+):', line)
149 if m:
150 values.append(m.group(1))
151 continue
152
153 # getters
154 m = re.search('get ([\w]+)\(\)', line)
155 if m:
156 values.append(m.group(1))
157 return values
158
159# Make sure that the modes defined in the C++ header match those defined in
160# the js file. Note that this doesn't guarantee that the values are the same,
161# but does make sure if we add or remove we can signal to the developer that
162# they should be aware that this dependency exists.
163def CheckModesMatch(input_api, output_api):
164 errs = []
165 repo_root = input_api.change.RepositoryRoot()
166
167 ax_modes_in_header = GetConstexprFromFile(
168 os.path.join(repo_root,AX_MODE_HEADER))
169 ax_modes_in_js = GetAccessibilityModesFromFile(
170 os.path.join(repo_root, AX_JS_FILE))
171
172 for value in ax_modes_in_header:
173 if value not in ax_modes_in_js:
174 errs.append(output_api.PresubmitError(
175 'Found %s in %s, but did not find %s in %s' % (
176 value, AX_MODE_HEADER, value, AX_JS_FILE)))
177 return errs
178
dmazzoni832058d12016-08-31 21:55:31179def CheckChangeOnUpload(input_api, output_api):
Doug Turner63f3c7b2017-07-29 05:10:01180 errs = []
Doug Turner4430d79c2017-08-07 21:00:40181 for path in input_api.LocalPaths():
182 path = path.replace('\\', '/')
183 if AX_IDL == path:
184 errs.extend(CheckEnumsMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01185
Doug Turner4430d79c2017-08-07 21:00:40186 if AX_MODE_HEADER == path:
187 errs.extend(CheckModesMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01188
189 return errs
dmazzoni832058d12016-08-31 21:55:31190
191def CheckChangeOnCommit(input_api, output_api):
Doug Turner63f3c7b2017-07-29 05:10:01192 errs = []
Doug Turner4430d79c2017-08-07 21:00:40193 for path in input_api.LocalPaths():
194 path = path.replace('\\', '/')
195 if AX_IDL == path:
196 errs.extend(CheckEnumsMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01197
Doug Turner4430d79c2017-08-07 21:00:40198 if AX_MODE_HEADER == path:
199 errs.extend(CheckModesMatch(input_api, output_api))
Doug Turner63f3c7b2017-07-29 05:10:01200
201 return errs