blob: 46d6167ba27f564ad30bb56fca58c266eaaa8d87 [file] [log] [blame]
Daniel Mensingerd7ea0662021-10-09 15:47:34 +02001#!/usr/bin/env python3
2
Dylan Bakere991c4d2023-12-13 11:38:41 -08003# SPDX-License-Identifier: Apache-2.0
Daniel Mensingerd7ea0662021-10-09 15:47:34 +02004# Copyright 2021 The Meson development team
5
6import argparse
7import json
8from pathlib import Path
9from copy import deepcopy
10
11import typing as T
12
13T_None = type(None)
14
15# Global root object
16root: dict
17
18def assert_has_typed_keys(path: str, data: dict, keys: T.Dict[str, T.Any]) -> dict:
19 assert set(data.keys()).issuperset(keys.keys()), f'{path}: DIFF: {set(data.keys()).difference(keys.keys())}'
20 res = dict()
21 for key, val in keys.items():
22 cur = data.pop(key)
23 assert isinstance(cur, val), f'{path}: type({key}: {cur}) != {val}'
24 res[key] = cur
25 return res
26
27def validate_base_obj(path: str, name: str, obj: dict) -> None:
28 expected: T.Dict[str, T.Any] = {
29 'name': str,
30 'description': str,
31 'since': (str, T_None),
32 'deprecated': (str, T_None),
33 'notes': list,
34 'warnings': list,
35 }
36 cur = assert_has_typed_keys(f'{path}.{name}', obj, expected)
37 assert cur['name'], f'{path}.{name}'
38 assert cur['description'], f'{path}.{name}'
39 assert cur['name'] == name, f'{path}.{name}'
40 assert all(isinstance(x, str) and x for x in cur['notes']), f'{path}.{name}'
41 assert all(isinstance(x, str) and x for x in cur['warnings']), f'{path}.{name}'
42
43def validate_type(path: str, typ: dict) -> None:
44 expected: T.Dict[str, T.Any] = {
45 'obj': str,
46 'holds': list,
47 }
48 cur = assert_has_typed_keys(path, typ, expected)
49 assert not typ, f'{path} has extra keys: {typ.keys()}'
50 assert cur['obj'] in root['objects'], path
51 for i in cur['holds']:
52 validate_type(path, i)
53
54def validate_arg(path: str, name: str, arg: dict) -> None:
55 validate_base_obj(path, name, arg)
56 expected: T.Dict[str, T.Any] = {
57 'type': list,
58 'type_str': str,
59 'required': bool,
60 'default': (str, T_None),
61 'min_varargs': (int, T_None),
62 'max_varargs': (int, T_None),
63 }
64 cur = assert_has_typed_keys(f'{path}.{name}', arg, expected)
65 assert not arg, f'{path}.{name} has extra keys: {arg.keys()}'
66 assert cur['type'], f'{path}.{name}'
67 assert cur['type_str'], f'{path}.{name}'
68 for i in cur['type']:
69 validate_type(f'{path}.{name}', i)
70 if cur['min_varargs'] is not None:
71 assert cur['min_varargs'] > 0, f'{path}.{name}'
72 if cur['max_varargs'] is not None:
73 assert cur['max_varargs'] > 0, f'{path}.{name}'
74
75def validate_function(path: str, name: str, func: dict) -> None:
76 validate_base_obj(path, name, func)
77 expected: T.Dict[str, T.Any] = {
78 'returns': list,
79 'returns_str': str,
80 'example': (str, T_None),
81 'posargs': dict,
82 'optargs': dict,
83 'kwargs': dict,
84 'varargs': (dict, T_None),
Daniel Mensingerf192ecd2022-01-23 16:03:41 +010085 'arg_flattening': bool,
Daniel Mensingerd7ea0662021-10-09 15:47:34 +020086 }
87 cur = assert_has_typed_keys(f'{path}.{name}', func, expected)
88 assert not func, f'{path}.{name} has extra keys: {func.keys()}'
89 assert cur['returns'], f'{path}.{name}'
90 assert cur['returns_str'], f'{path}.{name}'
91 for i in cur['returns']:
92 validate_type(f'{path}.{name}', i)
93 for k, v in cur['posargs'].items():
94 validate_arg(f'{path}.{name}', k, v)
95 for k, v in cur['optargs'].items():
96 validate_arg(f'{path}.{name}', k, v)
97 for k, v in cur['kwargs'].items():
98 validate_arg(f'{path}.{name}', k, v)
99 if cur['varargs']:
100 validate_arg(f'{path}.{name}', cur['varargs']['name'], cur['varargs'])
101
102def validate_object(path: str, name: str, obj: dict) -> None:
103 validate_base_obj(path, name, obj)
104 expected: T.Dict[str, T.Any] = {
105 'example': (str, T_None),
106 'object_type': str,
107 'methods': dict,
108 'is_container': bool,
109 'extends': (str, T_None),
110 'returned_by': list,
111 'extended_by': list,
112 'defined_by_module': (str, T_None),
113 }
114 cur = assert_has_typed_keys(f'{path}.{name}', obj, expected)
115 assert not obj, f'{path}.{name} has extra keys: {obj.keys()}'
116 for key, val in cur['methods'].items():
117 validate_function(f'{path}.{name}', key, val)
118 if cur['extends'] is not None:
119 assert cur['extends'] in root['objects'], f'{path}.{name}'
120 assert all(isinstance(x, str) for x in cur['returned_by']), f'{path}.{name}'
121 assert all(isinstance(x, str) for x in cur['extended_by']), f'{path}.{name}'
122 assert all(x in root['objects'] for x in cur['extended_by']), f'{path}.{name}'
123 if cur['defined_by_module'] is not None:
124 assert cur['defined_by_module'] in root['objects'], f'{path}.{name}'
125 assert cur['object_type'] == 'RETURNED', f'{path}.{name}'
126 assert root['objects'][cur['defined_by_module']]['object_type'] == 'MODULE', f'{path}.{name}'
127 assert name in root['objects_by_type']['modules'][cur['defined_by_module']], f'{path}.{name}'
128 return
129 assert cur['object_type'] in {'ELEMENTARY', 'BUILTIN', 'MODULE', 'RETURNED'}, f'{path}.{name}'
130 if cur['object_type'] == 'ELEMENTARY':
131 assert name in root['objects_by_type']['elementary'], f'{path}.{name}'
132 if cur['object_type'] == 'BUILTIN':
133 assert name in root['objects_by_type']['builtins'], f'{path}.{name}'
134 if cur['object_type'] == 'RETURNED':
135 assert name in root['objects_by_type']['returned'], f'{path}.{name}'
136 if cur['object_type'] == 'MODULE':
137 assert name in root['objects_by_type']['modules'], f'{path}.{name}'
138
139def main() -> int:
140 global root
141
142 parser = argparse.ArgumentParser(description='Meson JSON docs validator')
143 parser.add_argument('doc_file', type=Path, help='The JSON docs to validate')
144 args = parser.parse_args()
145
146 root_tmp = json.loads(args.doc_file.read_text(encoding='utf-8'))
147 root = deepcopy(root_tmp)
148 assert isinstance(root, dict)
149
150 expected: T.Dict[str, T.Any] = {
151 'version_major': int,
152 'version_minor': int,
153 'meson_version': str,
154 'functions': dict,
155 'objects': dict,
156 'objects_by_type': dict,
157 }
158 cur = assert_has_typed_keys('root', root_tmp, expected)
159 assert not root_tmp, f'root has extra keys: {root_tmp.keys()}'
160
161 refs = cur['objects_by_type']
162 expected = {
163 'elementary': list,
164 'builtins': list,
165 'returned': list,
166 'modules': dict,
167 }
168 assert_has_typed_keys(f'root.objects_by_type', refs, expected)
169 assert not refs, f'root.objects_by_type has extra keys: {refs.keys()}'
170 assert all(isinstance(x, str) for x in root['objects_by_type']['elementary'])
171 assert all(isinstance(x, str) for x in root['objects_by_type']['builtins'])
172 assert all(isinstance(x, str) for x in root['objects_by_type']['returned'])
173 assert all(isinstance(x, str) for x in root['objects_by_type']['modules'])
174 assert all(x in root['objects'] for x in root['objects_by_type']['elementary'])
175 assert all(x in root['objects'] for x in root['objects_by_type']['builtins'])
176 assert all(x in root['objects'] for x in root['objects_by_type']['returned'])
177 assert all(x in root['objects'] for x in root['objects_by_type']['modules'])
178 assert all(root['objects'][x]['object_type'] == 'ELEMENTARY' for x in root['objects_by_type']['elementary'])
179 assert all(root['objects'][x]['object_type'] == 'BUILTIN' for x in root['objects_by_type']['builtins'])
180 assert all(root['objects'][x]['object_type'] == 'RETURNED' for x in root['objects_by_type']['returned'])
181 assert all(root['objects'][x]['object_type'] == 'MODULE' for x in root['objects_by_type']['modules'])
182
183 # Check that module references are correct
184 assert all(all(isinstance(x, str) for x in v) for k, v in root['objects_by_type']['modules'].items())
185 assert all(all(x in root['objects'] for x in v) for k, v in root['objects_by_type']['modules'].items())
186 assert all(all(root['objects'][x]['defined_by_module'] == k for x in v) for k, v in root['objects_by_type']['modules'].items())
187
188 for key, val in cur['functions'].items():
189 validate_function('root', key, val)
190 for key, val in cur['objects'].items():
191 validate_object('root', key, val)
192
193 return 0
194
195if __name__ == '__main__':
196 raise SystemExit(main())