1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/config/check_spidermonkey_style.py Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,588 @@ 1.4 +# vim: set ts=8 sts=4 et sw=4 tw=99: 1.5 +# This Source Code Form is subject to the terms of the Mozilla Public 1.6 +# License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 +# file, You can obtain one at http://mozilla.org/MPL/2.0/. 1.8 + 1.9 +#---------------------------------------------------------------------------- 1.10 +# This script checks various aspects of SpiderMonkey code style. The current checks are as 1.11 +# follows. 1.12 +# 1.13 +# We check the following things in headers. 1.14 +# 1.15 +# - No cyclic dependencies. 1.16 +# 1.17 +# - No normal header should #include a inlines.h/-inl.h file. 1.18 +# 1.19 +# - #ifndef wrappers should have the right form. (XXX: not yet implemented) 1.20 +# - Every header file should have one. 1.21 +# - The guard name used should be appropriate for the filename. 1.22 +# 1.23 +# We check the following things in all files. 1.24 +# 1.25 +# - #includes should have full paths, e.g. "jit/Ion.h", not "Ion.h". 1.26 +# 1.27 +# - #includes should use the appropriate form for system headers (<...>) and 1.28 +# local headers ("..."). 1.29 +# 1.30 +# - #includes should be ordered correctly. 1.31 +# - Each one should be in the correct section. 1.32 +# - Alphabetical order should be used within sections. 1.33 +# - Sections should be in the right order. 1.34 +# Note that the presence of #if/#endif blocks complicates things, to the 1.35 +# point that it's not always clear where a conditionally-compiled #include 1.36 +# statement should go, even to a human. Therefore, we check the #include 1.37 +# statements within each #if/#endif block (including nested ones) in 1.38 +# isolation, but don't try to do any order checking between such blocks. 1.39 +#---------------------------------------------------------------------------- 1.40 + 1.41 +from __future__ import print_function 1.42 + 1.43 +import difflib 1.44 +import os 1.45 +import re 1.46 +import subprocess 1.47 +import sys 1.48 +import traceback 1.49 + 1.50 +# We don't bother checking files in these directories, because they're (a) auxiliary or (b) 1.51 +# imported code that doesn't follow our coding style. 1.52 +ignored_js_src_dirs = [ 1.53 + 'js/src/config/', # auxiliary stuff 1.54 + 'js/src/ctypes/libffi/', # imported code 1.55 + 'js/src/devtools/', # auxiliary stuff 1.56 + 'js/src/editline/', # imported code 1.57 + 'js/src/gdb/', # auxiliary stuff 1.58 + 'js/src/vtune/' # imported code 1.59 +] 1.60 + 1.61 +# We ignore #includes of these files, because they don't follow the usual rules. 1.62 +included_inclnames_to_ignore = set([ 1.63 + 'ffi.h', # generated in ctypes/libffi/ 1.64 + 'devtools/sharkctl.h', # we ignore devtools/ in general 1.65 + 'devtools/Instruments.h', # we ignore devtools/ in general 1.66 + 'double-conversion.h', # strange MFBT case 1.67 + 'javascript-trace.h', # generated in $OBJDIR if HAVE_DTRACE is defined 1.68 + 'jsautokw.h', # generated in $OBJDIR 1.69 + 'jscustomallocator.h', # provided by embedders; allowed to be missing 1.70 + 'js-config.h', # generated in $OBJDIR 1.71 + 'pratom.h', # NSPR 1.72 + 'prcvar.h', # NSPR 1.73 + 'prinit.h', # NSPR 1.74 + 'prlink.h', # NSPR 1.75 + 'prlock.h', # NSPR 1.76 + 'prprf.h', # NSPR 1.77 + 'prthread.h', # NSPR 1.78 + 'prtypes.h', # NSPR 1.79 + 'selfhosted.out.h', # generated in $OBJDIR 1.80 + 'unicode/locid.h', # ICU 1.81 + 'unicode/numsys.h', # ICU 1.82 + 'unicode/ucal.h', # ICU 1.83 + 'unicode/uclean.h', # ICU 1.84 + 'unicode/ucol.h', # ICU 1.85 + 'unicode/udat.h', # ICU 1.86 + 'unicode/udatpg.h', # ICU 1.87 + 'unicode/uenum.h', # ICU 1.88 + 'unicode/unorm.h', # ICU 1.89 + 'unicode/unum.h', # ICU 1.90 + 'unicode/ustring.h', # ICU 1.91 + 'unicode/utypes.h', # ICU 1.92 + 'vtune/VTuneWrapper.h' # VTune 1.93 +]) 1.94 + 1.95 +# These files have additional constraints on where they are #included, so we 1.96 +# ignore #includes of them when checking #include ordering. 1.97 +oddly_ordered_inclnames = set([ 1.98 + 'ctypes/typedefs.h', # Included multiple times in the body of ctypes/CTypes.h 1.99 + 'jsautokw.h', # Included in the body of frontend/TokenStream.h 1.100 + 'jswin.h', # Must be #included before <psapi.h> 1.101 + 'machine/endian.h', # Must be included after <sys/types.h> on BSD 1.102 + 'winbase.h', # Must precede other system headers(?) 1.103 + 'windef.h' # Must precede other system headers(?) 1.104 +]) 1.105 + 1.106 +# The files in tests/style/ contain code that fails this checking in various 1.107 +# ways. Here is the output we expect. If the actual output differs from 1.108 +# this, one of the following must have happened. 1.109 +# - New SpiderMonkey code violates one of the checked rules. 1.110 +# - The tests/style/ files have changed without expected_output being changed 1.111 +# accordingly. 1.112 +# - This script has been broken somehow. 1.113 +# 1.114 +expected_output = '''\ 1.115 +js/src/tests/style/BadIncludes2.h:1: error: 1.116 + vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h" 1.117 + 1.118 +js/src/tests/style/BadIncludes.h:3: error: 1.119 + the file includes itself 1.120 + 1.121 +js/src/tests/style/BadIncludes.h:6: error: 1.122 + "BadIncludes2.h" is included using the wrong path; 1.123 + did you forget a prefix, or is the file not yet committed? 1.124 + 1.125 +js/src/tests/style/BadIncludes.h:8: error: 1.126 + <tests/style/BadIncludes2.h> should be included using 1.127 + the #include "..." form 1.128 + 1.129 +js/src/tests/style/BadIncludes.h:10: error: 1.130 + "stdio.h" is included using the wrong path; 1.131 + did you forget a prefix, or is the file not yet committed? 1.132 + 1.133 +js/src/tests/style/BadIncludesOrder-inl.h:5:6: error: 1.134 + "vm/Interpreter-inl.h" should be included after "jsscriptinlines.h" 1.135 + 1.136 +js/src/tests/style/BadIncludesOrder-inl.h:6:7: error: 1.137 + "jsscriptinlines.h" should be included after "js/Value.h" 1.138 + 1.139 +js/src/tests/style/BadIncludesOrder-inl.h:7:8: error: 1.140 + "js/Value.h" should be included after "ds/LifoAlloc.h" 1.141 + 1.142 +js/src/tests/style/BadIncludesOrder-inl.h:8:9: error: 1.143 + "ds/LifoAlloc.h" should be included after "jsapi.h" 1.144 + 1.145 +js/src/tests/style/BadIncludesOrder-inl.h:9:10: error: 1.146 + "jsapi.h" should be included after <stdio.h> 1.147 + 1.148 +js/src/tests/style/BadIncludesOrder-inl.h:10:11: error: 1.149 + <stdio.h> should be included after "mozilla/HashFunctions.h" 1.150 + 1.151 +js/src/tests/style/BadIncludesOrder-inl.h:27:28: error: 1.152 + "jsobj.h" should be included after "jsfun.h" 1.153 + 1.154 +(multiple files): error: 1.155 + header files form one or more cycles 1.156 + 1.157 + tests/style/HeaderCycleA1.h 1.158 + -> tests/style/HeaderCycleA2.h 1.159 + -> tests/style/HeaderCycleA3.h 1.160 + -> tests/style/HeaderCycleA1.h 1.161 + 1.162 + tests/style/HeaderCycleB1-inl.h 1.163 + -> tests/style/HeaderCycleB2-inl.h 1.164 + -> tests/style/HeaderCycleB3-inl.h 1.165 + -> tests/style/HeaderCycleB4-inl.h 1.166 + -> tests/style/HeaderCycleB1-inl.h 1.167 + -> tests/style/jsheadercycleB5inlines.h 1.168 + -> tests/style/HeaderCycleB1-inl.h 1.169 + -> tests/style/HeaderCycleB4-inl.h 1.170 + 1.171 +'''.splitlines(True) 1.172 + 1.173 +actual_output = [] 1.174 + 1.175 + 1.176 +def out(*lines): 1.177 + for line in lines: 1.178 + actual_output.append(line + '\n') 1.179 + 1.180 + 1.181 +def error(filename, linenum, *lines): 1.182 + location = filename 1.183 + if linenum is not None: 1.184 + location += ':' + str(linenum) 1.185 + out(location + ': error:') 1.186 + for line in (lines): 1.187 + out(' ' + line) 1.188 + out('') 1.189 + 1.190 + 1.191 +class FileKind(object): 1.192 + C = 1 1.193 + CPP = 2 1.194 + INL_H = 3 1.195 + H = 4 1.196 + TBL = 5 1.197 + MSG = 6 1.198 + 1.199 + @staticmethod 1.200 + def get(filename): 1.201 + if filename.endswith('.c'): 1.202 + return FileKind.C 1.203 + 1.204 + if filename.endswith('.cpp'): 1.205 + return FileKind.CPP 1.206 + 1.207 + if filename.endswith(('inlines.h', '-inl.h')): 1.208 + return FileKind.INL_H 1.209 + 1.210 + if filename.endswith('.h'): 1.211 + return FileKind.H 1.212 + 1.213 + if filename.endswith('.tbl'): 1.214 + return FileKind.TBL 1.215 + 1.216 + if filename.endswith('.msg'): 1.217 + return FileKind.MSG 1.218 + 1.219 + error(filename, None, 'unknown file kind') 1.220 + 1.221 + 1.222 +def get_all_filenames(): 1.223 + '''Get a list of all the files in the (Mercurial or Git) repository.''' 1.224 + cmds = [['hg', 'manifest', '-q'], ['git', 'ls-files', '--full-name', '../..']] 1.225 + for cmd in cmds: 1.226 + try: 1.227 + all_filenames = subprocess.check_output(cmd, universal_newlines=True, 1.228 + stderr=subprocess.PIPE).split('\n') 1.229 + return all_filenames 1.230 + except: 1.231 + continue 1.232 + else: 1.233 + raise Exception('failed to run any of the repo manifest commands', cmds) 1.234 + 1.235 + 1.236 +def check_style(): 1.237 + # We deal with two kinds of name. 1.238 + # - A "filename" is a full path to a file from the repository root. 1.239 + # - An "inclname" is how a file is referred to in a #include statement. 1.240 + # 1.241 + # Examples (filename -> inclname) 1.242 + # - "mfbt/Attributes.h" -> "mozilla/Attributes.h" 1.243 + # - "js/public/Vector.h" -> "js/Vector.h" 1.244 + # - "js/src/vm/String.h" -> "vm/String.h" 1.245 + 1.246 + mfbt_inclnames = set() # type: set(inclname) 1.247 + js_names = dict() # type: dict(filename, inclname) 1.248 + 1.249 + # Select the appropriate files. 1.250 + for filename in get_all_filenames(): 1.251 + if filename.startswith('mfbt/') and filename.endswith('.h'): 1.252 + inclname = 'mozilla/' + filename[len('mfbt/'):] 1.253 + mfbt_inclnames.add(inclname) 1.254 + 1.255 + if filename.startswith('js/public/') and filename.endswith('.h'): 1.256 + inclname = 'js/' + filename[len('js/public/'):] 1.257 + js_names[filename] = inclname 1.258 + 1.259 + if filename.startswith('js/src/') and \ 1.260 + not filename.startswith(tuple(ignored_js_src_dirs)) and \ 1.261 + filename.endswith(('.c', '.cpp', '.h', '.tbl', '.msg')): 1.262 + inclname = filename[len('js/src/'):] 1.263 + js_names[filename] = inclname 1.264 + 1.265 + all_inclnames = mfbt_inclnames | set(js_names.values()) 1.266 + 1.267 + edges = dict() # type: dict(inclname, set(inclname)) 1.268 + 1.269 + # We don't care what's inside the MFBT files, but because they are 1.270 + # #included from JS files we have to add them to the inclusion graph. 1.271 + for inclname in mfbt_inclnames: 1.272 + edges[inclname] = set() 1.273 + 1.274 + # Process all the JS files. 1.275 + for filename in js_names.keys(): 1.276 + inclname = js_names[filename] 1.277 + file_kind = FileKind.get(filename) 1.278 + if file_kind == FileKind.C or file_kind == FileKind.CPP or \ 1.279 + file_kind == FileKind.H or file_kind == FileKind.INL_H: 1.280 + included_h_inclnames = set() # type: set(inclname) 1.281 + 1.282 + # This script is run in js/src/, so prepend '../../' to get to the root of the Mozilla 1.283 + # source tree. 1.284 + with open(os.path.join('../..', filename)) as f: 1.285 + do_file(filename, inclname, file_kind, f, all_inclnames, included_h_inclnames) 1.286 + 1.287 + edges[inclname] = included_h_inclnames 1.288 + 1.289 + find_cycles(all_inclnames, edges) 1.290 + 1.291 + # Compare expected and actual output. 1.292 + difflines = difflib.unified_diff(expected_output, actual_output, 1.293 + fromfile='check_spider_monkey_style.py expected output', 1.294 + tofile='check_spider_monkey_style.py actual output') 1.295 + ok = True 1.296 + for diffline in difflines: 1.297 + ok = False 1.298 + print(diffline, end='') 1.299 + 1.300 + return ok 1.301 + 1.302 + 1.303 +def module_name(name): 1.304 + '''Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename.''' 1.305 + 1.306 + return name.replace('inlines.h', '').replace('-inl.h', '').replace('.h', '').replace('.cpp', '') 1.307 + 1.308 + 1.309 +def is_module_header(enclosing_inclname, header_inclname): 1.310 + '''Determine if an included name is the "module header", i.e. should be 1.311 + first in the file.''' 1.312 + 1.313 + module = module_name(enclosing_inclname) 1.314 + 1.315 + # Normal case, e.g. module == "foo/Bar", header_inclname == "foo/Bar.h". 1.316 + if module == module_name(header_inclname): 1.317 + return True 1.318 + 1.319 + # A public header, e.g. module == "foo/Bar", header_inclname == "js/Bar.h". 1.320 + m = re.match(r'js\/(.*)\.h', header_inclname) 1.321 + if m is not None and module.endswith('/' + m.group(1)): 1.322 + return True 1.323 + 1.324 + return False 1.325 + 1.326 + 1.327 +class Include(object): 1.328 + '''Important information for a single #include statement.''' 1.329 + 1.330 + def __init__(self, inclname, linenum, is_system): 1.331 + self.inclname = inclname 1.332 + self.linenum = linenum 1.333 + self.is_system = is_system 1.334 + 1.335 + def isLeaf(self): 1.336 + return True 1.337 + 1.338 + def section(self, enclosing_inclname): 1.339 + '''Identify which section inclname belongs to. 1.340 + 1.341 + The section numbers are as follows. 1.342 + 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp) 1.343 + 1. mozilla/Foo.h 1.344 + 2. <foo.h> or <foo> 1.345 + 3. jsfoo.h, prmjtime.h, etc 1.346 + 4. foo/Bar.h 1.347 + 5. jsfooinlines.h 1.348 + 6. foo/Bar-inl.h 1.349 + 7. non-.h, e.g. *.tbl, *.msg 1.350 + ''' 1.351 + 1.352 + if self.is_system: 1.353 + return 2 1.354 + 1.355 + if not self.inclname.endswith('.h'): 1.356 + return 7 1.357 + 1.358 + # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need 1.359 + # special handling. 1.360 + if is_module_header(enclosing_inclname, self.inclname): 1.361 + return 0 1.362 + 1.363 + if '/' in self.inclname: 1.364 + if self.inclname.startswith('mozilla/'): 1.365 + return 1 1.366 + 1.367 + if self.inclname.endswith('-inl.h'): 1.368 + return 6 1.369 + 1.370 + return 4 1.371 + 1.372 + if self.inclname.endswith('inlines.h'): 1.373 + return 5 1.374 + 1.375 + return 3 1.376 + 1.377 + def quote(self): 1.378 + if self.is_system: 1.379 + return '<' + self.inclname + '>' 1.380 + else: 1.381 + return '"' + self.inclname + '"' 1.382 + 1.383 + 1.384 +class HashIfBlock(object): 1.385 + '''Important information about a #if/#endif block. 1.386 + 1.387 + A #if/#endif block is the contents of a #if/#endif (or similar) section. 1.388 + The top-level block, which is not within a #if/#endif pair, is also 1.389 + considered a block. 1.390 + 1.391 + Each leaf is either an Include (representing a #include), or another 1.392 + nested HashIfBlock.''' 1.393 + def __init__(self): 1.394 + self.kids = [] 1.395 + 1.396 + def isLeaf(self): 1.397 + return False 1.398 + 1.399 + 1.400 +def do_file(filename, inclname, file_kind, f, all_inclnames, included_h_inclnames): 1.401 + block_stack = [HashIfBlock()] 1.402 + 1.403 + # Extract the #include statements as a tree of IBlocks and IIncludes. 1.404 + for linenum, line in enumerate(f, start=1): 1.405 + # Look for a |#include "..."| line. 1.406 + m = re.match(r'\s*#\s*include\s+"([^"]*)"', line) 1.407 + if m is not None: 1.408 + block_stack[-1].kids.append(Include(m.group(1), linenum, False)) 1.409 + 1.410 + # Look for a |#include <...>| line. 1.411 + m = re.match(r'\s*#\s*include\s+<([^>]*)>', line) 1.412 + if m is not None: 1.413 + block_stack[-1].kids.append(Include(m.group(1), linenum, True)) 1.414 + 1.415 + # Look for a |#{if,ifdef,ifndef}| line. 1.416 + m = re.match(r'\s*#\s*(if|ifdef|ifndef)\b', line) 1.417 + if m is not None: 1.418 + # Open a new block. 1.419 + new_block = HashIfBlock() 1.420 + block_stack[-1].kids.append(new_block) 1.421 + block_stack.append(new_block) 1.422 + 1.423 + # Look for a |#{elif,else}| line. 1.424 + m = re.match(r'\s*#\s*(elif|else)\b', line) 1.425 + if m is not None: 1.426 + # Close the current block, and open an adjacent one. 1.427 + block_stack.pop() 1.428 + new_block = HashIfBlock() 1.429 + block_stack[-1].kids.append(new_block) 1.430 + block_stack.append(new_block) 1.431 + 1.432 + # Look for a |#endif| line. 1.433 + m = re.match(r'\s*#\s*endif\b', line) 1.434 + if m is not None: 1.435 + # Close the current block. 1.436 + block_stack.pop() 1.437 + 1.438 + def check_include_statement(include): 1.439 + '''Check the style of a single #include statement.''' 1.440 + 1.441 + if include.is_system: 1.442 + # Check it is not a known local file (in which case it's probably a system header). 1.443 + if include.inclname in included_inclnames_to_ignore or \ 1.444 + include.inclname in all_inclnames: 1.445 + error(filename, include.linenum, 1.446 + include.quote() + ' should be included using', 1.447 + 'the #include "..." form') 1.448 + 1.449 + else: 1.450 + if include.inclname not in included_inclnames_to_ignore: 1.451 + included_kind = FileKind.get(include.inclname) 1.452 + 1.453 + # Check the #include path has the correct form. 1.454 + if include.inclname not in all_inclnames: 1.455 + error(filename, include.linenum, 1.456 + include.quote() + ' is included ' + 'using the wrong path;', 1.457 + 'did you forget a prefix, or is the file not yet committed?') 1.458 + 1.459 + # Record inclusions of .h files for cycle detection later. 1.460 + # (Exclude .tbl and .msg files.) 1.461 + elif included_kind == FileKind.H or included_kind == FileKind.INL_H: 1.462 + included_h_inclnames.add(include.inclname) 1.463 + 1.464 + # Check a H file doesn't #include an INL_H file. 1.465 + if file_kind == FileKind.H and included_kind == FileKind.INL_H: 1.466 + error(filename, include.linenum, 1.467 + 'vanilla header includes an inline-header file ' + include.quote()) 1.468 + 1.469 + # Check a file doesn't #include itself. (We do this here because the cycle 1.470 + # detection below doesn't detect this case.) 1.471 + if inclname == include.inclname: 1.472 + error(filename, include.linenum, 'the file includes itself') 1.473 + 1.474 + def check_includes_order(include1, include2): 1.475 + '''Check the ordering of two #include statements.''' 1.476 + 1.477 + if include1.inclname in oddly_ordered_inclnames or \ 1.478 + include2.inclname in oddly_ordered_inclnames: 1.479 + return 1.480 + 1.481 + section1 = include1.section(inclname) 1.482 + section2 = include2.section(inclname) 1.483 + if (section1 > section2) or \ 1.484 + ((section1 == section2) and (include1.inclname.lower() > include2.inclname.lower())): 1.485 + error(filename, str(include1.linenum) + ':' + str(include2.linenum), 1.486 + include1.quote() + ' should be included after ' + include2.quote()) 1.487 + 1.488 + # The #include statements in the files in assembler/ and yarr/ have all manner of implicit 1.489 + # ordering requirements. Boo. Ignore them. 1.490 + skip_order_checking = inclname.startswith(('assembler/', 'yarr/')) 1.491 + 1.492 + # Check the extracted #include statements, both individually, and the ordering of 1.493 + # adjacent pairs that live in the same block. 1.494 + def pair_traverse(prev, this): 1.495 + if this.isLeaf(): 1.496 + check_include_statement(this) 1.497 + if prev is not None and prev.isLeaf() and not skip_order_checking: 1.498 + check_includes_order(prev, this) 1.499 + else: 1.500 + for prev2, this2 in zip([None] + this.kids[0:-1], this.kids): 1.501 + pair_traverse(prev2, this2) 1.502 + 1.503 + pair_traverse(None, block_stack[-1]) 1.504 + 1.505 + 1.506 +def find_cycles(all_inclnames, edges): 1.507 + '''Find and draw any cycles.''' 1.508 + 1.509 + SCCs = tarjan(all_inclnames, edges) 1.510 + 1.511 + # The various sorted() calls below ensure the output is deterministic. 1.512 + 1.513 + def draw_SCC(c): 1.514 + cset = set(c) 1.515 + drawn = set() 1.516 + def draw(v, indent): 1.517 + out(' ' * indent + ('-> ' if indent else ' ') + v) 1.518 + if v in drawn: 1.519 + return 1.520 + drawn.add(v) 1.521 + for succ in sorted(edges[v]): 1.522 + if succ in cset: 1.523 + draw(succ, indent + 1) 1.524 + draw(sorted(c)[0], 0) 1.525 + out('') 1.526 + 1.527 + have_drawn_an_SCC = False 1.528 + for scc in sorted(SCCs): 1.529 + if len(scc) != 1: 1.530 + if not have_drawn_an_SCC: 1.531 + error('(multiple files)', None, 'header files form one or more cycles') 1.532 + have_drawn_an_SCC = True 1.533 + 1.534 + draw_SCC(scc) 1.535 + 1.536 + 1.537 +# Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph. 1.538 +# https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm 1.539 +def tarjan(V, E): 1.540 + vertex_index = {} 1.541 + vertex_lowlink = {} 1.542 + index = 0 1.543 + S = [] 1.544 + all_SCCs = [] 1.545 + 1.546 + def strongconnect(v, index): 1.547 + # Set the depth index for v to the smallest unused index 1.548 + vertex_index[v] = index 1.549 + vertex_lowlink[v] = index 1.550 + index += 1 1.551 + S.append(v) 1.552 + 1.553 + # Consider successors of v 1.554 + for w in E[v]: 1.555 + if w not in vertex_index: 1.556 + # Successor w has not yet been visited; recurse on it 1.557 + index = strongconnect(w, index) 1.558 + vertex_lowlink[v] = min(vertex_lowlink[v], vertex_lowlink[w]) 1.559 + elif w in S: 1.560 + # Successor w is in stack S and hence in the current SCC 1.561 + vertex_lowlink[v] = min(vertex_lowlink[v], vertex_index[w]) 1.562 + 1.563 + # If v is a root node, pop the stack and generate an SCC 1.564 + if vertex_lowlink[v] == vertex_index[v]: 1.565 + i = S.index(v) 1.566 + scc = S[i:] 1.567 + del S[i:] 1.568 + all_SCCs.append(scc) 1.569 + 1.570 + return index 1.571 + 1.572 + for v in V: 1.573 + if v not in vertex_index: 1.574 + index = strongconnect(v, index) 1.575 + 1.576 + return all_SCCs 1.577 + 1.578 + 1.579 +def main(): 1.580 + ok = check_style() 1.581 + 1.582 + if ok: 1.583 + print('TEST-PASS | check_spidermonkey_style.py | ok') 1.584 + else: 1.585 + print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | actual output does not match expected output; diff is above') 1.586 + 1.587 + sys.exit(0 if ok else 1) 1.588 + 1.589 + 1.590 +if __name__ == '__main__': 1.591 + main()