@@ -9,6 +9,7 @@ import { SemVer } from "semver";
99import type { Readable } from "stream" ;
1010import tk from "tree-kill" ;
1111import type { CancellationToken , Disposable , Uri } from "vscode" ;
12+ import { dir } from "tmp-promise" ;
1213
1314import type {
1415 BqrsInfo ,
@@ -202,9 +203,11 @@ interface BqrsDecodeOptions {
202203 entities ?: string [ ] ;
203204}
204205
205- type OnLineCallback = (
206- line : string ,
207- ) => Promise < string | undefined > | string | undefined ;
206+ interface BqrsDiffOptions {
207+ retainResultSets ?: string [ ] ;
208+ }
209+
210+ type OnLineCallback = ( line : string ) => Promise < string | undefined > ;
208211
209212type VersionChangedListener = (
210213 newVersionAndFeatures : VersionAndFeatures | undefined ,
@@ -368,12 +371,11 @@ export class CodeQLCliServer implements Disposable {
368371 */
369372 private async launchProcess ( ) : Promise < ChildProcessWithoutNullStreams > {
370373 const codeQlPath = await this . getCodeQlPath ( ) ;
371- const args = [ ] ;
372- if ( shouldDebugCliServer ( ) ) {
373- args . push (
374- "-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9012,server=n,suspend=y,quiet=y" ,
375- ) ;
376- }
374+ const args = shouldDebugCliServer ( )
375+ ? [
376+ "-J=-agentlib:jdwp=transport=dt_socket,address=localhost:9012,server=n,suspend=y,quiet=y" ,
377+ ]
378+ : [ ] ;
377379
378380 return spawnServer (
379381 codeQlPath ,
@@ -399,15 +401,11 @@ export class CodeQLCliServer implements Disposable {
399401 }
400402 this . commandInProcess = true ;
401403 try {
402- //Launch the process if it doesn't exist
403- if ( ! this . process ) {
404- this . process = await this . launchProcess ( ) ;
405- }
406- // Grab the process so that typescript know that it is always defined.
407- const process = this . process ;
404+ // Launch the process if it doesn't exist
405+ this . process ??= await this . launchProcess ( ) ;
408406
409407 // Compute the full args array
410- const args = command . concat ( LOGGING_FLAGS ) . concat ( commandArgs ) ;
408+ const args = command . concat ( LOGGING_FLAGS , commandArgs ) ;
411409 const argsString = args . join ( " " ) ;
412410 // If we are running silently, we don't want to print anything to the console.
413411 if ( ! silent ) {
@@ -416,7 +414,7 @@ export class CodeQLCliServer implements Disposable {
416414 ) ;
417415 }
418416 try {
419- return await this . handleProcessOutput ( process , {
417+ return await this . handleProcessOutput ( this . process , {
420418 handleNullTerminator : true ,
421419 onListenStart : ( process ) => {
422420 // Write the command followed by a null terminator.
@@ -451,7 +449,7 @@ export class CodeQLCliServer implements Disposable {
451449 ) : Promise < string > {
452450 const codeqlPath = await this . getCodeQlPath ( ) ;
453451
454- const args = command . concat ( LOGGING_FLAGS ) . concat ( commandArgs ) ;
452+ const args = command . concat ( LOGGING_FLAGS , commandArgs ) ;
455453 const argsString = args . join ( " " ) ;
456454
457455 // If we are running silently, we don't want to print anything to the console.
@@ -569,16 +567,15 @@ export class CodeQLCliServer implements Disposable {
569567
570568 stdoutBuffers . push ( newData ) ;
571569
572- if ( handleNullTerminator ) {
570+ if (
571+ handleNullTerminator &&
573572 // If the buffer ends in '0' then exit.
574573 // We don't have to check the middle as no output will be written after the null until
575574 // the next command starts
576- if (
577- newData . length > 0 &&
578- newData . readUInt8 ( newData . length - 1 ) === 0
579- ) {
580- resolve ( ) ;
581- }
575+ newData . length > 0 &&
576+ newData . readUInt8 ( newData . length - 1 ) === 0
577+ ) {
578+ resolve ( ) ;
582579 }
583580 } ;
584581 stderrListener = ( newData : Buffer ) => {
@@ -693,9 +690,7 @@ export class CodeQLCliServer implements Disposable {
693690 */
694691 private runNext ( ) : void {
695692 const callback = this . commandQueue . shift ( ) ;
696- if ( callback ) {
697- callback ( ) ;
698- }
693+ callback ?.( ) ;
699694 }
700695
701696 /**
@@ -813,7 +808,7 @@ export class CodeQLCliServer implements Disposable {
813808 * is false or not specified, this option is ignored.
814809 * @returns The contents of the command's stdout, if the command succeeded.
815810 */
816- runCodeQlCliCommand (
811+ private runCodeQlCliCommand (
817812 command : string [ ] ,
818813 commandArgs : string [ ] ,
819814 description : string ,
@@ -825,9 +820,7 @@ export class CodeQLCliServer implements Disposable {
825820 token,
826821 } : RunOptions = { } ,
827822 ) : Promise < string > {
828- if ( progressReporter ) {
829- progressReporter . report ( { message : description } ) ;
830- }
823+ progressReporter ?. report ( { message : description } ) ;
831824
832825 if ( runInNewProcess ) {
833826 return this . runCodeQlCliInNewProcess (
@@ -874,18 +867,17 @@ export class CodeQLCliServer implements Disposable {
874867 * @param progressReporter Used to output progress messages, e.g. to the status bar.
875868 * @returns The contents of the command's stdout, if the command succeeded.
876869 */
877- async runJsonCodeQlCliCommand < OutputType > (
870+ private async runJsonCodeQlCliCommand < OutputType > (
878871 command : string [ ] ,
879872 commandArgs : string [ ] ,
880873 description : string ,
881874 { addFormat = true , ...runOptions } : JsonRunOptions = { } ,
882875 ) : Promise < OutputType > {
883- let args : string [ ] = [ ] ;
884- if ( addFormat ) {
876+ const args = [
885877 // Add format argument first, in case commandArgs contains positional parameters.
886- args = args . concat ( [ "--format" , "json" ] ) ;
887- }
888- args = args . concat ( commandArgs ) ;
878+ ... ( addFormat ? [ "--format" , "json" ] : [ ] ) ,
879+ ... commandArgs ,
880+ ] ;
889881 const result = await this . runCodeQlCliCommand (
890882 command ,
891883 args ,
@@ -922,7 +914,7 @@ export class CodeQLCliServer implements Disposable {
922914 * @param runOptions Options for running the command.
923915 * @returns The contents of the command's stdout, if the command succeeded.
924916 */
925- async runJsonCodeQlCliCommandWithAuthentication < OutputType > (
917+ private async runJsonCodeQlCliCommandWithAuthentication < OutputType > (
926918 command : string [ ] ,
927919 commandArgs : string [ ] ,
928920 description : string ,
@@ -1226,8 +1218,8 @@ export class CodeQLCliServer implements Disposable {
12261218 }
12271219
12281220 /**
1229- * Gets the results from a bqrs.
1230- * @param bqrsPath The path to the bqrs.
1221+ * Gets the results from a bqrs file .
1222+ * @param bqrsPath The path to the bqrs file .
12311223 * @param resultSet The result set to get.
12321224 * @param options Optional BqrsDecodeOptions arguments
12331225 */
@@ -1240,30 +1232,62 @@ export class CodeQLCliServer implements Disposable {
12401232 `--entities=${ entities . join ( "," ) } ` ,
12411233 "--result-set" ,
12421234 resultSet ,
1243- ]
1244- . concat ( pageSize ? [ "--rows " , pageSize . toString ( ) ] : [ ] )
1245- . concat ( offset ? [ "--start-at" , offset . toString ( ) ] : [ ] )
1246- . concat ( [ bqrsPath ] ) ;
1247- return await this . runJsonCodeQlCliCommand < DecodedBqrsChunk > (
1235+ ... ( pageSize ? [ "--rows" , pageSize . toString ( ) ] : [ ] ) ,
1236+ ... ( offset ? [ "--start-at " , offset . toString ( ) ] : [ ] ) ,
1237+ bqrsPath ,
1238+ ] ;
1239+ return this . runJsonCodeQlCliCommand < DecodedBqrsChunk > (
12481240 [ "bqrs" , "decode" ] ,
12491241 subcommandArgs ,
12501242 "Reading bqrs data" ,
12511243 ) ;
12521244 }
12531245
12541246 /**
1255- * Gets all results from a bqrs.
1256- * @param bqrsPath The path to the bqrs.
1247+ * Gets all results from a bqrs file .
1248+ * @param bqrsPath The path to the bqrs file .
12571249 */
12581250 async bqrsDecodeAll ( bqrsPath : string ) : Promise < DecodedBqrs > {
1259- return await this . runJsonCodeQlCliCommand < DecodedBqrs > (
1251+ return this . runJsonCodeQlCliCommand < DecodedBqrs > (
12601252 [ "bqrs" , "decode" ] ,
12611253 [ bqrsPath ] ,
12621254 "Reading all bqrs data" ,
12631255 ) ;
12641256 }
12651257
1266- async runInterpretCommand (
1258+ /** Gets the difference between two bqrs files. */
1259+ async bqrsDiff (
1260+ bqrsPath1 : string ,
1261+ bqrsPath2 : string ,
1262+ options ?: BqrsDiffOptions ,
1263+ ) : Promise < {
1264+ uniquePath1 : string ;
1265+ uniquePath2 : string ;
1266+ path : string ;
1267+ cleanup : ( ) => Promise < void > ;
1268+ } > {
1269+ const { path, cleanup } = await dir ( { unsafeCleanup : true } ) ;
1270+ const uniquePath1 = join ( path , "left.bqrs" ) ;
1271+ const uniquePath2 = join ( path , "right.bqrs" ) ;
1272+ await this . runCodeQlCliCommand (
1273+ [ "bqrs" , "diff" ] ,
1274+ [
1275+ "--left" ,
1276+ uniquePath1 ,
1277+ "--right" ,
1278+ uniquePath2 ,
1279+ ...( options ?. retainResultSets
1280+ ? [ "--retain-result-sets" , options . retainResultSets . join ( "," ) ]
1281+ : [ ] ) ,
1282+ bqrsPath1 ,
1283+ bqrsPath2 ,
1284+ ] ,
1285+ "Diffing bqrs files" ,
1286+ ) ;
1287+ return { uniquePath1, uniquePath2, path, cleanup } ;
1288+ }
1289+
1290+ private async runInterpretCommand (
12671291 format : string ,
12681292 additonalArgs : string [ ] ,
12691293 metadata : QueryMetadata ,
@@ -1278,21 +1302,22 @@ export class CodeQLCliServer implements Disposable {
12781302 format ,
12791303 // Forward all of the query metadata.
12801304 ...Object . entries ( metadata ) . map ( ( [ key , value ] ) => `-t=${ key } =${ value } ` ) ,
1281- ] . concat ( additonalArgs ) ;
1282- if ( sourceInfo !== undefined ) {
1283- args . push (
1284- "--source-archive" ,
1285- sourceInfo . sourceArchive ,
1286- "--source-location-prefix" ,
1287- sourceInfo . sourceLocationPrefix ,
1288- ) ;
1289- }
1290-
1291- args . push ( "--threads" , this . cliConfig . numberThreads . toString ( ) ) ;
1292-
1293- args . push ( "--max-paths" , this . cliConfig . maxPaths . toString ( ) ) ;
1305+ ...additonalArgs ,
1306+ ...( sourceInfo !== undefined
1307+ ? [
1308+ "--source-archive" ,
1309+ sourceInfo . sourceArchive ,
1310+ "--source-location-prefix" ,
1311+ sourceInfo . sourceLocationPrefix ,
1312+ ]
1313+ : [ ] ) ,
1314+ "--threads" ,
1315+ this . cliConfig . numberThreads . toString ( ) ,
1316+ "--max-paths" ,
1317+ this . cliConfig . maxPaths . toString ( ) ,
1318+ resultsPath ,
1319+ ] ;
12941320
1295- args . push ( resultsPath ) ;
12961321 await this . runCodeQlCliCommand (
12971322 [ "bqrs" , "interpret" ] ,
12981323 args ,
@@ -1797,7 +1822,7 @@ export class CodeQLCliServer implements Disposable {
17971822 * Spawns a child server process using the CodeQL CLI
17981823 * and attaches listeners to it.
17991824 *
1800- * @param config The configuration containing the path to the CLI.
1825+ * @param codeqlPath The configuration containing the path to the CLI.
18011826 * @param name Name of the server being started, to be shown in log and error messages.
18021827 * @param command The `codeql` command to be run, provided as an array of command/subcommand names.
18031828 * @param commandArgs The arguments to pass to the `codeql` command.
@@ -1823,9 +1848,9 @@ export function spawnServer(
18231848 // Start the server process.
18241849 const base = codeqlPath ;
18251850 const argsString = args . join ( " " ) ;
1826- if ( progressReporter !== undefined ) {
1827- progressReporter . report ( { message : `Starting ${ name } ` } ) ;
1828- }
1851+
1852+ progressReporter ? .report ( { message : `Starting ${ name } ` } ) ;
1853+
18291854 void logger . log ( `Starting ${ name } using CodeQL CLI: ${ base } ${ argsString } ` ) ;
18301855 const child = spawnChildProcess ( base , args ) ;
18311856 if ( ! child || ! child . pid ) {
@@ -1859,9 +1884,8 @@ export function spawnServer(
18591884 child . stdout . on ( "data" , stdoutListener ) ;
18601885 }
18611886
1862- if ( progressReporter !== undefined ) {
1863- progressReporter . report ( { message : `Started ${ name } ` } ) ;
1864- }
1887+ progressReporter ?. report ( { message : `Started ${ name } ` } ) ;
1888+
18651889 void logger . log ( `${ name } started on PID: ${ child . pid } ` ) ;
18661890 return child ;
18671891}
0 commit comments