@@ -43,10 +43,6 @@ class FormatError(Error):
4343 pass
4444
4545
46- class ModifiedStubMarkedAsGeneratedError (Error ):
47- pass
48-
49-
5046class RootElementHasChildren (Error ):
5147 pass
5248
@@ -216,32 +212,11 @@ def get_classes_used_by(cls: ql.Class) -> typing.List[str]:
216212 return sorted (set (t for t in get_types_used_by (cls ) if t [0 ].isupper () and t != cls .name ))
217213
218214
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-
242215def 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" ))
216+ ql_files = [str (f ) for f in files if f .suffix in (".qll" , ".ql" )]
217+ if not ql_files :
218+ return
219+ format_cmd = [codeql , "query" , "format" , "--in-place" , "--" ] + ql_files
245220 res = subprocess .run (format_cmd , stderr = subprocess .PIPE , text = True )
246221 if res .returncode :
247222 for line in res .stderr .splitlines ():
@@ -307,11 +282,14 @@ def generate(opts, renderer):
307282 stub_out = opts .ql_stub_output
308283 test_out = opts .ql_test_output
309284 missing_test_source_filename = "MISSING_SOURCE.txt"
285+ include_file = stub_out .with_suffix (".qll" )
286+
287+ generated = {q for q in out .rglob ("*.qll" )}
288+ generated .add (include_file )
289+ generated .update (q for q in test_out .rglob ("*.ql" ))
290+ generated .update (q for q in test_out .rglob (missing_test_source_filename ))
310291
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 )}
292+ stubs = {q for q in stub_out .rglob ("*.qll" )}
315293
316294 data = schema .load_file (input )
317295
@@ -324,77 +302,75 @@ def generate(opts, renderer):
324302
325303 imports = {}
326304
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 )
305+ with renderer .manage (generated = generated , stubs = stubs , registry = opts .generated_registry ) as renderer :
306+
307+ db_classes = [cls for cls in classes .values () if not cls .ipa ]
308+ renderer .render (ql .DbClasses (db_classes ), out / "Raw.qll" )
309+
310+ classes_by_dir_and_name = sorted (classes .values (), key = lambda cls : (cls .dir , cls .name ))
311+ for c in classes_by_dir_and_name :
312+ imports [c .name ] = get_import (stub_out / c .path , opts .swift_dir )
313+
314+ for c in classes .values ():
315+ qll = out / c .path .with_suffix (".qll" )
316+ c .imports = [imports [t ] for t in get_classes_used_by (c )]
317+ renderer .render (c , qll )
318+ stub_file = stub_out / c .path .with_suffix (".qll" )
319+ if not renderer .is_customized_stub (stub_file ):
320+ stub = ql .Stub (name = c .name , base_import = get_import (qll , opts .swift_dir ))
321+ renderer .render (stub , stub_file )
322+
323+ # for example path/to/elements -> path/to/elements.qll
324+ renderer .render (ql .ImportList (list (imports .values ())), include_file )
325+
326+ renderer .render (ql .GetParentImplementation (list (classes .values ())), out / 'ParentChild.qll' )
327+
328+ for c in data .classes .values ():
329+ if _should_skip_qltest (c , data .classes ):
330+ continue
331+ test_dir = test_out / c .group / c .name
332+ test_dir .mkdir (parents = True , exist_ok = True )
333+ if not any (test_dir .glob ("*.swift" )):
334+ log .warning (f"no test source in { test_dir .relative_to (test_out )} " )
335+ renderer .render (ql .MissingTestInstructions (),
336+ test_dir / missing_test_source_filename )
337+ continue
338+ total_props , partial_props = _partition (_get_all_properties_to_be_tested (c , data .classes ),
339+ lambda p : p .is_single or p .is_predicate )
340+ renderer .render (ql .ClassTester (class_name = c .name ,
341+ properties = total_props ,
342+ # in case of collapsed hierarchies we want to see the actual QL class in results
343+ show_ql_class = "qltest_collapse_hierarchy" in c .pragmas ),
344+ test_dir / f"{ c .name } .ql" )
345+ for p in partial_props :
346+ renderer .render (ql .PropertyTester (class_name = c .name ,
347+ property = p ), test_dir / f"{ c .name } _{ p .getter } .ql" )
348+
349+ final_ipa_types = []
350+ non_final_ipa_types = []
351+ constructor_imports = []
352+ ipa_constructor_imports = []
353+ stubs = {}
354+ for cls in sorted (data .classes .values (), key = lambda cls : (cls .group , cls .name )):
355+ ipa_type = get_ql_ipa_class (cls )
356+ if ipa_type .is_final :
357+ final_ipa_types .append (ipa_type )
358+ if ipa_type .has_params :
359+ stub_file = stub_out / cls .group / f"{ cls .name } Constructor.qll"
360+ if not renderer .is_customized_stub (stub_file ):
361+ # stub rendering must be postponed as we might not have yet all subtracted ipa types in `ipa_type`
362+ stubs [stub_file ] = ql .Synth .ConstructorStub (ipa_type )
363+ constructor_import = get_import (stub_file , opts .swift_dir )
364+ constructor_imports .append (constructor_import )
365+ if ipa_type .is_ipa :
366+ ipa_constructor_imports .append (constructor_import )
367+ else :
368+ non_final_ipa_types .append (ipa_type )
369+
370+ for stub_file , data in stubs .items ():
371+ renderer .render (data , stub_file )
372+ renderer .render (ql .Synth .Types (root .name , final_ipa_types , non_final_ipa_types ), out / "Synth.qll" )
373+ renderer .render (ql .ImportList (constructor_imports ), out / "SynthConstructors.qll" )
374+ renderer .render (ql .ImportList (ipa_constructor_imports ), out / "PureSynthConstructors.qll" )
375+ if opts .ql_format :
376+ format (opts .codeql_binary , renderer .written )
0 commit comments