Skip to content

Commit 6e18b27

Browse files
Merge pull request #137 from github/invalid_language
Provide a better error message when languages are not recognised
2 parents 8608105 + d5c453c commit 6e18b27

File tree

6 files changed

+178
-17
lines changed

6 files changed

+178
-17
lines changed

lib/config-utils.js

Lines changed: 41 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/config-utils.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/config-utils.test.js

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/config-utils.test.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/config-utils.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,19 @@ function mockGetContents(content: GetContentsResponse): sinon.SinonStub<any, any
3636
return spyGetContents;
3737
}
3838

39+
function mockListLanguages(languages: string[]) {
40+
// Passing an auth token is required, so we just use a dummy value
41+
let client = new github.GitHub('123');
42+
const response = {
43+
data: {},
44+
};
45+
for (const language of languages) {
46+
response.data[language] = 123;
47+
}
48+
sinon.stub(client.repos, "listLanguages").resolves(response as any);
49+
sinon.stub(api, "getApiClient").value(() => client);
50+
}
51+
3952
test("load empty config", async t => {
4053
return await util.withTmpDir(async tmpDir => {
4154
process.env['RUNNER_TEMP'] = tmpDir;
@@ -343,6 +356,38 @@ test("Invalid format of remote config handled correctly", async t => {
343356
});
344357
});
345358

359+
test("No detected languages", async t => {
360+
return await util.withTmpDir(async tmpDir => {
361+
process.env['RUNNER_TEMP'] = tmpDir;
362+
process.env['GITHUB_WORKSPACE'] = tmpDir;
363+
364+
mockListLanguages([]);
365+
366+
try {
367+
await configUtils.initConfig();
368+
throw new Error('initConfig did not throw error');
369+
} catch (err) {
370+
t.deepEqual(err, new Error(configUtils.getNoLanguagesError()));
371+
}
372+
});
373+
});
374+
375+
test("Unknown languages", async t => {
376+
return await util.withTmpDir(async tmpDir => {
377+
process.env['RUNNER_TEMP'] = tmpDir;
378+
process.env['GITHUB_WORKSPACE'] = tmpDir;
379+
380+
setInput('languages', 'ruby,english');
381+
382+
try {
383+
await configUtils.initConfig();
384+
throw new Error('initConfig did not throw error');
385+
} catch (err) {
386+
t.deepEqual(err, new Error(configUtils.getUnknownLanguagesError(['ruby', 'english'])));
387+
}
388+
});
389+
});
390+
346391
function doInvalidInputTest(
347392
testName: string,
348393
inputFileContents: string,

src/config-utils.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,16 @@ const QUERIES_USES_PROPERTY = 'uses';
1717
const PATHS_IGNORE_PROPERTY = 'paths-ignore';
1818
const PATHS_PROPERTY = 'paths';
1919

20+
// All the languages supported by CodeQL
21+
const ALL_LANGUAGES = ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] as const;
22+
type Language = (typeof ALL_LANGUAGES)[number];
23+
24+
// Some alternate names for languages
25+
const LANGUAGE_ALIASES: {[name: string]: Language} = {
26+
'c': 'cpp',
27+
'typescript': 'javascript',
28+
};
29+
2030
/**
2131
* Format of the config file supplied by the user.
2232
*/
@@ -38,7 +48,7 @@ export interface Config {
3848
/**
3949
* Set of languages to run analysis for.
4050
*/
41-
languages: string[];
51+
languages: Language[];
4252
/**
4353
* Map from language to query files.
4454
* Will only contain .ql files and not other kinds of files,
@@ -402,12 +412,21 @@ function getConfigFilePropertyError(configFile: string, property: string, error:
402412
return 'The configuration file "' + configFile + '" is invalid: property "' + property + '" ' + error;
403413
}
404414

415+
export function getNoLanguagesError(): string {
416+
return "Did not detect any languages to analyze. " +
417+
"Please update input in workflow or check that GitHub detects the correct languages in your repository.";
418+
}
419+
420+
export function getUnknownLanguagesError(languages: string[]): string {
421+
return "Did not recognise the following languages: " + languages.join(', ');
422+
}
423+
405424
/**
406425
* Gets the set of languages in the current repository
407426
*/
408-
async function getLanguagesInRepo(): Promise<string[]> {
427+
async function getLanguagesInRepo(): Promise<Language[]> {
409428
// Translate between GitHub's API names for languages and ours
410-
const codeqlLanguages = {
429+
const codeqlLanguages: {[lang: string]: Language} = {
411430
'C': 'cpp',
412431
'C++': 'cpp',
413432
'C#': 'csharp',
@@ -423,18 +442,18 @@ async function getLanguagesInRepo(): Promise<string[]> {
423442
let repo = repo_nwo[1];
424443

425444
core.debug(`GitHub repo ${owner} ${repo}`);
426-
const response = await api.getApiClient(true).request("GET /repos/:owner/:repo/languages", ({
445+
const response = await api.getApiClient(true).repos.listLanguages({
427446
owner,
428447
repo
429-
}));
448+
});
430449

431450
core.debug("Languages API response: " + JSON.stringify(response));
432451

433452
// The GitHub API is going to return languages in order of popularity,
434453
// When we pick a language to autobuild we want to pick the most popular traced language
435454
// Since sets in javascript maintain insertion order, using a set here and then splatting it
436455
// into an array gives us an array of languages ordered by popularity
437-
let languages: Set<string> = new Set();
456+
let languages: Set<Language> = new Set();
438457
for (let lang in response.data) {
439458
if (lang in codeqlLanguages) {
440459
languages.add(codeqlLanguages[lang]);
@@ -456,7 +475,7 @@ async function getLanguagesInRepo(): Promise<string[]> {
456475
* If no languages could be detected from either the workflow or the repository
457476
* then throw an error.
458477
*/
459-
async function getLanguages(): Promise<string[]> {
478+
async function getLanguages(): Promise<Language[]> {
460479

461480
// Obtain from action input 'languages' if set
462481
let languages = core.getInput('languages', { required: false })
@@ -474,11 +493,32 @@ async function getLanguages(): Promise<string[]> {
474493
// If the languages parameter was not given and no languages were
475494
// detected then fail here as this is a workflow configuration error.
476495
if (languages.length === 0) {
477-
throw new Error("Did not detect any languages to analyze. " +
478-
"Please update input in workflow or check that GitHub detects the correct languages in your repository.");
496+
throw new Error(getNoLanguagesError());
497+
}
498+
499+
// Make sure they are supported
500+
const checkedLanguages: Language[] = [];
501+
const unknownLanguages: string[] = [];
502+
for (let language of languages) {
503+
// Normalise to lower case
504+
language = language.toLowerCase();
505+
// Resolve any known aliases
506+
if (language in LANGUAGE_ALIASES) {
507+
language = LANGUAGE_ALIASES[language];
508+
}
509+
510+
const checkedLanguage = ALL_LANGUAGES.find(l => l === language);
511+
if (checkedLanguage === undefined) {
512+
unknownLanguages.push(language);
513+
} else if (checkedLanguages.indexOf(checkedLanguage) === -1) {
514+
checkedLanguages.push(checkedLanguage);
515+
}
516+
}
517+
if (unknownLanguages.length > 0) {
518+
throw new Error(getUnknownLanguagesError(unknownLanguages));
479519
}
480520

481-
return languages;
521+
return checkedLanguages;
482522
}
483523

484524
/**

0 commit comments

Comments
 (0)