diff --git a/src/codeql.test.ts b/src/codeql.test.ts index 2d665b5a3..0db008fb2 100644 --- a/src/codeql.test.ts +++ b/src/codeql.test.ts @@ -63,3 +63,35 @@ test('parse codeql bundle url version', t => { } } }); + +test('getExtraOptions works for explicit paths', t => { + t.deepEqual(codeql.getExtraOptions({}, ['foo'], []), []); + + t.deepEqual(codeql.getExtraOptions({foo: [42]}, ['foo'], []), ['42']); + + t.deepEqual(codeql.getExtraOptions({foo: {bar: [42]}}, ['foo', 'bar'], []), ['42']); +}); + +test('getExtraOptions works for wildcards', t => { + t.deepEqual(codeql.getExtraOptions({'*': [42]}, ['foo'], []), ['42']); +}); + +test('getExtraOptions works for wildcards and explicit paths', t => { + let o1 = {'*': [42], foo: [87]}; + t.deepEqual(codeql.getExtraOptions(o1, ['foo'], []), ['42', '87']); + + let o2 = {'*': [42], foo: [87]}; + t.deepEqual(codeql.getExtraOptions(o2, ['foo', 'bar'], []), ['42']); + + let o3 = {'*': [42], foo: { '*': [87], bar: [99]}}; + let p = ['foo', 'bar']; + t.deepEqual(codeql.getExtraOptions(o3, p, []), ['42', '87', '99']); +}); + +test('getExtraOptions throws for bad content', t => { + t.throws(() => codeql.getExtraOptions({'*': 42}, ['foo'], [])); + + t.throws(() => codeql.getExtraOptions({foo: 87}, ['foo'], [])); + + t.throws(() => codeql.getExtraOptions({'*': [42], foo: { '*': 87, bar: [99]}}, ['foo', 'bar'], [])); +}); diff --git a/src/codeql.ts b/src/codeql.ts index 7ab4692d9..e05f524bf 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -307,6 +307,7 @@ function getCodeQLForCmd(cmd: string): CodeQL { 'trace-command', databasePath, ...compilerSpecArg, + ...getExtraOptionsFromEnv(['database', 'trace-command']), process.execPath, path.resolve(__dirname, 'tracer-env.js'), envFile @@ -320,6 +321,7 @@ function getCodeQLForCmd(cmd: string): CodeQL { databasePath, '--language=' + language, '--source-root=' + sourceRoot, + ...getExtraOptionsFromEnv(['database', 'init']), ]); }, runAutobuild: async function(language: string) { @@ -345,7 +347,8 @@ function getCodeQLForCmd(cmd: string): CodeQL { 'resolve', 'extractor', '--format=json', - '--language=' + language + '--language=' + language, + ...getExtraOptionsFromEnv(['resolve', 'extractor']), ], { silent: true, @@ -363,6 +366,7 @@ function getCodeQLForCmd(cmd: string): CodeQL { await exec.exec(cmd, [ 'database', 'trace-command', + ...getExtraOptionsFromEnv(['database', 'trace-command']), databasePath, '--', traceCommand @@ -372,6 +376,7 @@ function getCodeQLForCmd(cmd: string): CodeQL { await exec.exec(cmd, [ 'database', 'finalize', + ...getExtraOptionsFromEnv(['database', 'finalize']), databasePath ]); }, @@ -380,7 +385,8 @@ function getCodeQLForCmd(cmd: string): CodeQL { 'resolve', 'queries', ...queries, - '--format=bylanguage' + '--format=bylanguage', + ...getExtraOptionsFromEnv(['resolve', 'queries']) ]; if (extraSearchPath !== undefined) { codeqlArgs.push('--search-path', extraSearchPath); @@ -406,6 +412,7 @@ function getCodeQLForCmd(cmd: string): CodeQL { '--format=sarif-latest', '--output=' + sarifFile, '--no-sarif-add-snippets', + ...getExtraOptionsFromEnv(['database', 'analyze']), querySuite ]); } @@ -419,3 +426,60 @@ export function isTracedLanguage(language: string): boolean { export function isScannedLanguage(language: string): boolean { return !isTracedLanguage(language); } + +/** + * Gets the options for `path` of `options` as an array of extra option strings. + */ +function getExtraOptionsFromEnv(path: string[]) { + let options: ExtraOptions = util.getExtraOptionsEnvParam(); + return getExtraOptions(options, path, []); +} + +/** + * Gets the options for `path` of `options` as an array of extra option strings. + * + * - the special terminal step name '*' in `options` matches all path steps + * - throws an exception if this conversion is impossible. + */ +export /* exported for testing */ function getExtraOptions( + options: any, + path: string[], + pathInfo: string[]): string[] { + /** + * Gets `options` as an array of extra option strings. + * + * - throws an exception mentioning `pathInfo` if this conversion is impossible. + */ + function asExtraOptions(options: any, pathInfo: string[]): string[] { + if (options === undefined) { + return []; + } + if (!Array.isArray(options)) { + const msg = + 'The extra options for `' + + pathInfo.join('.') + + '` (`' + + JSON.stringify(options) + + '`) are not in an array.'; + throw new Error(msg); + } + return options.map(o => { + const t = typeof o; + if (t !== 'string' && t !== 'number' && t !== 'boolean') { + const msg = + 'The extra option for `' + + pathInfo.join('.') + + '` (`' + + JSON.stringify(o) + + '`) is not a primitive value.'; + throw new Error(msg); + } + return o + ''; + }); + } + let all = asExtraOptions(options?.['*'], pathInfo.concat('*')); + let specific = path.length === 0 ? + asExtraOptions(options, pathInfo) : + getExtraOptions(options?.[path[0]], path?.slice(1), pathInfo.concat(path[0])); + return all.concat(specific); +}