Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #137 from github/invalid_language
Provide a better error message when languages are not recognised
Robert authored and GitHub committed Aug 10, 2020
2 parents 8608105 + d5c453c commit 6e18b27
Showing 6 changed files with 178 additions and 17 deletions.
46 changes: 41 additions & 5 deletions lib/config-utils.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/config-utils.js.map

Large diffs are not rendered by default.

40 changes: 40 additions & 0 deletions lib/config-utils.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lib/config-utils.test.js.map

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions src/config-utils.test.ts
@@ -36,6 +36,19 @@ function mockGetContents(content: GetContentsResponse): sinon.SinonStub<any, any
return spyGetContents;
}

function mockListLanguages(languages: string[]) {
// Passing an auth token is required, so we just use a dummy value
let client = new github.GitHub('123');
const response = {
data: {},
};
for (const language of languages) {
response.data[language] = 123;
}
sinon.stub(client.repos, "listLanguages").resolves(response as any);
sinon.stub(api, "getApiClient").value(() => client);
}

test("load empty config", async t => {
return await util.withTmpDir(async tmpDir => {
process.env['RUNNER_TEMP'] = tmpDir;
@@ -343,6 +356,38 @@ test("Invalid format of remote config handled correctly", async t => {
});
});

test("No detected languages", async t => {
return await util.withTmpDir(async tmpDir => {
process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;

mockListLanguages([]);

try {
await configUtils.initConfig();
throw new Error('initConfig did not throw error');
} catch (err) {
t.deepEqual(err, new Error(configUtils.getNoLanguagesError()));
}
});
});

test("Unknown languages", async t => {
return await util.withTmpDir(async tmpDir => {
process.env['RUNNER_TEMP'] = tmpDir;
process.env['GITHUB_WORKSPACE'] = tmpDir;

setInput('languages', 'ruby,english');

try {
await configUtils.initConfig();
throw new Error('initConfig did not throw error');
} catch (err) {
t.deepEqual(err, new Error(configUtils.getUnknownLanguagesError(['ruby', 'english'])));
}
});
});

function doInvalidInputTest(
testName: string,
inputFileContents: string,
60 changes: 50 additions & 10 deletions src/config-utils.ts
@@ -17,6 +17,16 @@ const QUERIES_USES_PROPERTY = 'uses';
const PATHS_IGNORE_PROPERTY = 'paths-ignore';
const PATHS_PROPERTY = 'paths';

// All the languages supported by CodeQL
const ALL_LANGUAGES = ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] as const;
type Language = (typeof ALL_LANGUAGES)[number];

// Some alternate names for languages
const LANGUAGE_ALIASES: {[name: string]: Language} = {
'c': 'cpp',
'typescript': 'javascript',
};

/**
* Format of the config file supplied by the user.
*/
@@ -38,7 +48,7 @@ export interface Config {
/**
* Set of languages to run analysis for.
*/
languages: string[];
languages: Language[];
/**
* Map from language to query files.
* Will only contain .ql files and not other kinds of files,
@@ -402,12 +412,21 @@ function getConfigFilePropertyError(configFile: string, property: string, error:
return 'The configuration file "' + configFile + '" is invalid: property "' + property + '" ' + error;
}

export function getNoLanguagesError(): string {
return "Did not detect any languages to analyze. " +
"Please update input in workflow or check that GitHub detects the correct languages in your repository.";
}

export function getUnknownLanguagesError(languages: string[]): string {
return "Did not recognise the following languages: " + languages.join(', ');
}

/**
* Gets the set of languages in the current repository
*/
async function getLanguagesInRepo(): Promise<string[]> {
async function getLanguagesInRepo(): Promise<Language[]> {
// Translate between GitHub's API names for languages and ours
const codeqlLanguages = {
const codeqlLanguages: {[lang: string]: Language} = {
'C': 'cpp',
'C++': 'cpp',
'C#': 'csharp',
@@ -423,18 +442,18 @@ async function getLanguagesInRepo(): Promise<string[]> {
let repo = repo_nwo[1];

core.debug(`GitHub repo ${owner} ${repo}`);
const response = await api.getApiClient(true).request("GET /repos/:owner/:repo/languages", ({
const response = await api.getApiClient(true).repos.listLanguages({
owner,
repo
}));
});

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

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

// Obtain from action input 'languages' if set
let languages = core.getInput('languages', { required: false })
@@ -474,11 +493,32 @@ async function getLanguages(): Promise<string[]> {
// If the languages parameter was not given and no languages were
// detected then fail here as this is a workflow configuration error.
if (languages.length === 0) {
throw new Error("Did not detect any languages to analyze. " +
"Please update input in workflow or check that GitHub detects the correct languages in your repository.");
throw new Error(getNoLanguagesError());
}

// Make sure they are supported
const checkedLanguages: Language[] = [];
const unknownLanguages: string[] = [];
for (let language of languages) {
// Normalise to lower case
language = language.toLowerCase();
// Resolve any known aliases
if (language in LANGUAGE_ALIASES) {
language = LANGUAGE_ALIASES[language];
}

const checkedLanguage = ALL_LANGUAGES.find(l => l === language);
if (checkedLanguage === undefined) {
unknownLanguages.push(language);
} else if (checkedLanguages.indexOf(checkedLanguage) === -1) {
checkedLanguages.push(checkedLanguage);
}
}
if (unknownLanguages.length > 0) {
throw new Error(getUnknownLanguagesError(unknownLanguages));
}

return languages;
return checkedLanguages;
}

/**

0 comments on commit 6e18b27

Please sign in to comment.