1 # vim: set ts=8 sts=4 et sw=4 tw=99:
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this
4 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 # ----------------------------------------------------------------------------
7 # This script checks various aspects of SpiderMonkey code style. The current checks are as
10 # We check the following things in headers.
12 # - No cyclic dependencies.
14 # - No normal header should #include a inlines.h/-inl.h file.
16 # - #ifndef wrappers should have the right form. (XXX: not yet implemented)
17 # - Every header file should have one.
18 # - The guard name used should be appropriate for the filename.
20 # We check the following things in all files.
22 # - #includes should have full paths, e.g. "jit/Ion.h", not "Ion.h".
24 # - #includes should use the appropriate form for system headers (<...>) and
25 # local headers ("...").
27 # - #includes should be ordered correctly.
28 # - Each one should be in the correct section.
29 # - Alphabetical order should be used within sections.
30 # - Sections should be in the right order.
31 # Note that the presence of #if/#endif blocks complicates things, to the
32 # point that it's not always clear where a conditionally-compiled #include
33 # statement should go, even to a human. Therefore, we check the #include
34 # statements within each #if/#endif block (including nested ones) in
35 # isolation, but don't try to do any order checking between such blocks.
36 # ----------------------------------------------------------------------------
38 from __future__
import absolute_import
, print_function
45 # We don't bother checking files in these directories, because they're (a) auxiliary or (b)
46 # imported code that doesn't follow our coding style.
47 ignored_js_src_dirs
= [
48 'js/src/config/', # auxiliary stuff
49 'js/src/ctypes/libffi/', # imported code
50 'js/src/devtools/', # auxiliary stuff
51 'js/src/editline/', # imported code
52 'js/src/gdb/', # auxiliary stuff
53 'js/src/vtune/', # imported code
54 'js/src/zydis/', # imported code
57 # We ignore #includes of these files, because they don't follow the usual rules.
58 included_inclnames_to_ignore
= set([
59 'ffi.h', # generated in ctypes/libffi/
60 'devtools/Instruments.h', # we ignore devtools/ in general
61 'double-conversion/double-conversion.h', # strange MFBT case
62 'javascript-trace.h', # generated in $OBJDIR if HAVE_DTRACE is defined
63 'frontend/ReservedWordsGenerated.h', # generated in $OBJDIR
64 'frontend/smoosh_generated.h', # generated in $OBJDIR
65 'gc/StatsPhasesGenerated.h', # generated in $OBJDIR
66 'gc/StatsPhasesGenerated.inc', # generated in $OBJDIR
67 'jit/CacheIROpsGenerated.h', # generated in $OBJDIR
68 'jit/LOpcodesGenerated.h', # generated in $OBJDIR
69 'jit/MOpcodesGenerated.h', # generated in $OBJDIR
70 'js/ProfilingCategoryList.h', # comes from mozglue/baseprofiler
71 'jscustomallocator.h', # provided by embedders; allowed to be missing
72 'js-config.h', # generated in $OBJDIR
74 'FuzzerDefs.h', # included without a path
75 'FuzzingInterface.h', # included without a path
76 'mozmemory.h', # included without a path
82 'private/pprio.h', # NSPR
88 'selfhosted.out.h', # generated in $OBJDIR
89 'shellmoduleloader.out.h', # generated in $OBJDIR
90 'unicode/basictz.h', # ICU
91 'unicode/locid.h', # ICU
92 'unicode/plurrule.h', # ICU
93 'unicode/putil.h', # ICU
94 'unicode/timezone.h', # ICU
95 'unicode/ucal.h', # ICU
96 'unicode/uchar.h', # ICU
97 'unicode/uclean.h', # ICU
98 'unicode/ucol.h', # ICU
99 'unicode/ucurr.h', # ICU
100 'unicode/udat.h', # ICU
101 'unicode/udata.h', # ICU
102 'unicode/udateintervalformat.h', # ICU
103 'unicode/udatpg.h', # ICU
104 'unicode/udisplaycontext.h', # ICU
105 'unicode/uenum.h', # ICU
106 'unicode/ufieldpositer.h', # ICU
107 'unicode/uformattedvalue.h', # ICU
108 'unicode/ulistformatter.h', # ICU
109 'unicode/uldnames.h', # ICU
110 'unicode/uloc.h', # ICU
111 'unicode/umachine.h', # ICU
112 'unicode/uniset.h', # ICU
113 'unicode/unistr.h', # ICU
114 'unicode/unorm2.h', # ICU
115 'unicode/unum.h', # ICU
116 'unicode/unumberformatter.h', # ICU
117 'unicode/unumsys.h', # ICU
118 'unicode/upluralrules.h', # ICU
119 'unicode/ureldatefmt.h', # ICU
120 'unicode/ures.h', # ICU
121 'unicode/ustring.h', # ICU
122 'unicode/utypes.h', # ICU
123 'unicode/uversion.h', # ICU
124 'vtune/VTuneWrapper.h', # VTune
125 'zydis/ZydisAPI.h', # Zydis
128 # These files have additional constraints on where they are #included, so we
129 # ignore #includes of them when checking #include ordering.
130 oddly_ordered_inclnames
= set([
131 'ctypes/typedefs.h', # Included multiple times in the body of ctypes/CTypes.h
132 # Included in the body of frontend/TokenStream.h
133 'frontend/ReservedWordsGenerated.h',
134 'gc/StatsPhasesGenerated.h', # Included in the body of gc/Statistics.h
135 'gc/StatsPhasesGenerated.inc', # Included in the body of gc/Statistics.cpp
136 'psapi.h', # Must be included after "util/Windows.h" on Windows
137 'machine/endian.h', # Must be included after <sys/types.h> on BSD
138 'winbase.h', # Must precede other system headers(?)
139 'windef.h' # Must precede other system headers(?)
142 # The files in tests/style/ contain code that fails this checking in various
143 # ways. Here is the output we expect. If the actual output differs from
144 # this, one of the following must have happened.
145 # - New SpiderMonkey code violates one of the checked rules.
146 # - The tests/style/ files have changed without expected_output being changed
148 # - This script has been broken somehow.
150 expected_output
= '''\
151 js/src/tests/style/BadIncludes.h:3: error:
152 the file includes itself
154 js/src/tests/style/BadIncludes.h:6: error:
155 "BadIncludes2.h" is included using the wrong path;
156 did you forget a prefix, or is the file not yet committed?
158 js/src/tests/style/BadIncludes.h:8: error:
159 <tests/style/BadIncludes2.h> should be included using
160 the #include "..." form
162 js/src/tests/style/BadIncludes.h:10: error:
163 "stdio.h" is included using the wrong path;
164 did you forget a prefix, or is the file not yet committed?
166 js/src/tests/style/BadIncludes2.h:1: error:
167 vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
169 js/src/tests/style/BadIncludesOrder-inl.h:5:6: error:
170 "vm/JSScript-inl.h" should be included after "vm/Interpreter-inl.h"
172 js/src/tests/style/BadIncludesOrder-inl.h:6:7: error:
173 "vm/Interpreter-inl.h" should be included after "js/Value.h"
175 js/src/tests/style/BadIncludesOrder-inl.h:7:8: error:
176 "js/Value.h" should be included after "ds/LifoAlloc.h"
178 js/src/tests/style/BadIncludesOrder-inl.h:8:9: error:
179 "ds/LifoAlloc.h" should be included after "jsapi.h"
181 js/src/tests/style/BadIncludesOrder-inl.h:9:10: error:
182 "jsapi.h" should be included after <stdio.h>
184 js/src/tests/style/BadIncludesOrder-inl.h:10:11: error:
185 <stdio.h> should be included after "mozilla/HashFunctions.h"
187 js/src/tests/style/BadIncludesOrder-inl.h:28:29: error:
188 "vm/JSScript.h" should be included after "vm/JSFunction.h"
190 (multiple files): error:
191 header files form one or more cycles
193 tests/style/HeaderCycleA1.h
194 -> tests/style/HeaderCycleA2.h
195 -> tests/style/HeaderCycleA3.h
196 -> tests/style/HeaderCycleA1.h
198 tests/style/HeaderCycleB1-inl.h
199 -> tests/style/HeaderCycleB2-inl.h
200 -> tests/style/HeaderCycleB3-inl.h
201 -> tests/style/HeaderCycleB4-inl.h
202 -> tests/style/HeaderCycleB1-inl.h
203 -> tests/style/jsheadercycleB5inlines.h
204 -> tests/style/HeaderCycleB1-inl.h
205 -> tests/style/HeaderCycleB4-inl.h
214 actual_output
.append(line
+ '\n')
217 def error(filename
, linenum
, *lines
):
219 if linenum
is not None:
220 location
+= ':' + str(linenum
)
221 out(location
+ ': error:')
227 class FileKind(object):
237 if filename
.endswith('.c'):
240 if filename
.endswith('.cpp'):
243 if filename
.endswith(('inlines.h', '-inl.h')):
244 return FileKind
.INL_H
246 if filename
.endswith('.h'):
249 if filename
.endswith('.tbl'):
252 if filename
.endswith('.msg'):
255 error(filename
, None, 'unknown file kind')
258 def check_style(enable_fixup
):
259 # We deal with two kinds of name.
260 # - A "filename" is a full path to a file from the repository root.
261 # - An "inclname" is how a file is referred to in a #include statement.
263 # Examples (filename -> inclname)
264 # - "mfbt/Attributes.h" -> "mozilla/Attributes.h"
265 # - "mozglue/misc/TimeStamp.h -> "mozilla/TimeStamp.h"
266 # - "memory/mozalloc/mozalloc.h -> "mozilla/mozalloc.h"
267 # - "js/public/Vector.h" -> "js/Vector.h"
268 # - "js/src/vm/String.h" -> "vm/String.h"
270 non_js_dirnames
= ('mfbt/',
272 'mozglue/') # type: tuple(str)
273 non_js_inclnames
= set() # type: set(inclname)
274 js_names
= dict() # type: dict(filename, inclname)
276 # Process files in js/src.
277 js_src_root
= os
.path
.join('js', 'src')
278 for dirpath
, dirnames
, filenames
in os
.walk(js_src_root
):
279 if dirpath
== js_src_root
:
280 # Skip any subdirectories that contain a config.status file
283 for dirname
in dirnames
:
284 path
= os
.path
.join(dirpath
, dirname
, 'config.status')
285 if os
.path
.isfile(path
):
286 builddirs
.append(dirname
)
287 for dirname
in builddirs
:
288 dirnames
.remove(dirname
)
289 for filename
in filenames
:
290 filepath
= os
.path
.join(dirpath
, filename
).replace('\\', '/')
291 if not filepath
.startswith(tuple(ignored_js_src_dirs
)) and \
292 filepath
.endswith(('.c', '.cpp', '.h', '.tbl', '.msg')):
293 inclname
= filepath
[len('js/src/'):]
294 js_names
[filepath
] = inclname
296 # Look for header files in directories in non_js_dirnames.
297 for non_js_dir
in non_js_dirnames
:
298 for dirpath
, dirnames
, filenames
in os
.walk(non_js_dir
):
299 for filename
in filenames
:
300 if filename
.endswith('.h'):
301 inclname
= 'mozilla/' + filename
302 non_js_inclnames
.add(inclname
)
304 # Look for header files in js/public.
305 js_public_root
= os
.path
.join('js', 'public')
306 for dirpath
, dirnames
, filenames
in os
.walk(js_public_root
):
307 for filename
in filenames
:
308 if filename
.endswith('.h'):
309 filepath
= os
.path
.join(dirpath
, filename
).replace('\\', '/')
310 inclname
= 'js/' + filepath
[len('js/public/'):]
311 js_names
[filepath
] = inclname
313 all_inclnames
= non_js_inclnames |
set(js_names
.values())
315 edges
= dict() # type: dict(inclname, set(inclname))
317 # We don't care what's inside the MFBT and MOZALLOC files, but because they
318 # are #included from JS files we have to add them to the inclusion graph.
319 for inclname
in non_js_inclnames
:
320 edges
[inclname
] = set()
322 # Process all the JS files.
323 for filename
in sorted(js_names
.keys()):
324 inclname
= js_names
[filename
]
325 file_kind
= FileKind
.get(filename
)
326 if file_kind
== FileKind
.C
or file_kind
== FileKind
.CPP
or \
327 file_kind
== FileKind
.H
or file_kind
== FileKind
.INL_H
:
328 included_h_inclnames
= set() # type: set(inclname)
330 with
open(filename
, encoding
='utf-8') as f
:
334 code
= code
.sorted(inclname
)
335 with
open(filename
, 'w') as f
:
336 f
.write(code
.to_source())
338 check_file(filename
, inclname
, file_kind
, code
,
339 all_inclnames
, included_h_inclnames
)
341 edges
[inclname
] = included_h_inclnames
343 find_cycles(all_inclnames
, edges
)
345 # Compare expected and actual output.
346 difflines
= difflib
.unified_diff(expected_output
, actual_output
,
347 fromfile
='check_spidermonkey_style.py expected output',
348 tofile
='check_spidermonkey_style.py actual output')
350 for diffline
in difflines
:
352 print(diffline
, end
='')
357 def module_name(name
):
358 '''Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename.'''
360 return name
.replace('inlines.h', '').replace('-inl.h', '').replace('.h', '').replace('.cpp', '') # NOQA: E501
363 def is_module_header(enclosing_inclname
, header_inclname
):
364 '''Determine if an included name is the "module header", i.e. should be
365 first in the file.'''
367 module
= module_name(enclosing_inclname
)
369 # Normal case, e.g. module == "foo/Bar", header_inclname == "foo/Bar.h".
370 if module
== module_name(header_inclname
):
373 # A public header, e.g. module == "foo/Bar", header_inclname == "js/Bar.h".
374 m
= re
.match(r
'js\/(.*)\.h', header_inclname
)
375 if m
is not None and module
.endswith('/' + m
.group(1)):
381 class Include(object):
382 '''Important information for a single #include statement.'''
384 def __init__(self
, include_prefix
, inclname
, line_suffix
, linenum
, is_system
):
385 self
.include_prefix
= include_prefix
386 self
.line_suffix
= line_suffix
387 self
.inclname
= inclname
388 self
.linenum
= linenum
389 self
.is_system
= is_system
391 def is_style_relevant(self
):
392 # Includes are style-relevant; that is, they're checked by the pairwise
393 # style-checking algorithm in check_file.
396 def section(self
, enclosing_inclname
):
397 '''Identify which section inclname belongs to.
399 The section numbers are as follows.
400 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp)
403 3. jsfoo.h, prmjtime.h, etc
407 7. non-.h, e.g. *.tbl, *.msg
413 if not self
.inclname
.endswith('.h'):
416 # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need
418 if is_module_header(enclosing_inclname
, self
.inclname
):
421 if '/' in self
.inclname
:
422 if self
.inclname
.startswith('mozilla/'):
425 if self
.inclname
.endswith('-inl.h'):
430 if self
.inclname
.endswith('inlines.h'):
437 return '<' + self
.inclname
+ '>'
439 return '"' + self
.inclname
+ '"'
441 def sort_key(self
, enclosing_inclname
):
442 return (self
.section(enclosing_inclname
), self
.inclname
.lower())
445 return self
.include_prefix
+ self
.quote() + self
.line_suffix
+ '\n'
448 class CppBlock(object):
449 '''C preprocessor block: a whole file or a single #if/#elif/#else block.
451 A #if/#endif block is the contents of a #if/#endif (or similar) section.
452 The top-level block, which is not within a #if/#endif pair, is also
455 Each kid is either an Include (representing a #include), OrdinaryCode, or
456 a nested CppBlock.'''
458 def __init__(self
, start_line
=""):
459 self
.start
= start_line
463 def is_style_relevant(self
):
466 def append_ordinary_line(self
, line
):
467 if len(self
.kids
) == 0 or not isinstance(self
.kids
[-1], OrdinaryCode
):
468 self
.kids
.append(OrdinaryCode())
469 self
.kids
[-1].lines
.append(line
)
471 def style_relevant_kids(self
):
472 """ Return a list of kids in this block that are style-relevant. """
473 return [kid
for kid
in self
.kids
if kid
.is_style_relevant()]
475 def sorted(self
, enclosing_inclname
):
476 """Return a hopefully-sorted copy of this block. Implements --fixup.
478 When in doubt, this leaves the code unchanged.
481 def pretty_sorted_includes(includes
):
482 """ Return a new list containing the given includes, in order,
483 with blank lines separating sections. """
484 keys
= [inc
.sort_key(enclosing_inclname
) for inc
in includes
]
485 if sorted(keys
) == keys
:
486 return includes
# if nothing is out of order, don't touch anything
489 current_section
= None
490 for (section
, _
), inc
in sorted(zip(keys
, includes
)):
491 if current_section
is not None and section
!= current_section
:
492 output
.append(OrdinaryCode(["\n"])) # blank line
494 current_section
= section
497 def should_try_to_sort(includes
):
498 if 'tests/style/BadIncludes' in enclosing_inclname
:
499 return False # don't straighten the counterexample
500 if any(inc
.inclname
in oddly_ordered_inclnames
for inc
in includes
):
501 return False # don't sort batches containing odd includes
502 if includes
== sorted(includes
, key
=lambda inc
: inc
.sort_key(enclosing_inclname
)):
503 return False # it's already sorted, avoid whitespace-only fixups
506 # The content of the eventual output of this method.
509 # The current batch of includes to sort. This list only ever contains Include objects
510 # and whitespace OrdinaryCode objects.
514 """Sort the contents of `batch` and move it to `output`."""
516 assert all(isinstance(item
, Include
)
517 or (isinstance(item
, OrdinaryCode
) and "".join(item
.lines
).isspace())
520 # Here we throw away the blank lines.
521 # `pretty_sorted_includes` puts them back.
523 last_include_index
= -1
524 for i
, item
in enumerate(batch
):
525 if isinstance(item
, Include
):
526 includes
.append(item
)
527 last_include_index
= i
528 cutoff
= last_include_index
+ 1
530 if should_try_to_sort(includes
):
531 output
.extend(pretty_sorted_includes(
532 includes
) + batch
[cutoff
:])
537 for kid
in self
.kids
:
538 if isinstance(kid
, CppBlock
):
540 output
.append(kid
.sorted(enclosing_inclname
))
541 elif isinstance(kid
, Include
):
544 assert isinstance(kid
, OrdinaryCode
)
545 if kid
.to_source().isspace():
553 result
.start
= self
.start
554 result
.end
= self
.end
559 return self
.start
+ ''.join(kid
.to_source() for kid
in self
.kids
) + self
.end
562 class OrdinaryCode(object):
563 ''' A list of lines of code that aren't #include/#if/#else/#endif lines. '''
565 def __init__(self
, lines
=None):
566 self
.lines
= lines
if lines
is not None else []
568 def is_style_relevant(self
):
572 return ''.join(self
.lines
)
575 # A "snippet" is one of:
577 # * Include - representing an #include line
578 # * CppBlock - a whole file or #if/#elif/#else block; contains a list of snippets
579 # * OrdinaryCode - representing lines of non-#include-relevant code
582 block_stack
= [CppBlock()]
584 # Extract the #include statements as a tree of snippets.
585 for linenum
, line
in enumerate(f
, start
=1):
586 if line
.lstrip().startswith('#'):
587 # Look for a |#include "..."| line.
588 m
= re
.match(r
'(\s*#\s*include\s+)"([^"]*)"(.*)', line
)
590 prefix
, inclname
, suffix
= m
.groups()
591 block_stack
[-1].kids
.append(Include(prefix
,
592 inclname
, suffix
, linenum
, is_system
=False))
595 # Look for a |#include <...>| line.
596 m
= re
.match(r
'(\s*#\s*include\s+)<([^>]*)>(.*)', line
)
598 prefix
, inclname
, suffix
= m
.groups()
599 block_stack
[-1].kids
.append(Include(prefix
,
600 inclname
, suffix
, linenum
, is_system
=True))
603 # Look for a |#{if,ifdef,ifndef}| line.
604 m
= re
.match(r
'\s*#\s*(if|ifdef|ifndef)\b', line
)
607 new_block
= CppBlock(line
)
608 block_stack
[-1].kids
.append(new_block
)
609 block_stack
.append(new_block
)
612 # Look for a |#{elif,else}| line.
613 m
= re
.match(r
'\s*#\s*(elif|else)\b', line
)
615 # Close the current block, and open an adjacent one.
617 new_block
= CppBlock(line
)
618 block_stack
[-1].kids
.append(new_block
)
619 block_stack
.append(new_block
)
622 # Look for a |#endif| line.
623 m
= re
.match(r
'\s*#\s*endif\b', line
)
625 # Close the current block.
626 block_stack
.pop().end
= line
627 if len(block_stack
) == 0:
629 "#endif without #if at line " + str(linenum
))
632 # Otherwise, we have an ordinary line.
633 block_stack
[-1].append_ordinary_line(line
)
635 if len(block_stack
) > 1:
636 raise ValueError("unmatched #if")
637 return block_stack
[-1]
640 def check_file(filename
, inclname
, file_kind
, code
, all_inclnames
, included_h_inclnames
):
642 def check_include_statement(include
):
643 '''Check the style of a single #include statement.'''
645 if include
.is_system
:
646 # Check it is not a known local file (in which case it's probably a system header).
647 if include
.inclname
in included_inclnames_to_ignore
or \
648 include
.inclname
in all_inclnames
:
649 error(filename
, include
.linenum
,
650 include
.quote() + ' should be included using',
651 'the #include "..." form')
654 if include
.inclname
not in included_inclnames_to_ignore
:
655 included_kind
= FileKind
.get(include
.inclname
)
657 # Check the #include path has the correct form.
658 if include
.inclname
not in all_inclnames
:
659 error(filename
, include
.linenum
,
660 include
.quote() + ' is included using the wrong path;',
661 'did you forget a prefix, or is the file not yet committed?')
663 # Record inclusions of .h files for cycle detection later.
664 # (Exclude .tbl and .msg files.)
665 elif included_kind
== FileKind
.H
or included_kind
== FileKind
.INL_H
:
666 included_h_inclnames
.add(include
.inclname
)
668 # Check a H file doesn't #include an INL_H file.
669 if file_kind
== FileKind
.H
and included_kind
== FileKind
.INL_H
:
670 error(filename
, include
.linenum
,
671 'vanilla header includes an inline-header file ' + include
.quote())
673 # Check a file doesn't #include itself. (We do this here because the cycle
674 # detection below doesn't detect this case.)
675 if inclname
== include
.inclname
:
676 error(filename
, include
.linenum
,
677 'the file includes itself')
679 def check_includes_order(include1
, include2
):
680 '''Check the ordering of two #include statements.'''
682 if include1
.inclname
in oddly_ordered_inclnames
or \
683 include2
.inclname
in oddly_ordered_inclnames
:
686 section1
= include1
.section(inclname
)
687 section2
= include2
.section(inclname
)
688 if (section1
> section2
) or \
689 ((section1
== section2
) and (include1
.inclname
.lower() > include2
.inclname
.lower())):
690 error(filename
, str(include1
.linenum
) + ':' + str(include2
.linenum
),
691 include1
.quote() + ' should be included after ' + include2
.quote())
693 # Check the extracted #include statements, both individually, and the ordering of
694 # adjacent pairs that live in the same block.
695 def pair_traverse(prev
, this
):
696 if isinstance(this
, Include
):
697 check_include_statement(this
)
698 if isinstance(prev
, Include
):
699 check_includes_order(prev
, this
)
701 kids
= this
.style_relevant_kids()
702 for prev2
, this2
in zip([None] + kids
[0:-1], kids
):
703 pair_traverse(prev2
, this2
)
705 pair_traverse(None, code
)
708 def find_cycles(all_inclnames
, edges
):
709 '''Find and draw any cycles.'''
711 SCCs
= tarjan(all_inclnames
, edges
)
713 # The various sorted() calls below ensure the output is deterministic.
720 out(' ' * indent
+ ('-> ' if indent
else ' ') + v
)
724 for succ
in sorted(edges
[v
]):
726 draw(succ
, indent
+ 1)
727 draw(sorted(c
)[0], 0)
730 have_drawn_an_SCC
= False
731 for scc
in sorted(SCCs
):
733 if not have_drawn_an_SCC
:
734 error('(multiple files)', None,
735 'header files form one or more cycles')
736 have_drawn_an_SCC
= True
741 # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph.
742 # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
750 def strongconnect(v
, index
):
751 # Set the depth index for v to the smallest unused index
752 vertex_index
[v
] = index
753 vertex_lowlink
[v
] = index
757 # Consider successors of v
759 if w
not in vertex_index
:
760 # Successor w has not yet been visited; recurse on it
761 index
= strongconnect(w
, index
)
762 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_lowlink
[w
])
764 # Successor w is in stack S and hence in the current SCC
765 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_index
[w
])
767 # If v is a root node, pop the stack and generate an SCC
768 if vertex_lowlink
[v
] == vertex_index
[v
]:
777 if v
not in vertex_index
:
778 index
= strongconnect(v
, index
)
784 if sys
.argv
[1:] == ["--fixup"]:
785 # Sort #include directives in-place. Fixup mode doesn't solve
786 # all possible silliness that the script checks for; it's just a
787 # hack for the common case where renaming a header causes style
790 elif sys
.argv
[1:] == []:
793 print("TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | unexpected command "
794 "line options: " + repr(sys
.argv
[1:]))
797 ok
= check_style(fixup
)
800 print('TEST-PASS | check_spidermonkey_style.py | ok')
802 print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | ' +
803 'actual output does not match expected output; diff is above.')
804 print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | ' +
805 'Hint: If the problem is that you renamed a header, and many #includes ' +
806 'are no longer in alphabetical order, commit your work and then try ' +
807 '`check_spidermonkey_style.py --fixup`. ' +
808 'You need to commit first because --fixup modifies your files in place.')
810 sys
.exit(0 if ok
else 1)
813 if __name__
== '__main__':