@@ -216,32 +216,11 @@ def get_classes_used_by(cls: ql.Class) -> typing.List[str]:
216
216
return sorted (set (t for t in get_types_used_by (cls ) if t [0 ].isupper () and t != cls .name ))
217
217
218
218
219
- _generated_stub_re = re .compile (r"\n*private import .*\n+class \w+ extends Generated::\w+ \{[ \n]?\}" , re .MULTILINE )
220
-
221
-
222
- def _is_generated_stub (file : pathlib .Path ) -> bool :
223
- with open (file ) as contents :
224
- for line in contents :
225
- if not line .startswith ("// generated" ):
226
- return False
227
- break
228
- else :
229
- # no lines
230
- return False
231
- # we still do not detect modified synth constructors
232
- if not file .name .endswith ("Constructor.qll" ):
233
- # one line already read, if we can read 5 other we are past the normal stub generation
234
- line_threshold = 5
235
- first_lines = list (itertools .islice (contents , line_threshold ))
236
- if len (first_lines ) == line_threshold or not _generated_stub_re .match ("" .join (first_lines )):
237
- raise ModifiedStubMarkedAsGeneratedError (
238
- f"{ file .name } stub was modified but is still marked as generated" )
239
- return True
240
-
241
-
242
219
def format (codeql , files ):
243
- format_cmd = [codeql , "query" , "format" , "--in-place" , "--" ]
244
- format_cmd .extend (str (f ) for f in files if f .suffix in (".qll" , ".ql" ))
220
+ ql_files = [str (f ) for f in files if f .suffix in (".qll" , ".ql" )]
221
+ if not ql_files :
222
+ return
223
+ format_cmd = [codeql , "query" , "format" , "--in-place" , "--" ] + ql_files
245
224
res = subprocess .run (format_cmd , stderr = subprocess .PIPE , text = True )
246
225
if res .returncode :
247
226
for line in res .stderr .splitlines ():
@@ -307,11 +286,14 @@ def generate(opts, renderer):
307
286
stub_out = opts .ql_stub_output
308
287
test_out = opts .ql_test_output
309
288
missing_test_source_filename = "MISSING_SOURCE.txt"
289
+ include_file = stub_out .with_suffix (".qll" )
290
+
291
+ generated = {q for q in out .rglob ("*.qll" )}
292
+ generated .add (include_file )
293
+ generated .update (q for q in test_out .rglob ("*.ql" ))
294
+ generated .update (q for q in test_out .rglob (missing_test_source_filename ))
310
295
311
- existing = {q for q in out .rglob ("*.qll" )}
312
- existing |= {q for q in stub_out .rglob ("*.qll" ) if _is_generated_stub (q )}
313
- existing |= {q for q in test_out .rglob ("*.ql" )}
314
- existing |= {q for q in test_out .rglob (missing_test_source_filename )}
296
+ stubs = {q for q in stub_out .rglob ("*.qll" )}
315
297
316
298
data = schema .load_file (input )
317
299
@@ -324,77 +306,75 @@ def generate(opts, renderer):
324
306
325
307
imports = {}
326
308
327
- db_classes = [cls for cls in classes .values () if not cls .ipa ]
328
- renderer .render (ql .DbClasses (db_classes ), out / "Raw.qll" )
329
-
330
- classes_by_dir_and_name = sorted (classes .values (), key = lambda cls : (cls .dir , cls .name ))
331
- for c in classes_by_dir_and_name :
332
- imports [c .name ] = get_import (stub_out / c .path , opts .swift_dir )
333
-
334
- for c in classes .values ():
335
- qll = out / c .path .with_suffix (".qll" )
336
- c .imports = [imports [t ] for t in get_classes_used_by (c )]
337
- renderer .render (c , qll )
338
- stub_file = stub_out / c .path .with_suffix (".qll" )
339
- if not stub_file .is_file () or _is_generated_stub (stub_file ):
340
- stub = ql .Stub (
341
- name = c .name , base_import = get_import (qll , opts .swift_dir ))
342
- renderer .render (stub , stub_file )
343
-
344
- # for example path/to/elements -> path/to/elements.qll
345
- include_file = stub_out .with_suffix (".qll" )
346
- renderer .render (ql .ImportList (list (imports .values ())), include_file )
347
-
348
- renderer .render (ql .GetParentImplementation (list (classes .values ())), out / 'ParentChild.qll' )
349
-
350
- for c in data .classes .values ():
351
- if _should_skip_qltest (c , data .classes ):
352
- continue
353
- test_dir = test_out / c .group / c .name
354
- test_dir .mkdir (parents = True , exist_ok = True )
355
- if not any (test_dir .glob ("*.swift" )):
356
- log .warning (f"no test source in { test_dir .relative_to (test_out )} " )
357
- renderer .render (ql .MissingTestInstructions (),
358
- test_dir / missing_test_source_filename )
359
- continue
360
- total_props , partial_props = _partition (_get_all_properties_to_be_tested (c , data .classes ),
361
- lambda p : p .is_single or p .is_predicate )
362
- renderer .render (ql .ClassTester (class_name = c .name ,
363
- properties = total_props ,
364
- # in case of collapsed hierarchies we want to see the actual QL class in results
365
- show_ql_class = "qltest_collapse_hierarchy" in c .pragmas ),
366
- test_dir / f"{ c .name } .ql" )
367
- for p in partial_props :
368
- renderer .render (ql .PropertyTester (class_name = c .name ,
369
- property = p ), test_dir / f"{ c .name } _{ p .getter } .ql" )
370
-
371
- final_ipa_types = []
372
- non_final_ipa_types = []
373
- constructor_imports = []
374
- ipa_constructor_imports = []
375
- stubs = {}
376
- for cls in sorted (data .classes .values (), key = lambda cls : (cls .group , cls .name )):
377
- ipa_type = get_ql_ipa_class (cls )
378
- if ipa_type .is_final :
379
- final_ipa_types .append (ipa_type )
380
- if ipa_type .has_params :
381
- stub_file = stub_out / cls .group / f"{ cls .name } Constructor.qll"
382
- if not stub_file .is_file () or _is_generated_stub (stub_file ):
383
- # stub rendering must be postponed as we might not have yet all subtracted ipa types in `ipa_type`
384
- stubs [stub_file ] = ql .Synth .ConstructorStub (ipa_type )
385
- constructor_import = get_import (stub_file , opts .swift_dir )
386
- constructor_imports .append (constructor_import )
387
- if ipa_type .is_ipa :
388
- ipa_constructor_imports .append (constructor_import )
389
- else :
390
- non_final_ipa_types .append (ipa_type )
391
-
392
- for stub_file , data in stubs .items ():
393
- renderer .render (data , stub_file )
394
- renderer .render (ql .Synth .Types (root .name , final_ipa_types , non_final_ipa_types ), out / "Synth.qll" )
395
- renderer .render (ql .ImportList (constructor_imports ), out / "SynthConstructors.qll" )
396
- renderer .render (ql .ImportList (ipa_constructor_imports ), out / "PureSynthConstructors.qll" )
397
-
398
- renderer .cleanup (existing )
399
- if opts .ql_format :
400
- format (opts .codeql_binary , renderer .written )
309
+ with renderer .manage (generated = generated , stubs = stubs , registry = opts .generated_registry ) as renderer :
310
+
311
+ db_classes = [cls for cls in classes .values () if not cls .ipa ]
312
+ renderer .render (ql .DbClasses (db_classes ), out / "Raw.qll" )
313
+
314
+ classes_by_dir_and_name = sorted (classes .values (), key = lambda cls : (cls .dir , cls .name ))
315
+ for c in classes_by_dir_and_name :
316
+ imports [c .name ] = get_import (stub_out / c .path , opts .swift_dir )
317
+
318
+ for c in classes .values ():
319
+ qll = out / c .path .with_suffix (".qll" )
320
+ c .imports = [imports [t ] for t in get_classes_used_by (c )]
321
+ renderer .render (c , qll )
322
+ stub_file = stub_out / c .path .with_suffix (".qll" )
323
+ if not renderer .is_customized_stub (stub_file ):
324
+ stub = ql .Stub (name = c .name , base_import = get_import (qll , opts .swift_dir ))
325
+ renderer .render (stub , stub_file )
326
+
327
+ # for example path/to/elements -> path/to/elements.qll
328
+ renderer .render (ql .ImportList (list (imports .values ())), include_file )
329
+
330
+ renderer .render (ql .GetParentImplementation (list (classes .values ())), out / 'ParentChild.qll' )
331
+
332
+ for c in data .classes .values ():
333
+ if _should_skip_qltest (c , data .classes ):
334
+ continue
335
+ test_dir = test_out / c .group / c .name
336
+ test_dir .mkdir (parents = True , exist_ok = True )
337
+ if not any (test_dir .glob ("*.swift" )):
338
+ log .warning (f"no test source in { test_dir .relative_to (test_out )} " )
339
+ renderer .render (ql .MissingTestInstructions (),
340
+ test_dir / missing_test_source_filename )
341
+ continue
342
+ total_props , partial_props = _partition (_get_all_properties_to_be_tested (c , data .classes ),
343
+ lambda p : p .is_single or p .is_predicate )
344
+ renderer .render (ql .ClassTester (class_name = c .name ,
345
+ properties = total_props ,
346
+ # in case of collapsed hierarchies we want to see the actual QL class in results
347
+ show_ql_class = "qltest_collapse_hierarchy" in c .pragmas ),
348
+ test_dir / f"{ c .name } .ql" )
349
+ for p in partial_props :
350
+ renderer .render (ql .PropertyTester (class_name = c .name ,
351
+ property = p ), test_dir / f"{ c .name } _{ p .getter } .ql" )
352
+
353
+ final_ipa_types = []
354
+ non_final_ipa_types = []
355
+ constructor_imports = []
356
+ ipa_constructor_imports = []
357
+ stubs = {}
358
+ for cls in sorted (data .classes .values (), key = lambda cls : (cls .group , cls .name )):
359
+ ipa_type = get_ql_ipa_class (cls )
360
+ if ipa_type .is_final :
361
+ final_ipa_types .append (ipa_type )
362
+ if ipa_type .has_params :
363
+ stub_file = stub_out / cls .group / f"{ cls .name } Constructor.qll"
364
+ if not renderer .is_customized_stub (stub_file ):
365
+ # stub rendering must be postponed as we might not have yet all subtracted ipa types in `ipa_type`
366
+ stubs [stub_file ] = ql .Synth .ConstructorStub (ipa_type )
367
+ constructor_import = get_import (stub_file , opts .swift_dir )
368
+ constructor_imports .append (constructor_import )
369
+ if ipa_type .is_ipa :
370
+ ipa_constructor_imports .append (constructor_import )
371
+ else :
372
+ non_final_ipa_types .append (ipa_type )
373
+
374
+ for stub_file , data in stubs .items ():
375
+ renderer .render (data , stub_file )
376
+ renderer .render (ql .Synth .Types (root .name , final_ipa_types , non_final_ipa_types ), out / "Synth.qll" )
377
+ renderer .render (ql .ImportList (constructor_imports ), out / "SynthConstructors.qll" )
378
+ renderer .render (ql .ImportList (ipa_constructor_imports ), out / "PureSynthConstructors.qll" )
379
+ if opts .ql_format :
380
+ format (opts .codeql_binary , renderer .written )
0 commit comments