1# coding: utf8
2
3# IWYU is missing the feature to designate a certain header as a "forward-decls
4# header". In the case of SpiderMonkey, there are certain commonly used forward
5# declarations that are all gathered in js/TypeDecls.h.
6# We postprocess the IWYU output to fix this, and also fix the output format
7# which is quite verbose, making it tedious to scroll through for 60 files.
8
9import re
10import sys
11
12
13class Colors:
14    NORMAL = '\33[0m'
15    RED = '\33[31m'
16    GREEN = '\33[32m'
17
18
19ADD, REMOVE, FULL = range(3)
20state = None
21file = None
22add = {}
23remove = {}
24all_includes = {}
25there_were_errors = False
26
27# When encountering one of these lines, move to a different state
28MATCHERS = {
29    r'\.\./(.*) should add these lines:': ADD,
30    r'\.\./(.*) should remove these lines:': REMOVE,
31    r'The full include-list for \.\./(.*):': FULL,
32    r'\(\.\./(.*) has correct #includes/fwd-decls\)': None
33}
34
35FWD_HEADER = '#include <js/TypeDecls.h>'
36FWD_DECLS_IN_HEADER = (
37    'class JSAtom;',
38    'struct JSContext;',
39    'struct JSClass;',
40    'class JSFunction;',
41    'class JSFreeOp;',
42    'class JSObject;',
43    'struct JSRuntime;',
44    'class JSScript;',
45    'class JSString;',
46    'namespace js { class TempAllocPolicy; }'
47    'namespace JS { struct PropertyKey; }',
48    'namespace JS { class Symbol; }',
49    'namespace JS { class BigInt; }',
50    'namespace JS { class Value; }',
51    'namespace JS { class Compartment; }',
52    'namespace JS { class Realm; }',
53    'namespace JS { struct Runtime; }',
54    'namespace JS { class Zone; }',
55)
56add_fwd_header = False
57
58FALSE_POSITIVES = (
59    # The bodies of these structs already come before their usage,
60    # we don't need to have forward declarations of them as well
61    ('cjs/atoms.h', 'class GjsAtoms;', ''),
62    ('cjs/atoms.h', 'struct GjsSymbolAtom;', ''),
63
64    # IWYU weird false positive when using std::vector::emplace_back() or
65    # std::vector::push_back()
66    ('gi/private.cpp', '#include <algorithm>', 'for max'),
67    ('cjs/importer.cpp', '#include <algorithm>', 'for max'),
68    ('modules/cairo-context.cpp', '#include <algorithm>', 'for max'),
69
70    # Weird false positive on some versions of IWYU
71    ('gi/arg.cpp', 'struct _GHashTable;', ''),
72    ('gi/arg.cpp', 'struct _GVariant;', ''),
73)
74
75
76def output():
77    global file, state, add_fwd_header, there_were_errors
78
79    if add_fwd_header:
80        if FWD_HEADER not in all_includes:
81            if FWD_HEADER in remove:
82                remove.pop(FWD_HEADER, None)
83            else:
84                add[FWD_HEADER] = ''
85
86    if add or remove:
87        print(f'\n== {file} ==')
88        for line, why in add.items():
89            if why:
90                why = '  // ' + why
91            print(f'{Colors.GREEN}+{line}{Colors.NORMAL}{why}')
92        for line, why in remove.items():
93            if why:
94                why = '  // ' + why
95            print(f'{Colors.RED}-{line}{Colors.NORMAL}{why}')
96        there_were_errors = True
97
98    state = None
99    file = None
100    add.clear()
101    remove.clear()
102    all_includes.clear()
103    add_fwd_header = False
104
105
106for line in sys.stdin:
107    line = line.strip()
108    if not line:
109        continue
110
111    # filter out errors having to do with compiler arguments unknown to IWYU
112    if line.startswith('error:'):
113        continue
114
115    if line == '---':
116        output()
117        continue
118
119    state_changed = False
120    file_changed = False
121    for matcher, newstate in MATCHERS.items():
122        match = re.match(matcher, line)
123        if match:
124            state = newstate
125            if match.group(1) != file:
126                if file is not None:
127                    file_changed = True
128                file = match.group(1)
129            state_changed = True
130            break
131    if file_changed:
132        output()
133        continue
134    if state_changed:
135        continue
136
137    line, _, why = line.partition(' // ')
138    line = line.strip()
139    if state == ADD:
140        if line in FWD_DECLS_IN_HEADER:
141            add_fwd_header = True
142            continue
143        if (file, line, why) in FALSE_POSITIVES:
144            continue
145        add[line] = why
146    elif state == REMOVE:
147        if line.startswith('- '):
148            line = line[2:]
149        remove[line] = why
150    elif state == FULL:
151        all_includes[line] = why
152
153if there_were_errors:
154    sys.exit(1)
155