|  | # vim: set ts=8 sts=4 et sw=4 tw=99: | 
|  | # This Source Code Form is subject to the terms of the Mozilla Public | 
|  | # License, v. 2.0. If a copy of the MPL was not distributed with this | 
|  | # file, You can obtain one at http://mozilla.org/MPL/2.0/. | 
|  |  | 
|  | #---------------------------------------------------------------------------- | 
|  | # This script checks various aspects of SpiderMonkey code style.  The current checks are as | 
|  | # follows. | 
|  | # | 
|  | # We check the following things in headers. | 
|  | # | 
|  | # - No cyclic dependencies. | 
|  | # | 
|  | # - No normal header should #include a inlines.h/-inl.h file. | 
|  | # | 
|  | # - #ifndef wrappers should have the right form. (XXX: not yet implemented) | 
|  | #   - Every header file should have one. | 
|  | #   - The guard name used should be appropriate for the filename. | 
|  | # | 
|  | # We check the following things in all files. | 
|  | # | 
|  | # - #includes should have full paths, e.g. "jit/Ion.h", not "Ion.h". | 
|  | # | 
|  | # - #includes should use the appropriate form for system headers (<...>) and | 
|  | #   local headers ("..."). | 
|  | # | 
|  | # - #includes should be ordered correctly. | 
|  | #   - Each one should be in the correct section. | 
|  | #   - Alphabetical order should be used within sections. | 
|  | #   - Sections should be in the right order. | 
|  | #   Note that the presence of #if/#endif blocks complicates things, to the | 
|  | #   point that it's not always clear where a conditionally-compiled #include | 
|  | #   statement should go, even to a human.  Therefore, we check the #include | 
|  | #   statements within each #if/#endif block (including nested ones) in | 
|  | #   isolation, but don't try to do any order checking between such blocks. | 
|  | #---------------------------------------------------------------------------- | 
|  |  | 
|  | from __future__ import print_function | 
|  |  | 
|  | import difflib | 
|  | import os | 
|  | import re | 
|  | import subprocess | 
|  | import sys | 
|  | import traceback | 
|  | from check_utils import get_all_toplevel_filenames | 
|  |  | 
|  | # We don't bother checking files in these directories, because they're (a) auxiliary or (b) | 
|  | # imported code that doesn't follow our coding style. | 
|  | ignored_js_src_dirs = [ | 
|  | 'js/src/config/',            # auxiliary stuff | 
|  | 'js/src/ctypes/libffi/',     # imported code | 
|  | 'js/src/devtools/',          # auxiliary stuff | 
|  | 'js/src/editline/',          # imported code | 
|  | 'js/src/gdb/',               # auxiliary stuff | 
|  | 'js/src/vtune/'              # imported code | 
|  | ] | 
|  |  | 
|  | # We ignore #includes of these files, because they don't follow the usual rules. | 
|  | included_inclnames_to_ignore = set([ | 
|  | 'ffi.h',                    # generated in ctypes/libffi/ | 
|  | 'devtools/sharkctl.h',      # we ignore devtools/ in general | 
|  | 'devtools/Instruments.h',   # we ignore devtools/ in general | 
|  | 'double-conversion.h',      # strange MFBT case | 
|  | 'javascript-trace.h',       # generated in $OBJDIR if HAVE_DTRACE is defined | 
|  | 'jsautokw.h',               # generated in $OBJDIR | 
|  | 'jscustomallocator.h',      # provided by embedders;  allowed to be missing | 
|  | 'js-config.h',              # generated in $OBJDIR | 
|  | 'pratom.h',                 # NSPR | 
|  | 'prcvar.h',                 # NSPR | 
|  | 'prerror.h',                # NSPR | 
|  | 'prinit.h',                 # NSPR | 
|  | 'prlink.h',                 # NSPR | 
|  | 'prlock.h',                 # NSPR | 
|  | 'prprf.h',                  # NSPR | 
|  | 'prthread.h',               # NSPR | 
|  | 'prtypes.h',                # NSPR | 
|  | 'selfhosted.out.h',         # generated in $OBJDIR | 
|  | 'shellmoduleloader.out.h',  # generated in $OBJDIR | 
|  | 'unicode/locid.h',          # ICU | 
|  | 'unicode/numsys.h',         # ICU | 
|  | 'unicode/timezone.h',       # ICU | 
|  | 'unicode/ucal.h',           # ICU | 
|  | 'unicode/uclean.h',         # ICU | 
|  | 'unicode/ucol.h',           # ICU | 
|  | 'unicode/udat.h',           # ICU | 
|  | 'unicode/udatpg.h',         # ICU | 
|  | 'unicode/uenum.h',          # ICU | 
|  | 'unicode/unorm.h',          # ICU | 
|  | 'unicode/unum.h',           # ICU | 
|  | 'unicode/ustring.h',        # ICU | 
|  | 'unicode/utypes.h',         # ICU | 
|  | 'vtune/VTuneWrapper.h'      # VTune | 
|  | ]) | 
|  |  | 
|  | # These files have additional constraints on where they are #included, so we | 
|  | # ignore #includes of them when checking #include ordering. | 
|  | oddly_ordered_inclnames = set([ | 
|  | 'ctypes/typedefs.h',        # Included multiple times in the body of ctypes/CTypes.h | 
|  | 'jsautokw.h',               # Included in the body of frontend/TokenStream.h | 
|  | 'jswin.h',                  # Must be #included before <psapi.h> | 
|  | 'machine/endian.h',         # Must be included after <sys/types.h> on BSD | 
|  | 'winbase.h',                # Must precede other system headers(?) | 
|  | 'windef.h'                  # Must precede other system headers(?) | 
|  | ]) | 
|  |  | 
|  | # The files in tests/style/ contain code that fails this checking in various | 
|  | # ways.  Here is the output we expect.  If the actual output differs from | 
|  | # this, one of the following must have happened. | 
|  | # - New SpiderMonkey code violates one of the checked rules. | 
|  | # - The tests/style/ files have changed without expected_output being changed | 
|  | #   accordingly. | 
|  | # - This script has been broken somehow. | 
|  | # | 
|  | expected_output = '''\ | 
|  | js/src/tests/style/BadIncludes2.h:1: error: | 
|  | vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h" | 
|  |  | 
|  | js/src/tests/style/BadIncludes.h:3: error: | 
|  | the file includes itself | 
|  |  | 
|  | js/src/tests/style/BadIncludes.h:6: error: | 
|  | "BadIncludes2.h" is included using the wrong path; | 
|  | did you forget a prefix, or is the file not yet committed? | 
|  |  | 
|  | js/src/tests/style/BadIncludes.h:8: error: | 
|  | <tests/style/BadIncludes2.h> should be included using | 
|  | the #include "..." form | 
|  |  | 
|  | js/src/tests/style/BadIncludes.h:10: error: | 
|  | "stdio.h" is included using the wrong path; | 
|  | did you forget a prefix, or is the file not yet committed? | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:5:6: error: | 
|  | "vm/Interpreter-inl.h" should be included after "jsscriptinlines.h" | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:6:7: error: | 
|  | "jsscriptinlines.h" should be included after "js/Value.h" | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:7:8: error: | 
|  | "js/Value.h" should be included after "ds/LifoAlloc.h" | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:8:9: error: | 
|  | "ds/LifoAlloc.h" should be included after "jsapi.h" | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:9:10: error: | 
|  | "jsapi.h" should be included after <stdio.h> | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:10:11: error: | 
|  | <stdio.h> should be included after "mozilla/HashFunctions.h" | 
|  |  | 
|  | js/src/tests/style/BadIncludesOrder-inl.h:27:28: error: | 
|  | "jsobj.h" should be included after "jsfun.h" | 
|  |  | 
|  | (multiple files): error: | 
|  | header files form one or more cycles | 
|  |  | 
|  | tests/style/HeaderCycleA1.h | 
|  | -> tests/style/HeaderCycleA2.h | 
|  | -> tests/style/HeaderCycleA3.h | 
|  | -> tests/style/HeaderCycleA1.h | 
|  |  | 
|  | tests/style/HeaderCycleB1-inl.h | 
|  | -> tests/style/HeaderCycleB2-inl.h | 
|  | -> tests/style/HeaderCycleB3-inl.h | 
|  | -> tests/style/HeaderCycleB4-inl.h | 
|  | -> tests/style/HeaderCycleB1-inl.h | 
|  | -> tests/style/jsheadercycleB5inlines.h | 
|  | -> tests/style/HeaderCycleB1-inl.h | 
|  | -> tests/style/HeaderCycleB4-inl.h | 
|  |  | 
|  | '''.splitlines(True) | 
|  |  | 
|  | actual_output = [] | 
|  |  | 
|  |  | 
|  | def out(*lines): | 
|  | for line in lines: | 
|  | actual_output.append(line + '\n') | 
|  |  | 
|  |  | 
|  | def error(filename, linenum, *lines): | 
|  | location = filename | 
|  | if linenum is not None: | 
|  | location += ':' + str(linenum) | 
|  | out(location + ': error:') | 
|  | for line in (lines): | 
|  | out('    ' + line) | 
|  | out('') | 
|  |  | 
|  |  | 
|  | class FileKind(object): | 
|  | C = 1 | 
|  | CPP = 2 | 
|  | INL_H = 3 | 
|  | H = 4 | 
|  | TBL = 5 | 
|  | MSG = 6 | 
|  |  | 
|  | @staticmethod | 
|  | def get(filename): | 
|  | if filename.endswith('.c'): | 
|  | return FileKind.C | 
|  |  | 
|  | if filename.endswith('.cpp'): | 
|  | return FileKind.CPP | 
|  |  | 
|  | if filename.endswith(('inlines.h', '-inl.h')): | 
|  | return FileKind.INL_H | 
|  |  | 
|  | if filename.endswith('.h'): | 
|  | return FileKind.H | 
|  |  | 
|  | if filename.endswith('.tbl'): | 
|  | return FileKind.TBL | 
|  |  | 
|  | if filename.endswith('.msg'): | 
|  | return FileKind.MSG | 
|  |  | 
|  | error(filename, None, 'unknown file kind') | 
|  |  | 
|  |  | 
|  | def check_style(): | 
|  | # We deal with two kinds of name. | 
|  | # - A "filename" is a full path to a file from the repository root. | 
|  | # - An "inclname" is how a file is referred to in a #include statement. | 
|  | # | 
|  | # Examples (filename -> inclname) | 
|  | # - "mfbt/Attributes.h"     -> "mozilla/Attributes.h" | 
|  | # - "mfbt/decimal/Decimal.h -> "mozilla/Decimal.h" | 
|  | # - "js/public/Vector.h"    -> "js/Vector.h" | 
|  | # - "js/src/vm/String.h"    -> "vm/String.h" | 
|  |  | 
|  | mfbt_inclnames = set()      # type: set(inclname) | 
|  | mozalloc_inclnames = set()  # type: set(inclname) | 
|  | js_names = dict()           # type: dict(filename, inclname) | 
|  |  | 
|  | # Select the appropriate files. | 
|  | for filename in get_all_toplevel_filenames(): | 
|  | if filename.startswith('mfbt/') and filename.endswith('.h'): | 
|  | inclname = 'mozilla/' + filename.split('/')[-1] | 
|  | mfbt_inclnames.add(inclname) | 
|  |  | 
|  | if filename.startswith('memory/mozalloc/') and filename.endswith('.h'): | 
|  | inclname = 'mozilla/' + filename.split('/')[-1] | 
|  | mozalloc_inclnames.add(inclname) | 
|  |  | 
|  | if filename.startswith('js/public/') and filename.endswith('.h'): | 
|  | inclname = 'js/' + filename[len('js/public/'):] | 
|  | js_names[filename] = inclname | 
|  |  | 
|  | if filename.startswith('js/src/') and \ | 
|  | not filename.startswith(tuple(ignored_js_src_dirs)) and \ | 
|  | filename.endswith(('.c', '.cpp', '.h', '.tbl', '.msg')): | 
|  | inclname = filename[len('js/src/'):] | 
|  | js_names[filename] = inclname | 
|  |  | 
|  | all_inclnames = mfbt_inclnames | mozalloc_inclnames | set(js_names.values()) | 
|  |  | 
|  | edges = dict()      # type: dict(inclname, set(inclname)) | 
|  |  | 
|  | # We don't care what's inside the MFBT and MOZALLOC files, but because they | 
|  | # are #included from JS files we have to add them to the inclusion graph. | 
|  | for inclname in mfbt_inclnames | mozalloc_inclnames: | 
|  | edges[inclname] = set() | 
|  |  | 
|  | # Process all the JS files. | 
|  | for filename in js_names.keys(): | 
|  | inclname = js_names[filename] | 
|  | file_kind = FileKind.get(filename) | 
|  | if file_kind == FileKind.C or file_kind == FileKind.CPP or \ | 
|  | file_kind == FileKind.H or file_kind == FileKind.INL_H: | 
|  | included_h_inclnames = set()    # type: set(inclname) | 
|  |  | 
|  | # This script is run in js/src/, so prepend '../../' to get to the root of the Mozilla | 
|  | # source tree. | 
|  | with open(os.path.join('../..', filename)) as f: | 
|  | do_file(filename, inclname, file_kind, f, all_inclnames, included_h_inclnames) | 
|  |  | 
|  | edges[inclname] = included_h_inclnames | 
|  |  | 
|  | find_cycles(all_inclnames, edges) | 
|  |  | 
|  | # Compare expected and actual output. | 
|  | difflines = difflib.unified_diff(expected_output, actual_output, | 
|  | fromfile='check_spider_monkey_style.py expected output', | 
|  | tofile='check_spider_monkey_style.py actual output') | 
|  | ok = True | 
|  | for diffline in difflines: | 
|  | ok = False | 
|  | print(diffline, end='') | 
|  |  | 
|  | return ok | 
|  |  | 
|  |  | 
|  | def module_name(name): | 
|  | '''Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename.''' | 
|  |  | 
|  | return name.replace('inlines.h', '').replace('-inl.h', '').replace('.h', '').replace('.cpp', '') | 
|  |  | 
|  |  | 
|  | def is_module_header(enclosing_inclname, header_inclname): | 
|  | '''Determine if an included name is the "module header", i.e. should be | 
|  | first in the file.''' | 
|  |  | 
|  | module = module_name(enclosing_inclname) | 
|  |  | 
|  | # Normal case, e.g. module == "foo/Bar", header_inclname == "foo/Bar.h". | 
|  | if module == module_name(header_inclname): | 
|  | return True | 
|  |  | 
|  | # A public header, e.g. module == "foo/Bar", header_inclname == "js/Bar.h". | 
|  | m = re.match(r'js\/(.*)\.h', header_inclname) | 
|  | if m is not None and module.endswith('/' + m.group(1)): | 
|  | return True | 
|  |  | 
|  | return False | 
|  |  | 
|  |  | 
|  | class Include(object): | 
|  | '''Important information for a single #include statement.''' | 
|  |  | 
|  | def __init__(self, inclname, linenum, is_system): | 
|  | self.inclname = inclname | 
|  | self.linenum = linenum | 
|  | self.is_system = is_system | 
|  |  | 
|  | def isLeaf(self): | 
|  | return True | 
|  |  | 
|  | def section(self, enclosing_inclname): | 
|  | '''Identify which section inclname belongs to. | 
|  |  | 
|  | The section numbers are as follows. | 
|  | 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp) | 
|  | 1. mozilla/Foo.h | 
|  | 2. <foo.h> or <foo> | 
|  | 3. jsfoo.h, prmjtime.h, etc | 
|  | 4. foo/Bar.h | 
|  | 5. jsfooinlines.h | 
|  | 6. foo/Bar-inl.h | 
|  | 7. non-.h, e.g. *.tbl, *.msg | 
|  | ''' | 
|  |  | 
|  | if self.is_system: | 
|  | return 2 | 
|  |  | 
|  | if not self.inclname.endswith('.h'): | 
|  | return 7 | 
|  |  | 
|  | # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need | 
|  | # special handling. | 
|  | if is_module_header(enclosing_inclname, self.inclname): | 
|  | return 0 | 
|  |  | 
|  | if '/' in self.inclname: | 
|  | if self.inclname.startswith('mozilla/'): | 
|  | return 1 | 
|  |  | 
|  | if self.inclname.endswith('-inl.h'): | 
|  | return 6 | 
|  |  | 
|  | return 4 | 
|  |  | 
|  | if self.inclname.endswith('inlines.h'): | 
|  | return 5 | 
|  |  | 
|  | return 3 | 
|  |  | 
|  | def quote(self): | 
|  | if self.is_system: | 
|  | return '<' + self.inclname + '>' | 
|  | else: | 
|  | return '"' + self.inclname + '"' | 
|  |  | 
|  |  | 
|  | class HashIfBlock(object): | 
|  | '''Important information about a #if/#endif block. | 
|  |  | 
|  | A #if/#endif block is the contents of a #if/#endif (or similar) section. | 
|  | The top-level block, which is not within a #if/#endif pair, is also | 
|  | considered a block. | 
|  |  | 
|  | Each leaf is either an Include (representing a #include), or another | 
|  | nested HashIfBlock.''' | 
|  | def __init__(self): | 
|  | self.kids = [] | 
|  |  | 
|  | def isLeaf(self): | 
|  | return False | 
|  |  | 
|  |  | 
|  | def do_file(filename, inclname, file_kind, f, all_inclnames, included_h_inclnames): | 
|  | block_stack = [HashIfBlock()] | 
|  |  | 
|  | # Extract the #include statements as a tree of IBlocks and IIncludes. | 
|  | for linenum, line in enumerate(f, start=1): | 
|  | # We're only interested in lines that contain a '#'. | 
|  | if not '#' in line: | 
|  | continue | 
|  |  | 
|  | # Look for a |#include "..."| line. | 
|  | m = re.match(r'\s*#\s*include\s+"([^"]*)"', line) | 
|  | if m is not None: | 
|  | block_stack[-1].kids.append(Include(m.group(1), linenum, False)) | 
|  |  | 
|  | # Look for a |#include <...>| line. | 
|  | m = re.match(r'\s*#\s*include\s+<([^>]*)>', line) | 
|  | if m is not None: | 
|  | block_stack[-1].kids.append(Include(m.group(1), linenum, True)) | 
|  |  | 
|  | # Look for a |#{if,ifdef,ifndef}| line. | 
|  | m = re.match(r'\s*#\s*(if|ifdef|ifndef)\b', line) | 
|  | if m is not None: | 
|  | # Open a new block. | 
|  | new_block = HashIfBlock() | 
|  | block_stack[-1].kids.append(new_block) | 
|  | block_stack.append(new_block) | 
|  |  | 
|  | # Look for a |#{elif,else}| line. | 
|  | m = re.match(r'\s*#\s*(elif|else)\b', line) | 
|  | if m is not None: | 
|  | # Close the current block, and open an adjacent one. | 
|  | block_stack.pop() | 
|  | new_block = HashIfBlock() | 
|  | block_stack[-1].kids.append(new_block) | 
|  | block_stack.append(new_block) | 
|  |  | 
|  | # Look for a |#endif| line. | 
|  | m = re.match(r'\s*#\s*endif\b', line) | 
|  | if m is not None: | 
|  | # Close the current block. | 
|  | block_stack.pop() | 
|  |  | 
|  | def check_include_statement(include): | 
|  | '''Check the style of a single #include statement.''' | 
|  |  | 
|  | if include.is_system: | 
|  | # Check it is not a known local file (in which case it's probably a system header). | 
|  | if include.inclname in included_inclnames_to_ignore or \ | 
|  | include.inclname in all_inclnames: | 
|  | error(filename, include.linenum, | 
|  | include.quote() + ' should be included using', | 
|  | 'the #include "..." form') | 
|  |  | 
|  | else: | 
|  | if include.inclname not in included_inclnames_to_ignore: | 
|  | included_kind = FileKind.get(include.inclname) | 
|  |  | 
|  | # Check the #include path has the correct form. | 
|  | if include.inclname not in all_inclnames: | 
|  | error(filename, include.linenum, | 
|  | include.quote() + ' is included using the wrong path;', | 
|  | 'did you forget a prefix, or is the file not yet committed?') | 
|  |  | 
|  | # Record inclusions of .h files for cycle detection later. | 
|  | # (Exclude .tbl and .msg files.) | 
|  | elif included_kind == FileKind.H or included_kind == FileKind.INL_H: | 
|  | included_h_inclnames.add(include.inclname) | 
|  |  | 
|  | # Check a H file doesn't #include an INL_H file. | 
|  | if file_kind == FileKind.H and included_kind == FileKind.INL_H: | 
|  | error(filename, include.linenum, | 
|  | 'vanilla header includes an inline-header file ' + include.quote()) | 
|  |  | 
|  | # Check a file doesn't #include itself.  (We do this here because the cycle | 
|  | # detection below doesn't detect this case.) | 
|  | if inclname == include.inclname: | 
|  | error(filename, include.linenum, 'the file includes itself') | 
|  |  | 
|  | def check_includes_order(include1, include2): | 
|  | '''Check the ordering of two #include statements.''' | 
|  |  | 
|  | if include1.inclname in oddly_ordered_inclnames or \ | 
|  | include2.inclname in oddly_ordered_inclnames: | 
|  | return | 
|  |  | 
|  | section1 = include1.section(inclname) | 
|  | section2 = include2.section(inclname) | 
|  | if (section1 > section2) or \ | 
|  | ((section1 == section2) and (include1.inclname.lower() > include2.inclname.lower())): | 
|  | error(filename, str(include1.linenum) + ':' + str(include2.linenum), | 
|  | include1.quote() + ' should be included after ' + include2.quote()) | 
|  |  | 
|  | # Check the extracted #include statements, both individually, and the ordering of | 
|  | # adjacent pairs that live in the same block. | 
|  | def pair_traverse(prev, this): | 
|  | if this.isLeaf(): | 
|  | check_include_statement(this) | 
|  | if prev is not None and prev.isLeaf(): | 
|  | check_includes_order(prev, this) | 
|  | else: | 
|  | for prev2, this2 in zip([None] + this.kids[0:-1], this.kids): | 
|  | pair_traverse(prev2, this2) | 
|  |  | 
|  | pair_traverse(None, block_stack[-1]) | 
|  |  | 
|  |  | 
|  | def find_cycles(all_inclnames, edges): | 
|  | '''Find and draw any cycles.''' | 
|  |  | 
|  | SCCs = tarjan(all_inclnames, edges) | 
|  |  | 
|  | # The various sorted() calls below ensure the output is deterministic. | 
|  |  | 
|  | def draw_SCC(c): | 
|  | cset = set(c) | 
|  | drawn = set() | 
|  | def draw(v, indent): | 
|  | out('   ' * indent + ('-> ' if indent else '   ') + v) | 
|  | if v in drawn: | 
|  | return | 
|  | drawn.add(v) | 
|  | for succ in sorted(edges[v]): | 
|  | if succ in cset: | 
|  | draw(succ, indent + 1) | 
|  | draw(sorted(c)[0], 0) | 
|  | out('') | 
|  |  | 
|  | have_drawn_an_SCC = False | 
|  | for scc in sorted(SCCs): | 
|  | if len(scc) != 1: | 
|  | if not have_drawn_an_SCC: | 
|  | error('(multiple files)', None, 'header files form one or more cycles') | 
|  | have_drawn_an_SCC = True | 
|  |  | 
|  | draw_SCC(scc) | 
|  |  | 
|  |  | 
|  | # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph. | 
|  | # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm | 
|  | def tarjan(V, E): | 
|  | vertex_index = {} | 
|  | vertex_lowlink = {} | 
|  | index = 0 | 
|  | S = [] | 
|  | all_SCCs = [] | 
|  |  | 
|  | def strongconnect(v, index): | 
|  | # Set the depth index for v to the smallest unused index | 
|  | vertex_index[v] = index | 
|  | vertex_lowlink[v] = index | 
|  | index += 1 | 
|  | S.append(v) | 
|  |  | 
|  | # Consider successors of v | 
|  | for w in E[v]: | 
|  | if w not in vertex_index: | 
|  | # Successor w has not yet been visited; recurse on it | 
|  | index = strongconnect(w, index) | 
|  | vertex_lowlink[v] = min(vertex_lowlink[v], vertex_lowlink[w]) | 
|  | elif w in S: | 
|  | # Successor w is in stack S and hence in the current SCC | 
|  | vertex_lowlink[v] = min(vertex_lowlink[v], vertex_index[w]) | 
|  |  | 
|  | # If v is a root node, pop the stack and generate an SCC | 
|  | if vertex_lowlink[v] == vertex_index[v]: | 
|  | i = S.index(v) | 
|  | scc = S[i:] | 
|  | del S[i:] | 
|  | all_SCCs.append(scc) | 
|  |  | 
|  | return index | 
|  |  | 
|  | for v in V: | 
|  | if v not in vertex_index: | 
|  | index = strongconnect(v, index) | 
|  |  | 
|  | return all_SCCs | 
|  |  | 
|  |  | 
|  | def main(): | 
|  | ok = check_style() | 
|  |  | 
|  | if ok: | 
|  | print('TEST-PASS | check_spidermonkey_style.py | ok') | 
|  | else: | 
|  | print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | actual output does not match expected output;  diff is above') | 
|  |  | 
|  | sys.exit(0 if ok else 1) | 
|  |  | 
|  |  | 
|  | if __name__ == '__main__': | 
|  | main() |