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 # ----------------------------------------------------------------------------
43 # We don't bother checking files in these directories, because they're (a) auxiliary or (b)
44 # imported code that doesn't follow our coding style.
45 ignored_js_src_dirs
= [
46 "js/src/config/", # auxiliary stuff
47 "js/src/ctypes/libffi/", # imported code
48 "js/src/devtools/", # auxiliary stuff
49 "js/src/editline/", # imported code
50 "js/src/gdb/", # auxiliary stuff
51 "js/src/vtune/", # imported code
52 "js/src/zydis/", # imported code
55 # We ignore #includes of these files, because they don't follow the usual rules.
56 included_inclnames_to_ignore
= set(
58 "ffi.h", # generated in ctypes/libffi/
59 "devtools/Instruments.h", # we ignore devtools/ in general
60 "double-conversion/double-conversion.h", # strange MFBT case
61 "javascript-trace.h", # generated in $OBJDIR if HAVE_DTRACE is defined
62 "frontend/ReservedWordsGenerated.h", # generated in $OBJDIR
63 "frontend/smoosh_generated.h", # generated in $OBJDIR
64 "gc/StatsPhasesGenerated.h", # generated in $OBJDIR
65 "gc/StatsPhasesGenerated.inc", # generated in $OBJDIR
66 "jit/ABIFunctionTypeGenerated.h", # generated in $OBJDIR"
67 "jit/AtomicOperationsGenerated.h", # generated in $OBJDIR
68 "jit/CacheIROpsGenerated.h", # generated in $OBJDIR
69 "jit/LIROpsGenerated.h", # generated in $OBJDIR
70 "jit/MIROpsGenerated.h", # generated in $OBJDIR
71 "js/ProfilingCategoryList.h", # comes from mozglue/baseprofiler
72 "mozilla/glue/Debug.h", # comes from mozglue/misc, shadowed by <mozilla/Debug.h>
73 "jscustomallocator.h", # provided by embedders; allowed to be missing
74 "js-config.h", # generated in $OBJDIR
76 "FuzzerDefs.h", # included without a path
77 "FuzzingInterface.h", # included without a path
78 "ICU4XGraphemeClusterSegmenter.h", # ICU4X
79 "ICU4XSentenceSegmenter.h", # ICU4X
80 "ICU4XWordSegmenter.h", # ICU4X
81 "mozmemory.h", # included without a path
87 "private/pprio.h", # NSPR
93 "selfhosted.out.h", # generated in $OBJDIR
94 "shellmoduleloader.out.h", # generated in $OBJDIR
95 "unicode/locid.h", # ICU
96 "unicode/uchar.h", # ICU
97 "unicode/uniset.h", # ICU
98 "unicode/unistr.h", # ICU
99 "unicode/utypes.h", # ICU
100 "vtune/VTuneWrapper.h", # VTune
101 "wasm/WasmBuiltinModuleGenerated.h", # generated in $OBJDIR"
102 "zydis/ZydisAPI.h", # Zydis
106 deprecated_inclnames
= {
107 "mozilla/Unused.h": "Use [[nodiscard]] and (void)expr casts instead.",
110 # JSAPI functions should be included through headers from js/public instead of
111 # using the old, catch-all jsapi.h file.
112 deprecated_inclnames_in_header
= {
113 "jsapi.h": "Prefer including headers from js/public.",
116 # Temporary exclusions for files which still need to include jsapi.h.
117 deprecated_inclnames_in_header_excludes
= {
118 "js/src/vm/Compartment-inl.h", # for JS::InformalValueTypeName
119 "js/src/jsapi-tests/tests.h", # for JS_ValueToSource
122 # These files have additional constraints on where they are #included, so we
123 # ignore #includes of them when checking #include ordering.
124 oddly_ordered_inclnames
= set(
126 "ctypes/typedefs.h", # Included multiple times in the body of ctypes/CTypes.h
127 # Included in the body of frontend/TokenStream.h
128 "frontend/ReservedWordsGenerated.h",
129 "gc/StatsPhasesGenerated.h", # Included in the body of gc/Statistics.h
130 "gc/StatsPhasesGenerated.inc", # Included in the body of gc/Statistics.cpp
131 "psapi.h", # Must be included after "util/WindowsWrapper.h" on Windows
132 "machine/endian.h", # Must be included after <sys/types.h> on BSD
133 "process.h", # Windows-specific
134 "winbase.h", # Must precede other system headers(?)
135 "windef.h", # Must precede other system headers(?)
136 "windows.h", # Must precede other system headers(?)
140 # The files in tests/style/ contain code that fails this checking in various
141 # ways. Here is the output we expect. If the actual output differs from
142 # this, one of the following must have happened.
143 # - New SpiderMonkey code violates one of the checked rules.
144 # - The tests/style/ files have changed without expected_output being changed
146 # - This script has been broken somehow.
148 expected_output
= """\
149 js/src/tests/style/BadIncludes.h:3: error:
150 the file includes itself
152 js/src/tests/style/BadIncludes.h:6: error:
153 "BadIncludes2.h" is included using the wrong path;
154 did you forget a prefix, or is the file not yet committed?
156 js/src/tests/style/BadIncludes.h:8: error:
157 <tests/style/BadIncludes2.h> should be included using
158 the #include "..." form
160 js/src/tests/style/BadIncludes.h:10: error:
161 "stdio.h" is included using the wrong path;
162 did you forget a prefix, or is the file not yet committed?
164 js/src/tests/style/BadIncludes.h:12: error:
165 "mozilla/Unused.h" is deprecated: Use [[nodiscard]] and (void)expr casts instead.
167 js/src/tests/style/BadIncludes2.h:1: error:
168 vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
170 js/src/tests/style/BadIncludesOrder-inl.h:5:6: error:
171 "vm/JSScript-inl.h" should be included after "vm/Interpreter-inl.h"
173 js/src/tests/style/BadIncludesOrder-inl.h:6:7: error:
174 "vm/Interpreter-inl.h" should be included after "js/Value.h"
176 js/src/tests/style/BadIncludesOrder-inl.h:7:8: error:
177 "js/Value.h" should be included after "ds/LifoAlloc.h"
179 js/src/tests/style/BadIncludesOrder-inl.h:9: error:
180 "jsapi.h" is deprecated: Prefer including headers from js/public.
182 js/src/tests/style/BadIncludesOrder-inl.h:8:9: error:
183 "ds/LifoAlloc.h" should be included after "jsapi.h"
185 js/src/tests/style/BadIncludesOrder-inl.h:9:10: error:
186 "jsapi.h" should be included after <stdio.h>
188 js/src/tests/style/BadIncludesOrder-inl.h:10:11: error:
189 <stdio.h> should be included after "mozilla/HashFunctions.h"
191 js/src/tests/style/BadIncludesOrder-inl.h:20: error:
192 "jsapi.h" is deprecated: Prefer including headers from js/public.
194 js/src/tests/style/BadIncludesOrder-inl.h:28:29: error:
195 "vm/JSScript.h" should be included after "vm/JSFunction.h"
197 (multiple files): error:
198 header files form one or more cycles
200 tests/style/HeaderCycleA1.h
201 -> tests/style/HeaderCycleA2.h
202 -> tests/style/HeaderCycleA3.h
203 -> tests/style/HeaderCycleA1.h
205 tests/style/HeaderCycleB1-inl.h
206 -> tests/style/HeaderCycleB2-inl.h
207 -> tests/style/HeaderCycleB3-inl.h
208 -> tests/style/HeaderCycleB4-inl.h
209 -> tests/style/HeaderCycleB1-inl.h
210 -> tests/style/jsheadercycleB5inlines.h
211 -> tests/style/HeaderCycleB1-inl.h
212 -> tests/style/HeaderCycleB4-inl.h
223 actual_output
.append(line
+ "\n")
226 def error(filename
, linenum
, *lines
):
228 if linenum
is not None:
229 location
+= ":" + str(linenum
)
230 out(location
+ ": error:")
236 class FileKind(object):
246 if filename
.endswith(".c"):
249 if filename
.endswith(".cpp"):
252 if filename
.endswith(("inlines.h", "-inl.h")):
253 return FileKind
.INL_H
255 if filename
.endswith(".h"):
258 if filename
.endswith(".tbl"):
261 if filename
.endswith(".msg"):
264 error(filename
, None, "unknown file kind")
267 def check_style(enable_fixup
):
268 # We deal with two kinds of name.
269 # - A "filename" is a full path to a file from the repository root.
270 # - An "inclname" is how a file is referred to in a #include statement.
272 # Examples (filename -> inclname)
273 # - "mfbt/Attributes.h" -> "mozilla/Attributes.h"
274 # - "mozglue/misc/TimeStamp.h -> "mozilla/TimeStamp.h"
275 # - "memory/mozalloc/mozalloc.h -> "mozilla/mozalloc.h"
276 # - "js/public/Vector.h" -> "js/Vector.h"
277 # - "js/src/vm/String.h" -> "vm/String.h"
285 non_js_inclnames
= set() # type: set(inclname)
286 js_names
= dict() # type: dict(filename, inclname)
288 # Process files in js/src.
289 js_src_root
= os
.path
.join("js", "src")
290 for dirpath
, dirnames
, filenames
in os
.walk(js_src_root
):
291 if dirpath
== js_src_root
:
292 # Skip any subdirectories that contain a config.status file
295 for dirname
in dirnames
:
296 path
= os
.path
.join(dirpath
, dirname
, "config.status")
297 if os
.path
.isfile(path
):
298 builddirs
.append(dirname
)
299 for dirname
in builddirs
:
300 dirnames
.remove(dirname
)
301 for filename
in filenames
:
302 filepath
= os
.path
.join(dirpath
, filename
).replace("\\", "/")
303 if not filepath
.startswith(
304 tuple(ignored_js_src_dirs
)
305 ) and filepath
.endswith((".c", ".cpp", ".h", ".tbl", ".msg")):
306 inclname
= filepath
[len("js/src/") :]
307 js_names
[filepath
] = inclname
309 # Look for header files in directories in non_js_dirnames.
310 for non_js_dir
in non_js_dirnames
:
311 for dirpath
, dirnames
, filenames
in os
.walk(non_js_dir
):
312 for filename
in filenames
:
313 if filename
.endswith(".h"):
314 inclname
= "mozilla/" + filename
315 if non_js_dir
== "intl/components/":
316 inclname
= "mozilla/intl/" + filename
317 non_js_inclnames
.add(inclname
)
319 # Look for header files in js/public.
320 js_public_root
= os
.path
.join("js", "public")
321 for dirpath
, dirnames
, filenames
in os
.walk(js_public_root
):
322 for filename
in filenames
:
323 if filename
.endswith((".h", ".msg")):
324 filepath
= os
.path
.join(dirpath
, filename
).replace("\\", "/")
325 inclname
= "js/" + filepath
[len("js/public/") :]
326 js_names
[filepath
] = inclname
328 all_inclnames
= non_js_inclnames |
set(js_names
.values())
330 edges
= dict() # type: dict(inclname, set(inclname))
332 # We don't care what's inside the MFBT and MOZALLOC files, but because they
333 # are #included from JS files we have to add them to the inclusion graph.
334 for inclname
in non_js_inclnames
:
335 edges
[inclname
] = set()
337 # Process all the JS files.
338 for filename
in sorted(js_names
.keys()):
339 inclname
= js_names
[filename
]
340 file_kind
= FileKind
.get(filename
)
342 file_kind
== FileKind
.C
343 or file_kind
== FileKind
.CPP
344 or file_kind
== FileKind
.H
345 or file_kind
== FileKind
.INL_H
347 included_h_inclnames
= set() # type: set(inclname)
349 with
open(filename
, encoding
="utf-8") as f
:
353 code
= code
.sorted(inclname
)
354 with
open(filename
, "w") as f
:
355 f
.write(code
.to_source())
358 filename
, inclname
, file_kind
, code
, all_inclnames
, included_h_inclnames
361 edges
[inclname
] = included_h_inclnames
363 find_cycles(all_inclnames
, edges
)
365 # Compare expected and actual output.
366 difflines
= difflib
.unified_diff(
369 fromfile
="check_spidermonkey_style.py expected output",
370 tofile
="check_spidermonkey_style.py actual output",
373 for diffline
in difflines
:
375 print(diffline
, end
="")
380 def module_name(name
):
381 """Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename."""
384 name
.replace("inlines.h", "")
385 .replace("-inl.h", "")
391 def is_module_header(enclosing_inclname
, header_inclname
):
392 """Determine if an included name is the "module header", i.e. should be
393 first in the file."""
395 module
= module_name(enclosing_inclname
)
397 # Normal case, for example:
398 # module == "vm/Runtime", header_inclname == "vm/Runtime.h".
399 if module
== module_name(header_inclname
):
402 # A public header, for example:
404 # module == "vm/CharacterEncoding",
405 # header_inclname == "js/CharacterEncoding.h"
407 # or (for implementation files for js/public/*/*.h headers)
409 # module == "vm/SourceHook",
410 # header_inclname == "js/experimental/SourceHook.h"
411 m
= re
.match(r
"js\/.*?([^\/]+)\.h", header_inclname
)
412 if m
is not None and module
.endswith("/" + m
.group(1)):
418 class Include(object):
419 """Important information for a single #include statement."""
421 def __init__(self
, include_prefix
, inclname
, line_suffix
, linenum
, is_system
):
422 self
.include_prefix
= include_prefix
423 self
.line_suffix
= line_suffix
424 self
.inclname
= inclname
425 self
.linenum
= linenum
426 self
.is_system
= is_system
428 def is_style_relevant(self
):
429 # Includes are style-relevant; that is, they're checked by the pairwise
430 # style-checking algorithm in check_file.
433 def section(self
, enclosing_inclname
):
434 """Identify which section inclname belongs to.
436 The section numbers are as follows.
437 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp)
440 3. jsfoo.h, prmjtime.h, etc
444 7. non-.h, e.g. *.tbl, *.msg (these can be scattered throughout files)
450 if not self
.inclname
.endswith(".h"):
453 # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need
455 if is_module_header(enclosing_inclname
, self
.inclname
):
458 if "/" in self
.inclname
:
459 if self
.inclname
.startswith("mozilla/"):
462 if self
.inclname
.endswith("-inl.h"):
467 if self
.inclname
.endswith("inlines.h"):
474 return "<" + self
.inclname
+ ">"
476 return '"' + self
.inclname
+ '"'
478 def sort_key(self
, enclosing_inclname
):
479 return (self
.section(enclosing_inclname
), self
.inclname
.lower())
482 return self
.include_prefix
+ self
.quote() + self
.line_suffix
+ "\n"
485 class CppBlock(object):
486 """C preprocessor block: a whole file or a single #if/#elif/#else block.
488 A #if/#endif block is the contents of a #if/#endif (or similar) section.
489 The top-level block, which is not within a #if/#endif pair, is also
492 Each kid is either an Include (representing a #include), OrdinaryCode, or
493 a nested CppBlock."""
495 def __init__(self
, start_line
=""):
496 self
.start
= start_line
500 def is_style_relevant(self
):
503 def append_ordinary_line(self
, line
):
504 if len(self
.kids
) == 0 or not isinstance(self
.kids
[-1], OrdinaryCode
):
505 self
.kids
.append(OrdinaryCode())
506 self
.kids
[-1].lines
.append(line
)
508 def style_relevant_kids(self
):
509 """Return a list of kids in this block that are style-relevant."""
510 return [kid
for kid
in self
.kids
if kid
.is_style_relevant()]
512 def sorted(self
, enclosing_inclname
):
513 """Return a hopefully-sorted copy of this block. Implements --fixup.
515 When in doubt, this leaves the code unchanged.
518 def pretty_sorted_includes(includes
):
519 """Return a new list containing the given includes, in order,
520 with blank lines separating sections."""
521 keys
= [inc
.sort_key(enclosing_inclname
) for inc
in includes
]
522 if sorted(keys
) == keys
:
523 return includes
# if nothing is out of order, don't touch anything
526 current_section
= None
527 for (section
, _
), inc
in sorted(zip(keys
, includes
)):
528 if current_section
is not None and section
!= current_section
:
529 output
.append(OrdinaryCode(["\n"])) # blank line
531 current_section
= section
534 def should_try_to_sort(includes
):
535 if "tests/style/BadIncludes" in enclosing_inclname
:
536 return False # don't straighten the counterexample
537 if any(inc
.inclname
in oddly_ordered_inclnames
for inc
in includes
):
538 return False # don't sort batches containing odd includes
539 if includes
== sorted(
540 includes
, key
=lambda inc
: inc
.sort_key(enclosing_inclname
)
542 return False # it's already sorted, avoid whitespace-only fixups
545 # The content of the eventual output of this method.
548 # The current batch of includes to sort. This list only ever contains Include objects
549 # and whitespace OrdinaryCode objects.
553 """Sort the contents of `batch` and move it to `output`."""
556 isinstance(item
, Include
)
557 or (isinstance(item
, OrdinaryCode
) and "".join(item
.lines
).isspace())
561 # Here we throw away the blank lines.
562 # `pretty_sorted_includes` puts them back.
564 last_include_index
= -1
565 for i
, item
in enumerate(batch
):
566 if isinstance(item
, Include
):
567 includes
.append(item
)
568 last_include_index
= i
569 cutoff
= last_include_index
+ 1
571 if should_try_to_sort(includes
):
572 output
.extend(pretty_sorted_includes(includes
) + batch
[cutoff
:])
577 for kid
in self
.kids
:
578 if isinstance(kid
, CppBlock
):
580 output
.append(kid
.sorted(enclosing_inclname
))
581 elif isinstance(kid
, Include
):
584 assert isinstance(kid
, OrdinaryCode
)
585 if kid
.to_source().isspace():
593 result
.start
= self
.start
594 result
.end
= self
.end
599 return self
.start
+ "".join(kid
.to_source() for kid
in self
.kids
) + self
.end
602 class OrdinaryCode(object):
603 """A list of lines of code that aren't #include/#if/#else/#endif lines."""
605 def __init__(self
, lines
=None):
606 self
.lines
= lines
if lines
is not None else []
608 def is_style_relevant(self
):
612 return "".join(self
.lines
)
615 # A "snippet" is one of:
617 # * Include - representing an #include line
618 # * CppBlock - a whole file or #if/#elif/#else block; contains a list of snippets
619 # * OrdinaryCode - representing lines of non-#include-relevant code
623 block_stack
= [CppBlock()]
625 # Extract the #include statements as a tree of snippets.
626 for linenum
, line
in enumerate(f
, start
=1):
627 if line
.lstrip().startswith("#"):
628 # Look for a |#include "..."| line.
629 m
= re
.match(r
'(\s*#\s*include\s+)"([^"]*)"(.*)', line
)
631 prefix
, inclname
, suffix
= m
.groups()
632 block_stack
[-1].kids
.append(
633 Include(prefix
, inclname
, suffix
, linenum
, is_system
=False)
637 # Look for a |#include <...>| line.
638 m
= re
.match(r
"(\s*#\s*include\s+)<([^>]*)>(.*)", line
)
640 prefix
, inclname
, suffix
= m
.groups()
641 block_stack
[-1].kids
.append(
642 Include(prefix
, inclname
, suffix
, linenum
, is_system
=True)
646 # Look for a |#{if,ifdef,ifndef}| line.
647 m
= re
.match(r
"\s*#\s*(if|ifdef|ifndef)\b", line
)
650 new_block
= CppBlock(line
)
651 block_stack
[-1].kids
.append(new_block
)
652 block_stack
.append(new_block
)
655 # Look for a |#{elif,else}| line.
656 m
= re
.match(r
"\s*#\s*(elif|else)\b", line
)
658 # Close the current block, and open an adjacent one.
660 new_block
= CppBlock(line
)
661 block_stack
[-1].kids
.append(new_block
)
662 block_stack
.append(new_block
)
665 # Look for a |#endif| line.
666 m
= re
.match(r
"\s*#\s*endif\b", line
)
668 # Close the current block.
669 block_stack
.pop().end
= line
670 if len(block_stack
) == 0:
671 raise ValueError("#endif without #if at line " + str(linenum
))
674 # Otherwise, we have an ordinary line.
675 block_stack
[-1].append_ordinary_line(line
)
677 if len(block_stack
) > 1:
678 raise ValueError("unmatched #if")
679 return block_stack
[-1]
683 filename
, inclname
, file_kind
, code
, all_inclnames
, included_h_inclnames
685 def check_include_statement(include
):
686 """Check the style of a single #include statement."""
688 if include
.is_system
:
689 # Check it is not a known local file (in which case it's probably a system header).
691 include
.inclname
in included_inclnames_to_ignore
692 or include
.inclname
in all_inclnames
697 include
.quote() + " should be included using",
698 'the #include "..." form',
702 msg
= deprecated_inclnames
.get(include
.inclname
)
707 include
.quote() + " is deprecated: " + msg
,
710 if file_kind
== FileKind
.H
or file_kind
== FileKind
.INL_H
:
711 msg
= deprecated_inclnames_in_header
.get(include
.inclname
)
712 if msg
and filename
not in deprecated_inclnames_in_header_excludes
:
716 include
.quote() + " is deprecated: " + msg
,
719 if include
.inclname
not in included_inclnames_to_ignore
:
720 included_kind
= FileKind
.get(include
.inclname
)
722 # Check the #include path has the correct form.
723 if include
.inclname
not in all_inclnames
:
727 include
.quote() + " is included using the wrong path;",
728 "did you forget a prefix, or is the file not yet committed?",
731 # Record inclusions of .h files for cycle detection later.
732 # (Exclude .tbl and .msg files.)
733 elif included_kind
== FileKind
.H
or included_kind
== FileKind
.INL_H
:
734 included_h_inclnames
.add(include
.inclname
)
736 # Check a H file doesn't #include an INL_H file.
737 if file_kind
== FileKind
.H
and included_kind
== FileKind
.INL_H
:
741 "vanilla header includes an inline-header file "
745 # Check a file doesn't #include itself. (We do this here because the cycle
746 # detection below doesn't detect this case.)
747 if inclname
== include
.inclname
:
748 error(filename
, include
.linenum
, "the file includes itself")
750 def check_includes_order(include1
, include2
):
751 """Check the ordering of two #include statements."""
754 include1
.inclname
in oddly_ordered_inclnames
755 or include2
.inclname
in oddly_ordered_inclnames
759 section1
= include1
.section(inclname
)
760 section2
= include2
.section(inclname
)
761 if (section1
> section2
) or (
762 (section1
== section2
)
763 and (include1
.inclname
.lower() > include2
.inclname
.lower())
767 str(include1
.linenum
) + ":" + str(include2
.linenum
),
768 include1
.quote() + " should be included after " + include2
.quote(),
771 # Check the extracted #include statements, both individually, and the ordering of
772 # adjacent pairs that live in the same block.
773 def pair_traverse(prev
, this
):
774 if isinstance(this
, Include
):
775 check_include_statement(this
)
776 if isinstance(prev
, Include
):
777 check_includes_order(prev
, this
)
779 kids
= this
.style_relevant_kids()
780 for prev2
, this2
in zip([None] + kids
[0:-1], kids
):
781 pair_traverse(prev2
, this2
)
783 pair_traverse(None, code
)
786 def find_cycles(all_inclnames
, edges
):
787 """Find and draw any cycles."""
789 SCCs
= tarjan(all_inclnames
, edges
)
791 # The various sorted() calls below ensure the output is deterministic.
798 out(" " * indent
+ ("-> " if indent
else " ") + v
)
802 for succ
in sorted(edges
[v
]):
804 draw(succ
, indent
+ 1)
806 draw(sorted(c
)[0], 0)
809 have_drawn_an_SCC
= False
810 for scc
in sorted(SCCs
):
812 if not have_drawn_an_SCC
:
813 error("(multiple files)", None, "header files form one or more cycles")
814 have_drawn_an_SCC
= True
819 # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph.
820 # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
828 def strongconnect(v
, index
):
829 # Set the depth index for v to the smallest unused index
830 vertex_index
[v
] = index
831 vertex_lowlink
[v
] = index
835 # Consider successors of v
837 if w
not in vertex_index
:
838 # Successor w has not yet been visited; recurse on it
839 index
= strongconnect(w
, index
)
840 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_lowlink
[w
])
842 # Successor w is in stack S and hence in the current SCC
843 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_index
[w
])
845 # If v is a root node, pop the stack and generate an SCC
846 if vertex_lowlink
[v
] == vertex_index
[v
]:
855 if v
not in vertex_index
:
856 index
= strongconnect(v
, index
)
862 if sys
.argv
[1:] == ["--fixup"]:
863 # Sort #include directives in-place. Fixup mode doesn't solve
864 # all possible silliness that the script checks for; it's just a
865 # hack for the common case where renaming a header causes style
868 elif sys
.argv
[1:] == []:
872 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | unexpected command "
873 "line options: " + repr(sys
.argv
[1:])
877 ok
= check_style(fixup
)
880 print("TEST-PASS | check_spidermonkey_style.py | ok")
883 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
884 + "actual output does not match expected output; diff is above."
887 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
888 + "Hint: If the problem is that you renamed a header, and many #includes "
889 + "are no longer in alphabetical order, commit your work and then try "
890 + "`check_spidermonkey_style.py --fixup`. "
891 + "You need to commit first because --fixup modifies your files in place."
894 sys
.exit(0 if ok
else 1)
897 if __name__
== "__main__":