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', '.msg')):
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, for example:
370 # module == "vm/Runtime", header_inclname == "vm/Runtime.h".
371 if module
== module_name(header_inclname
):
374 # A public header, for example:
376 # module == "vm/CharacterEncoding",
377 # header_inclname == "js/CharacterEncoding.h"
379 # or (for implementation files for js/public/*/*.h headers)
381 # module == "vm/SourceHook",
382 # header_inclname == "js/experimental/SourceHook.h"
383 m
= re
.match(r
'js\/.*?([^\/]+)\.h', header_inclname
)
384 if m
is not None and module
.endswith('/' + m
.group(1)):
390 class Include(object):
391 '''Important information for a single #include statement.'''
393 def __init__(self
, include_prefix
, inclname
, line_suffix
, linenum
, is_system
):
394 self
.include_prefix
= include_prefix
395 self
.line_suffix
= line_suffix
396 self
.inclname
= inclname
397 self
.linenum
= linenum
398 self
.is_system
= is_system
400 def is_style_relevant(self
):
401 # Includes are style-relevant; that is, they're checked by the pairwise
402 # style-checking algorithm in check_file.
405 def section(self
, enclosing_inclname
):
406 '''Identify which section inclname belongs to.
408 The section numbers are as follows.
409 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp)
412 3. jsfoo.h, prmjtime.h, etc
416 7. non-.h, e.g. *.tbl, *.msg (these can be scattered throughout files)
422 if not self
.inclname
.endswith('.h'):
425 # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need
427 if is_module_header(enclosing_inclname
, self
.inclname
):
430 if '/' in self
.inclname
:
431 if self
.inclname
.startswith('mozilla/'):
434 if self
.inclname
.endswith('-inl.h'):
439 if self
.inclname
.endswith('inlines.h'):
446 return '<' + self
.inclname
+ '>'
448 return '"' + self
.inclname
+ '"'
450 def sort_key(self
, enclosing_inclname
):
451 return (self
.section(enclosing_inclname
), self
.inclname
.lower())
454 return self
.include_prefix
+ self
.quote() + self
.line_suffix
+ '\n'
457 class CppBlock(object):
458 '''C preprocessor block: a whole file or a single #if/#elif/#else block.
460 A #if/#endif block is the contents of a #if/#endif (or similar) section.
461 The top-level block, which is not within a #if/#endif pair, is also
464 Each kid is either an Include (representing a #include), OrdinaryCode, or
465 a nested CppBlock.'''
467 def __init__(self
, start_line
=""):
468 self
.start
= start_line
472 def is_style_relevant(self
):
475 def append_ordinary_line(self
, line
):
476 if len(self
.kids
) == 0 or not isinstance(self
.kids
[-1], OrdinaryCode
):
477 self
.kids
.append(OrdinaryCode())
478 self
.kids
[-1].lines
.append(line
)
480 def style_relevant_kids(self
):
481 """ Return a list of kids in this block that are style-relevant. """
482 return [kid
for kid
in self
.kids
if kid
.is_style_relevant()]
484 def sorted(self
, enclosing_inclname
):
485 """Return a hopefully-sorted copy of this block. Implements --fixup.
487 When in doubt, this leaves the code unchanged.
490 def pretty_sorted_includes(includes
):
491 """ Return a new list containing the given includes, in order,
492 with blank lines separating sections. """
493 keys
= [inc
.sort_key(enclosing_inclname
) for inc
in includes
]
494 if sorted(keys
) == keys
:
495 return includes
# if nothing is out of order, don't touch anything
498 current_section
= None
499 for (section
, _
), inc
in sorted(zip(keys
, includes
)):
500 if current_section
is not None and section
!= current_section
:
501 output
.append(OrdinaryCode(["\n"])) # blank line
503 current_section
= section
506 def should_try_to_sort(includes
):
507 if 'tests/style/BadIncludes' in enclosing_inclname
:
508 return False # don't straighten the counterexample
509 if any(inc
.inclname
in oddly_ordered_inclnames
for inc
in includes
):
510 return False # don't sort batches containing odd includes
511 if includes
== sorted(includes
, key
=lambda inc
: inc
.sort_key(enclosing_inclname
)):
512 return False # it's already sorted, avoid whitespace-only fixups
515 # The content of the eventual output of this method.
518 # The current batch of includes to sort. This list only ever contains Include objects
519 # and whitespace OrdinaryCode objects.
523 """Sort the contents of `batch` and move it to `output`."""
525 assert all(isinstance(item
, Include
)
526 or (isinstance(item
, OrdinaryCode
) and "".join(item
.lines
).isspace())
529 # Here we throw away the blank lines.
530 # `pretty_sorted_includes` puts them back.
532 last_include_index
= -1
533 for i
, item
in enumerate(batch
):
534 if isinstance(item
, Include
):
535 includes
.append(item
)
536 last_include_index
= i
537 cutoff
= last_include_index
+ 1
539 if should_try_to_sort(includes
):
540 output
.extend(pretty_sorted_includes(
541 includes
) + batch
[cutoff
:])
546 for kid
in self
.kids
:
547 if isinstance(kid
, CppBlock
):
549 output
.append(kid
.sorted(enclosing_inclname
))
550 elif isinstance(kid
, Include
):
553 assert isinstance(kid
, OrdinaryCode
)
554 if kid
.to_source().isspace():
562 result
.start
= self
.start
563 result
.end
= self
.end
568 return self
.start
+ ''.join(kid
.to_source() for kid
in self
.kids
) + self
.end
571 class OrdinaryCode(object):
572 ''' A list of lines of code that aren't #include/#if/#else/#endif lines. '''
574 def __init__(self
, lines
=None):
575 self
.lines
= lines
if lines
is not None else []
577 def is_style_relevant(self
):
581 return ''.join(self
.lines
)
584 # A "snippet" is one of:
586 # * Include - representing an #include line
587 # * CppBlock - a whole file or #if/#elif/#else block; contains a list of snippets
588 # * OrdinaryCode - representing lines of non-#include-relevant code
591 block_stack
= [CppBlock()]
593 # Extract the #include statements as a tree of snippets.
594 for linenum
, line
in enumerate(f
, start
=1):
595 if line
.lstrip().startswith('#'):
596 # Look for a |#include "..."| line.
597 m
= re
.match(r
'(\s*#\s*include\s+)"([^"]*)"(.*)', line
)
599 prefix
, inclname
, suffix
= m
.groups()
600 block_stack
[-1].kids
.append(Include(prefix
,
601 inclname
, suffix
, linenum
, is_system
=False))
604 # Look for a |#include <...>| line.
605 m
= re
.match(r
'(\s*#\s*include\s+)<([^>]*)>(.*)', line
)
607 prefix
, inclname
, suffix
= m
.groups()
608 block_stack
[-1].kids
.append(Include(prefix
,
609 inclname
, suffix
, linenum
, is_system
=True))
612 # Look for a |#{if,ifdef,ifndef}| line.
613 m
= re
.match(r
'\s*#\s*(if|ifdef|ifndef)\b', line
)
616 new_block
= CppBlock(line
)
617 block_stack
[-1].kids
.append(new_block
)
618 block_stack
.append(new_block
)
621 # Look for a |#{elif,else}| line.
622 m
= re
.match(r
'\s*#\s*(elif|else)\b', line
)
624 # Close the current block, and open an adjacent one.
626 new_block
= CppBlock(line
)
627 block_stack
[-1].kids
.append(new_block
)
628 block_stack
.append(new_block
)
631 # Look for a |#endif| line.
632 m
= re
.match(r
'\s*#\s*endif\b', line
)
634 # Close the current block.
635 block_stack
.pop().end
= line
636 if len(block_stack
) == 0:
638 "#endif without #if at line " + str(linenum
))
641 # Otherwise, we have an ordinary line.
642 block_stack
[-1].append_ordinary_line(line
)
644 if len(block_stack
) > 1:
645 raise ValueError("unmatched #if")
646 return block_stack
[-1]
649 def check_file(filename
, inclname
, file_kind
, code
, all_inclnames
, included_h_inclnames
):
651 def check_include_statement(include
):
652 '''Check the style of a single #include statement.'''
654 if include
.is_system
:
655 # Check it is not a known local file (in which case it's probably a system header).
656 if include
.inclname
in included_inclnames_to_ignore
or \
657 include
.inclname
in all_inclnames
:
658 error(filename
, include
.linenum
,
659 include
.quote() + ' should be included using',
660 'the #include "..." form')
663 if include
.inclname
not in included_inclnames_to_ignore
:
664 included_kind
= FileKind
.get(include
.inclname
)
666 # Check the #include path has the correct form.
667 if include
.inclname
not in all_inclnames
:
668 error(filename
, include
.linenum
,
669 include
.quote() + ' is included using the wrong path;',
670 'did you forget a prefix, or is the file not yet committed?')
672 # Record inclusions of .h files for cycle detection later.
673 # (Exclude .tbl and .msg files.)
674 elif included_kind
== FileKind
.H
or included_kind
== FileKind
.INL_H
:
675 included_h_inclnames
.add(include
.inclname
)
677 # Check a H file doesn't #include an INL_H file.
678 if file_kind
== FileKind
.H
and included_kind
== FileKind
.INL_H
:
679 error(filename
, include
.linenum
,
680 'vanilla header includes an inline-header file ' + include
.quote())
682 # Check a file doesn't #include itself. (We do this here because the cycle
683 # detection below doesn't detect this case.)
684 if inclname
== include
.inclname
:
685 error(filename
, include
.linenum
,
686 'the file includes itself')
688 def check_includes_order(include1
, include2
):
689 '''Check the ordering of two #include statements.'''
691 if include1
.inclname
in oddly_ordered_inclnames
or \
692 include2
.inclname
in oddly_ordered_inclnames
:
695 section1
= include1
.section(inclname
)
696 section2
= include2
.section(inclname
)
697 if (section1
> section2
) or \
698 ((section1
== section2
) and (include1
.inclname
.lower() > include2
.inclname
.lower())):
699 error(filename
, str(include1
.linenum
) + ':' + str(include2
.linenum
),
700 include1
.quote() + ' should be included after ' + include2
.quote())
702 # Check the extracted #include statements, both individually, and the ordering of
703 # adjacent pairs that live in the same block.
704 def pair_traverse(prev
, this
):
705 if isinstance(this
, Include
):
706 check_include_statement(this
)
707 if isinstance(prev
, Include
):
708 check_includes_order(prev
, this
)
710 kids
= this
.style_relevant_kids()
711 for prev2
, this2
in zip([None] + kids
[0:-1], kids
):
712 pair_traverse(prev2
, this2
)
714 pair_traverse(None, code
)
717 def find_cycles(all_inclnames
, edges
):
718 '''Find and draw any cycles.'''
720 SCCs
= tarjan(all_inclnames
, edges
)
722 # The various sorted() calls below ensure the output is deterministic.
729 out(' ' * indent
+ ('-> ' if indent
else ' ') + v
)
733 for succ
in sorted(edges
[v
]):
735 draw(succ
, indent
+ 1)
736 draw(sorted(c
)[0], 0)
739 have_drawn_an_SCC
= False
740 for scc
in sorted(SCCs
):
742 if not have_drawn_an_SCC
:
743 error('(multiple files)', None,
744 'header files form one or more cycles')
745 have_drawn_an_SCC
= True
750 # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph.
751 # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
759 def strongconnect(v
, index
):
760 # Set the depth index for v to the smallest unused index
761 vertex_index
[v
] = index
762 vertex_lowlink
[v
] = index
766 # Consider successors of v
768 if w
not in vertex_index
:
769 # Successor w has not yet been visited; recurse on it
770 index
= strongconnect(w
, index
)
771 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_lowlink
[w
])
773 # Successor w is in stack S and hence in the current SCC
774 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_index
[w
])
776 # If v is a root node, pop the stack and generate an SCC
777 if vertex_lowlink
[v
] == vertex_index
[v
]:
786 if v
not in vertex_index
:
787 index
= strongconnect(v
, index
)
793 if sys
.argv
[1:] == ["--fixup"]:
794 # Sort #include directives in-place. Fixup mode doesn't solve
795 # all possible silliness that the script checks for; it's just a
796 # hack for the common case where renaming a header causes style
799 elif sys
.argv
[1:] == []:
802 print("TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | unexpected command "
803 "line options: " + repr(sys
.argv
[1:]))
806 ok
= check_style(fixup
)
809 print('TEST-PASS | check_spidermonkey_style.py | ok')
811 print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | ' +
812 'actual output does not match expected output; diff is above.')
813 print('TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | ' +
814 'Hint: If the problem is that you renamed a header, and many #includes ' +
815 'are no longer in alphabetical order, commit your work and then try ' +
816 '`check_spidermonkey_style.py --fixup`. ' +
817 'You need to commit first because --fixup modifies your files in place.')
819 sys
.exit(0 if ok
else 1)
822 if __name__
== '__main__':