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(
60 "ffi.h", # generated in ctypes/libffi/
61 "devtools/Instruments.h", # we ignore devtools/ in general
62 "double-conversion/double-conversion.h", # strange MFBT case
63 "javascript-trace.h", # generated in $OBJDIR if HAVE_DTRACE is defined
64 "frontend/ReservedWordsGenerated.h", # generated in $OBJDIR
65 "frontend/smoosh_generated.h", # generated in $OBJDIR
66 "gc/StatsPhasesGenerated.h", # generated in $OBJDIR
67 "gc/StatsPhasesGenerated.inc", # 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 "jscustomallocator.h", # provided by embedders; allowed to be missing
73 "js-config.h", # generated in $OBJDIR
75 "FuzzerDefs.h", # included without a path
76 "FuzzingInterface.h", # included without a path
77 "mozmemory.h", # included without a path
83 "private/pprio.h", # NSPR
89 "selfhosted.out.h", # generated in $OBJDIR
90 "shellmoduleloader.out.h", # generated in $OBJDIR
91 "unicode/basictz.h", # ICU
92 "unicode/locid.h", # ICU
93 "unicode/plurrule.h", # ICU
94 "unicode/putil.h", # ICU
95 "unicode/timezone.h", # ICU
96 "unicode/ucal.h", # ICU
97 "unicode/uchar.h", # ICU
98 "unicode/uclean.h", # ICU
99 "unicode/ucol.h", # ICU
100 "unicode/ucurr.h", # ICU
101 "unicode/udat.h", # ICU
102 "unicode/udata.h", # ICU
103 "unicode/udateintervalformat.h", # ICU
104 "unicode/udatpg.h", # ICU
105 "unicode/udisplaycontext.h", # ICU
106 "unicode/uenum.h", # ICU
107 "unicode/ufieldpositer.h", # ICU
108 "unicode/uformattedvalue.h", # ICU
109 "unicode/ulistformatter.h", # ICU
110 "unicode/uldnames.h", # ICU
111 "unicode/uloc.h", # ICU
112 "unicode/umachine.h", # ICU
113 "unicode/uniset.h", # ICU
114 "unicode/unistr.h", # ICU
115 "unicode/unorm2.h", # ICU
116 "unicode/unum.h", # ICU
117 "unicode/unumberformatter.h", # ICU
118 "unicode/unumsys.h", # ICU
119 "unicode/upluralrules.h", # ICU
120 "unicode/ureldatefmt.h", # ICU
121 "unicode/ures.h", # ICU
122 "unicode/ustring.h", # ICU
123 "unicode/utypes.h", # ICU
124 "unicode/uversion.h", # ICU
125 "vtune/VTuneWrapper.h", # VTune
126 "wasm/WasmIntrinsicGenerated.h", # generated in $OBJDIR"
127 "zydis/ZydisAPI.h", # Zydis
131 deprecated_inclnames
= {
132 "mozilla/Unused.h": "Use [[nodiscard]] and (void)expr casts instead.",
135 # JSAPI functions should be included through headers from js/public instead of
136 # using the old, catch-all jsapi.h file.
137 deprecated_inclnames_in_header
= {
138 "jsapi.h": "Prefer including headers from js/public.",
141 # Temporary exclusions for files which still need to include jsapi.h.
142 deprecated_inclnames_in_header_excludes
= {
143 "js/src/vm/Compartment-inl.h", # for JS::InformalValueTypeName
144 "js/src/jsapi-tests/tests.h", # for JS_ValueToSource
147 # These files have additional constraints on where they are #included, so we
148 # ignore #includes of them when checking #include ordering.
149 oddly_ordered_inclnames
= set(
151 "ctypes/typedefs.h", # Included multiple times in the body of ctypes/CTypes.h
152 # Included in the body of frontend/TokenStream.h
153 "frontend/ReservedWordsGenerated.h",
154 "gc/StatsPhasesGenerated.h", # Included in the body of gc/Statistics.h
155 "gc/StatsPhasesGenerated.inc", # Included in the body of gc/Statistics.cpp
156 "psapi.h", # Must be included after "util/WindowsWrapper.h" on Windows
157 "machine/endian.h", # Must be included after <sys/types.h> on BSD
158 "winbase.h", # Must precede other system headers(?)
159 "windef.h", # Must precede other system headers(?)
163 # The files in tests/style/ contain code that fails this checking in various
164 # ways. Here is the output we expect. If the actual output differs from
165 # this, one of the following must have happened.
166 # - New SpiderMonkey code violates one of the checked rules.
167 # - The tests/style/ files have changed without expected_output being changed
169 # - This script has been broken somehow.
171 expected_output
= """\
172 js/src/tests/style/BadIncludes.h:3: error:
173 the file includes itself
175 js/src/tests/style/BadIncludes.h:6: error:
176 "BadIncludes2.h" is included using the wrong path;
177 did you forget a prefix, or is the file not yet committed?
179 js/src/tests/style/BadIncludes.h:8: error:
180 <tests/style/BadIncludes2.h> should be included using
181 the #include "..." form
183 js/src/tests/style/BadIncludes.h:10: error:
184 "stdio.h" is included using the wrong path;
185 did you forget a prefix, or is the file not yet committed?
187 js/src/tests/style/BadIncludes.h:12: error:
188 "mozilla/Unused.h" is deprecated: Use [[nodiscard]] and (void)expr casts instead.
190 js/src/tests/style/BadIncludes2.h:1: error:
191 vanilla header includes an inline-header file "tests/style/BadIncludes2-inl.h"
193 js/src/tests/style/BadIncludesOrder-inl.h:5:6: error:
194 "vm/JSScript-inl.h" should be included after "vm/Interpreter-inl.h"
196 js/src/tests/style/BadIncludesOrder-inl.h:6:7: error:
197 "vm/Interpreter-inl.h" should be included after "js/Value.h"
199 js/src/tests/style/BadIncludesOrder-inl.h:7:8: error:
200 "js/Value.h" should be included after "ds/LifoAlloc.h"
202 js/src/tests/style/BadIncludesOrder-inl.h:9: error:
203 "jsapi.h" is deprecated: Prefer including headers from js/public.
205 js/src/tests/style/BadIncludesOrder-inl.h:8:9: error:
206 "ds/LifoAlloc.h" should be included after "jsapi.h"
208 js/src/tests/style/BadIncludesOrder-inl.h:9:10: error:
209 "jsapi.h" should be included after <stdio.h>
211 js/src/tests/style/BadIncludesOrder-inl.h:10:11: error:
212 <stdio.h> should be included after "mozilla/HashFunctions.h"
214 js/src/tests/style/BadIncludesOrder-inl.h:20: error:
215 "jsapi.h" is deprecated: Prefer including headers from js/public.
217 js/src/tests/style/BadIncludesOrder-inl.h:28:29: error:
218 "vm/JSScript.h" should be included after "vm/JSFunction.h"
220 (multiple files): error:
221 header files form one or more cycles
223 tests/style/HeaderCycleA1.h
224 -> tests/style/HeaderCycleA2.h
225 -> tests/style/HeaderCycleA3.h
226 -> tests/style/HeaderCycleA1.h
228 tests/style/HeaderCycleB1-inl.h
229 -> tests/style/HeaderCycleB2-inl.h
230 -> tests/style/HeaderCycleB3-inl.h
231 -> tests/style/HeaderCycleB4-inl.h
232 -> tests/style/HeaderCycleB1-inl.h
233 -> tests/style/jsheadercycleB5inlines.h
234 -> tests/style/HeaderCycleB1-inl.h
235 -> tests/style/HeaderCycleB4-inl.h
246 actual_output
.append(line
+ "\n")
249 def error(filename
, linenum
, *lines
):
251 if linenum
is not None:
252 location
+= ":" + str(linenum
)
253 out(location
+ ": error:")
259 class FileKind(object):
269 if filename
.endswith(".c"):
272 if filename
.endswith(".cpp"):
275 if filename
.endswith(("inlines.h", "-inl.h")):
276 return FileKind
.INL_H
278 if filename
.endswith(".h"):
281 if filename
.endswith(".tbl"):
284 if filename
.endswith(".msg"):
287 error(filename
, None, "unknown file kind")
290 def check_style(enable_fixup
):
291 # We deal with two kinds of name.
292 # - A "filename" is a full path to a file from the repository root.
293 # - An "inclname" is how a file is referred to in a #include statement.
295 # Examples (filename -> inclname)
296 # - "mfbt/Attributes.h" -> "mozilla/Attributes.h"
297 # - "mozglue/misc/TimeStamp.h -> "mozilla/TimeStamp.h"
298 # - "memory/mozalloc/mozalloc.h -> "mozilla/mozalloc.h"
299 # - "js/public/Vector.h" -> "js/Vector.h"
300 # - "js/src/vm/String.h" -> "vm/String.h"
308 non_js_inclnames
= set() # type: set(inclname)
309 js_names
= dict() # type: dict(filename, inclname)
311 # Process files in js/src.
312 js_src_root
= os
.path
.join("js", "src")
313 for dirpath
, dirnames
, filenames
in os
.walk(js_src_root
):
314 if dirpath
== js_src_root
:
315 # Skip any subdirectories that contain a config.status file
318 for dirname
in dirnames
:
319 path
= os
.path
.join(dirpath
, dirname
, "config.status")
320 if os
.path
.isfile(path
):
321 builddirs
.append(dirname
)
322 for dirname
in builddirs
:
323 dirnames
.remove(dirname
)
324 for filename
in filenames
:
325 filepath
= os
.path
.join(dirpath
, filename
).replace("\\", "/")
326 if not filepath
.startswith(
327 tuple(ignored_js_src_dirs
)
328 ) and filepath
.endswith((".c", ".cpp", ".h", ".tbl", ".msg")):
329 inclname
= filepath
[len("js/src/") :]
330 js_names
[filepath
] = inclname
332 # Look for header files in directories in non_js_dirnames.
333 for non_js_dir
in non_js_dirnames
:
334 for dirpath
, dirnames
, filenames
in os
.walk(non_js_dir
):
335 for filename
in filenames
:
336 if filename
.endswith(".h"):
337 inclname
= "mozilla/" + filename
338 if non_js_dir
== "intl/components/":
339 inclname
= "mozilla/intl/" + filename
340 non_js_inclnames
.add(inclname
)
342 # Look for header files in js/public.
343 js_public_root
= os
.path
.join("js", "public")
344 for dirpath
, dirnames
, filenames
in os
.walk(js_public_root
):
345 for filename
in filenames
:
346 if filename
.endswith((".h", ".msg")):
347 filepath
= os
.path
.join(dirpath
, filename
).replace("\\", "/")
348 inclname
= "js/" + filepath
[len("js/public/") :]
349 js_names
[filepath
] = inclname
351 all_inclnames
= non_js_inclnames |
set(js_names
.values())
353 edges
= dict() # type: dict(inclname, set(inclname))
355 # We don't care what's inside the MFBT and MOZALLOC files, but because they
356 # are #included from JS files we have to add them to the inclusion graph.
357 for inclname
in non_js_inclnames
:
358 edges
[inclname
] = set()
360 # Process all the JS files.
361 for filename
in sorted(js_names
.keys()):
362 inclname
= js_names
[filename
]
363 file_kind
= FileKind
.get(filename
)
365 file_kind
== FileKind
.C
366 or file_kind
== FileKind
.CPP
367 or file_kind
== FileKind
.H
368 or file_kind
== FileKind
.INL_H
370 included_h_inclnames
= set() # type: set(inclname)
372 with
open(filename
, encoding
="utf-8") as f
:
376 code
= code
.sorted(inclname
)
377 with
open(filename
, "w") as f
:
378 f
.write(code
.to_source())
381 filename
, inclname
, file_kind
, code
, all_inclnames
, included_h_inclnames
384 edges
[inclname
] = included_h_inclnames
386 find_cycles(all_inclnames
, edges
)
388 # Compare expected and actual output.
389 difflines
= difflib
.unified_diff(
392 fromfile
="check_spidermonkey_style.py expected output",
393 tofile
="check_spidermonkey_style.py actual output",
396 for diffline
in difflines
:
398 print(diffline
, end
="")
403 def module_name(name
):
404 """Strip the trailing .cpp, .h, inlines.h or -inl.h from a filename."""
407 name
.replace("inlines.h", "")
408 .replace("-inl.h", "")
414 def is_module_header(enclosing_inclname
, header_inclname
):
415 """Determine if an included name is the "module header", i.e. should be
416 first in the file."""
418 module
= module_name(enclosing_inclname
)
420 # Normal case, for example:
421 # module == "vm/Runtime", header_inclname == "vm/Runtime.h".
422 if module
== module_name(header_inclname
):
425 # A public header, for example:
427 # module == "vm/CharacterEncoding",
428 # header_inclname == "js/CharacterEncoding.h"
430 # or (for implementation files for js/public/*/*.h headers)
432 # module == "vm/SourceHook",
433 # header_inclname == "js/experimental/SourceHook.h"
434 m
= re
.match(r
"js\/.*?([^\/]+)\.h", header_inclname
)
435 if m
is not None and module
.endswith("/" + m
.group(1)):
441 class Include(object):
442 """Important information for a single #include statement."""
444 def __init__(self
, include_prefix
, inclname
, line_suffix
, linenum
, is_system
):
445 self
.include_prefix
= include_prefix
446 self
.line_suffix
= line_suffix
447 self
.inclname
= inclname
448 self
.linenum
= linenum
449 self
.is_system
= is_system
451 def is_style_relevant(self
):
452 # Includes are style-relevant; that is, they're checked by the pairwise
453 # style-checking algorithm in check_file.
456 def section(self
, enclosing_inclname
):
457 """Identify which section inclname belongs to.
459 The section numbers are as follows.
460 0. Module header (e.g. jsfoo.h or jsfooinlines.h within jsfoo.cpp)
463 3. jsfoo.h, prmjtime.h, etc
467 7. non-.h, e.g. *.tbl, *.msg (these can be scattered throughout files)
473 if not self
.inclname
.endswith(".h"):
476 # A couple of modules have the .h file in js/ and the .cpp file elsewhere and so need
478 if is_module_header(enclosing_inclname
, self
.inclname
):
481 if "/" in self
.inclname
:
482 if self
.inclname
.startswith("mozilla/"):
485 if self
.inclname
.endswith("-inl.h"):
490 if self
.inclname
.endswith("inlines.h"):
497 return "<" + self
.inclname
+ ">"
499 return '"' + self
.inclname
+ '"'
501 def sort_key(self
, enclosing_inclname
):
502 return (self
.section(enclosing_inclname
), self
.inclname
.lower())
505 return self
.include_prefix
+ self
.quote() + self
.line_suffix
+ "\n"
508 class CppBlock(object):
509 """C preprocessor block: a whole file or a single #if/#elif/#else block.
511 A #if/#endif block is the contents of a #if/#endif (or similar) section.
512 The top-level block, which is not within a #if/#endif pair, is also
515 Each kid is either an Include (representing a #include), OrdinaryCode, or
516 a nested CppBlock."""
518 def __init__(self
, start_line
=""):
519 self
.start
= start_line
523 def is_style_relevant(self
):
526 def append_ordinary_line(self
, line
):
527 if len(self
.kids
) == 0 or not isinstance(self
.kids
[-1], OrdinaryCode
):
528 self
.kids
.append(OrdinaryCode())
529 self
.kids
[-1].lines
.append(line
)
531 def style_relevant_kids(self
):
532 """ Return a list of kids in this block that are style-relevant. """
533 return [kid
for kid
in self
.kids
if kid
.is_style_relevant()]
535 def sorted(self
, enclosing_inclname
):
536 """Return a hopefully-sorted copy of this block. Implements --fixup.
538 When in doubt, this leaves the code unchanged.
541 def pretty_sorted_includes(includes
):
542 """Return a new list containing the given includes, in order,
543 with blank lines separating sections."""
544 keys
= [inc
.sort_key(enclosing_inclname
) for inc
in includes
]
545 if sorted(keys
) == keys
:
546 return includes
# if nothing is out of order, don't touch anything
549 current_section
= None
550 for (section
, _
), inc
in sorted(zip(keys
, includes
)):
551 if current_section
is not None and section
!= current_section
:
552 output
.append(OrdinaryCode(["\n"])) # blank line
554 current_section
= section
557 def should_try_to_sort(includes
):
558 if "tests/style/BadIncludes" in enclosing_inclname
:
559 return False # don't straighten the counterexample
560 if any(inc
.inclname
in oddly_ordered_inclnames
for inc
in includes
):
561 return False # don't sort batches containing odd includes
562 if includes
== sorted(
563 includes
, key
=lambda inc
: inc
.sort_key(enclosing_inclname
)
565 return False # it's already sorted, avoid whitespace-only fixups
568 # The content of the eventual output of this method.
571 # The current batch of includes to sort. This list only ever contains Include objects
572 # and whitespace OrdinaryCode objects.
576 """Sort the contents of `batch` and move it to `output`."""
579 isinstance(item
, Include
)
580 or (isinstance(item
, OrdinaryCode
) and "".join(item
.lines
).isspace())
584 # Here we throw away the blank lines.
585 # `pretty_sorted_includes` puts them back.
587 last_include_index
= -1
588 for i
, item
in enumerate(batch
):
589 if isinstance(item
, Include
):
590 includes
.append(item
)
591 last_include_index
= i
592 cutoff
= last_include_index
+ 1
594 if should_try_to_sort(includes
):
595 output
.extend(pretty_sorted_includes(includes
) + batch
[cutoff
:])
600 for kid
in self
.kids
:
601 if isinstance(kid
, CppBlock
):
603 output
.append(kid
.sorted(enclosing_inclname
))
604 elif isinstance(kid
, Include
):
607 assert isinstance(kid
, OrdinaryCode
)
608 if kid
.to_source().isspace():
616 result
.start
= self
.start
617 result
.end
= self
.end
622 return self
.start
+ "".join(kid
.to_source() for kid
in self
.kids
) + self
.end
625 class OrdinaryCode(object):
626 """ A list of lines of code that aren't #include/#if/#else/#endif lines. """
628 def __init__(self
, lines
=None):
629 self
.lines
= lines
if lines
is not None else []
631 def is_style_relevant(self
):
635 return "".join(self
.lines
)
638 # A "snippet" is one of:
640 # * Include - representing an #include line
641 # * CppBlock - a whole file or #if/#elif/#else block; contains a list of snippets
642 # * OrdinaryCode - representing lines of non-#include-relevant code
646 block_stack
= [CppBlock()]
648 # Extract the #include statements as a tree of snippets.
649 for linenum
, line
in enumerate(f
, start
=1):
650 if line
.lstrip().startswith("#"):
651 # Look for a |#include "..."| line.
652 m
= re
.match(r
'(\s*#\s*include\s+)"([^"]*)"(.*)', line
)
654 prefix
, inclname
, suffix
= m
.groups()
655 block_stack
[-1].kids
.append(
656 Include(prefix
, inclname
, suffix
, linenum
, is_system
=False)
660 # Look for a |#include <...>| line.
661 m
= re
.match(r
"(\s*#\s*include\s+)<([^>]*)>(.*)", line
)
663 prefix
, inclname
, suffix
= m
.groups()
664 block_stack
[-1].kids
.append(
665 Include(prefix
, inclname
, suffix
, linenum
, is_system
=True)
669 # Look for a |#{if,ifdef,ifndef}| line.
670 m
= re
.match(r
"\s*#\s*(if|ifdef|ifndef)\b", line
)
673 new_block
= CppBlock(line
)
674 block_stack
[-1].kids
.append(new_block
)
675 block_stack
.append(new_block
)
678 # Look for a |#{elif,else}| line.
679 m
= re
.match(r
"\s*#\s*(elif|else)\b", line
)
681 # Close the current block, and open an adjacent one.
683 new_block
= CppBlock(line
)
684 block_stack
[-1].kids
.append(new_block
)
685 block_stack
.append(new_block
)
688 # Look for a |#endif| line.
689 m
= re
.match(r
"\s*#\s*endif\b", line
)
691 # Close the current block.
692 block_stack
.pop().end
= line
693 if len(block_stack
) == 0:
694 raise ValueError("#endif without #if at line " + str(linenum
))
697 # Otherwise, we have an ordinary line.
698 block_stack
[-1].append_ordinary_line(line
)
700 if len(block_stack
) > 1:
701 raise ValueError("unmatched #if")
702 return block_stack
[-1]
706 filename
, inclname
, file_kind
, code
, all_inclnames
, included_h_inclnames
708 def check_include_statement(include
):
709 """Check the style of a single #include statement."""
711 if include
.is_system
:
712 # Check it is not a known local file (in which case it's probably a system header).
714 include
.inclname
in included_inclnames_to_ignore
715 or include
.inclname
in all_inclnames
720 include
.quote() + " should be included using",
721 'the #include "..." form',
725 msg
= deprecated_inclnames
.get(include
.inclname
)
730 include
.quote() + " is deprecated: " + msg
,
733 if file_kind
== FileKind
.H
or file_kind
== FileKind
.INL_H
:
734 msg
= deprecated_inclnames_in_header
.get(include
.inclname
)
735 if msg
and filename
not in deprecated_inclnames_in_header_excludes
:
739 include
.quote() + " is deprecated: " + msg
,
742 if include
.inclname
not in included_inclnames_to_ignore
:
743 included_kind
= FileKind
.get(include
.inclname
)
745 # Check the #include path has the correct form.
746 if include
.inclname
not in all_inclnames
:
750 include
.quote() + " is included using the wrong path;",
751 "did you forget a prefix, or is the file not yet committed?",
754 # Record inclusions of .h files for cycle detection later.
755 # (Exclude .tbl and .msg files.)
756 elif included_kind
== FileKind
.H
or included_kind
== FileKind
.INL_H
:
757 included_h_inclnames
.add(include
.inclname
)
759 # Check a H file doesn't #include an INL_H file.
760 if file_kind
== FileKind
.H
and included_kind
== FileKind
.INL_H
:
764 "vanilla header includes an inline-header file "
768 # Check a file doesn't #include itself. (We do this here because the cycle
769 # detection below doesn't detect this case.)
770 if inclname
== include
.inclname
:
771 error(filename
, include
.linenum
, "the file includes itself")
773 def check_includes_order(include1
, include2
):
774 """Check the ordering of two #include statements."""
777 include1
.inclname
in oddly_ordered_inclnames
778 or include2
.inclname
in oddly_ordered_inclnames
782 section1
= include1
.section(inclname
)
783 section2
= include2
.section(inclname
)
784 if (section1
> section2
) or (
785 (section1
== section2
)
786 and (include1
.inclname
.lower() > include2
.inclname
.lower())
790 str(include1
.linenum
) + ":" + str(include2
.linenum
),
791 include1
.quote() + " should be included after " + include2
.quote(),
794 # Check the extracted #include statements, both individually, and the ordering of
795 # adjacent pairs that live in the same block.
796 def pair_traverse(prev
, this
):
797 if isinstance(this
, Include
):
798 check_include_statement(this
)
799 if isinstance(prev
, Include
):
800 check_includes_order(prev
, this
)
802 kids
= this
.style_relevant_kids()
803 for prev2
, this2
in zip([None] + kids
[0:-1], kids
):
804 pair_traverse(prev2
, this2
)
806 pair_traverse(None, code
)
809 def find_cycles(all_inclnames
, edges
):
810 """Find and draw any cycles."""
812 SCCs
= tarjan(all_inclnames
, edges
)
814 # The various sorted() calls below ensure the output is deterministic.
821 out(" " * indent
+ ("-> " if indent
else " ") + v
)
825 for succ
in sorted(edges
[v
]):
827 draw(succ
, indent
+ 1)
829 draw(sorted(c
)[0], 0)
832 have_drawn_an_SCC
= False
833 for scc
in sorted(SCCs
):
835 if not have_drawn_an_SCC
:
836 error("(multiple files)", None, "header files form one or more cycles")
837 have_drawn_an_SCC
= True
842 # Tarjan's algorithm for finding the strongly connected components (SCCs) of a graph.
843 # https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
851 def strongconnect(v
, index
):
852 # Set the depth index for v to the smallest unused index
853 vertex_index
[v
] = index
854 vertex_lowlink
[v
] = index
858 # Consider successors of v
860 if w
not in vertex_index
:
861 # Successor w has not yet been visited; recurse on it
862 index
= strongconnect(w
, index
)
863 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_lowlink
[w
])
865 # Successor w is in stack S and hence in the current SCC
866 vertex_lowlink
[v
] = min(vertex_lowlink
[v
], vertex_index
[w
])
868 # If v is a root node, pop the stack and generate an SCC
869 if vertex_lowlink
[v
] == vertex_index
[v
]:
878 if v
not in vertex_index
:
879 index
= strongconnect(v
, index
)
885 if sys
.argv
[1:] == ["--fixup"]:
886 # Sort #include directives in-place. Fixup mode doesn't solve
887 # all possible silliness that the script checks for; it's just a
888 # hack for the common case where renaming a header causes style
891 elif sys
.argv
[1:] == []:
895 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | unexpected command "
896 "line options: " + repr(sys
.argv
[1:])
900 ok
= check_style(fixup
)
903 print("TEST-PASS | check_spidermonkey_style.py | ok")
906 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
907 + "actual output does not match expected output; diff is above."
910 "TEST-UNEXPECTED-FAIL | check_spidermonkey_style.py | "
911 + "Hint: If the problem is that you renamed a header, and many #includes "
912 + "are no longer in alphabetical order, commit your work and then try "
913 + "`check_spidermonkey_style.py --fixup`. "
914 + "You need to commit first because --fixup modifies your files in place."
917 sys
.exit(0 if ok
else 1)
920 if __name__
== "__main__":