Merge pull request #137 from github/invalid_language
Provide a better error message when languages are not recognised
This commit is contained in:
commit
6e18b27d4d
6 changed files with 178 additions and 17 deletions
46
lib/config-utils.js
generated
46
lib/config-utils.js
generated
|
|
@ -23,6 +23,13 @@ const QUERIES_PROPERTY = 'queries';
|
||||||
const QUERIES_USES_PROPERTY = 'uses';
|
const QUERIES_USES_PROPERTY = 'uses';
|
||||||
const PATHS_IGNORE_PROPERTY = 'paths-ignore';
|
const PATHS_IGNORE_PROPERTY = 'paths-ignore';
|
||||||
const PATHS_PROPERTY = 'paths';
|
const PATHS_PROPERTY = 'paths';
|
||||||
|
// All the languages supported by CodeQL
|
||||||
|
const ALL_LANGUAGES = ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'];
|
||||||
|
// Some alternate names for languages
|
||||||
|
const LANGUAGE_ALIASES = {
|
||||||
|
'c': 'cpp',
|
||||||
|
'typescript': 'javascript',
|
||||||
|
};
|
||||||
/**
|
/**
|
||||||
* A list of queries from https://github.com/github/codeql that
|
* A list of queries from https://github.com/github/codeql that
|
||||||
* we don't want to run. Disabling them here is a quicker alternative to
|
* we don't want to run. Disabling them here is a quicker alternative to
|
||||||
|
|
@ -280,6 +287,15 @@ exports.getConfigFileDirectoryGivenMessage = getConfigFileDirectoryGivenMessage;
|
||||||
function getConfigFilePropertyError(configFile, property, error) {
|
function getConfigFilePropertyError(configFile, property, error) {
|
||||||
return 'The configuration file "' + configFile + '" is invalid: property "' + property + '" ' + error;
|
return 'The configuration file "' + configFile + '" is invalid: property "' + property + '" ' + error;
|
||||||
}
|
}
|
||||||
|
function getNoLanguagesError() {
|
||||||
|
return "Did not detect any languages to analyze. " +
|
||||||
|
"Please update input in workflow or check that GitHub detects the correct languages in your repository.";
|
||||||
|
}
|
||||||
|
exports.getNoLanguagesError = getNoLanguagesError;
|
||||||
|
function getUnknownLanguagesError(languages) {
|
||||||
|
return "Did not recognise the following languages: " + languages.join(', ');
|
||||||
|
}
|
||||||
|
exports.getUnknownLanguagesError = getUnknownLanguagesError;
|
||||||
/**
|
/**
|
||||||
* Gets the set of languages in the current repository
|
* Gets the set of languages in the current repository
|
||||||
*/
|
*/
|
||||||
|
|
@ -301,10 +317,10 @@ async function getLanguagesInRepo() {
|
||||||
let owner = repo_nwo[0];
|
let owner = repo_nwo[0];
|
||||||
let repo = repo_nwo[1];
|
let repo = repo_nwo[1];
|
||||||
core.debug(`GitHub repo ${owner} ${repo}`);
|
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,
|
owner,
|
||||||
repo
|
repo
|
||||||
}));
|
});
|
||||||
core.debug("Languages API response: " + JSON.stringify(response));
|
core.debug("Languages API response: " + JSON.stringify(response));
|
||||||
// The GitHub API is going to return languages in order of popularity,
|
// 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
|
// When we pick a language to autobuild we want to pick the most popular traced language
|
||||||
|
|
@ -347,10 +363,30 @@ async function getLanguages() {
|
||||||
// If the languages parameter was not given and no languages were
|
// If the languages parameter was not given and no languages were
|
||||||
// detected then fail here as this is a workflow configuration error.
|
// detected then fail here as this is a workflow configuration error.
|
||||||
if (languages.length === 0) {
|
if (languages.length === 0) {
|
||||||
throw new Error("Did not detect any languages to analyze. " +
|
throw new Error(getNoLanguagesError());
|
||||||
"Please update input in workflow or check that GitHub detects the correct languages in your repository.");
|
|
||||||
}
|
}
|
||||||
return languages;
|
// Make sure they are supported
|
||||||
|
const checkedLanguages = [];
|
||||||
|
const unknownLanguages = [];
|
||||||
|
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 checkedLanguages;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Get the default config for when the user has not supplied one.
|
* Get the default config for when the user has not supplied one.
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
40
lib/config-utils.test.js
generated
40
lib/config-utils.test.js
generated
|
|
@ -42,6 +42,18 @@ function mockGetContents(content) {
|
||||||
sinon_1.default.stub(api, "getApiClient").value(() => client);
|
sinon_1.default.stub(api, "getApiClient").value(() => client);
|
||||||
return spyGetContents;
|
return spyGetContents;
|
||||||
}
|
}
|
||||||
|
function mockListLanguages(languages) {
|
||||||
|
// 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_1.default.stub(client.repos, "listLanguages").resolves(response);
|
||||||
|
sinon_1.default.stub(api, "getApiClient").value(() => client);
|
||||||
|
}
|
||||||
ava_1.default("load empty config", async (t) => {
|
ava_1.default("load empty config", async (t) => {
|
||||||
return await util.withTmpDir(async (tmpDir) => {
|
return await util.withTmpDir(async (tmpDir) => {
|
||||||
process.env['RUNNER_TEMP'] = tmpDir;
|
process.env['RUNNER_TEMP'] = tmpDir;
|
||||||
|
|
@ -303,6 +315,34 @@ ava_1.default("Invalid format of remote config handled correctly", async (t) =>
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
ava_1.default("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()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ava_1.default("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, inputFileContents, expectedErrorMessageGenerator) {
|
function doInvalidInputTest(testName, inputFileContents, expectedErrorMessageGenerator) {
|
||||||
ava_1.default("load invalid input - " + testName, async (t) => {
|
ava_1.default("load invalid input - " + testName, async (t) => {
|
||||||
return await util.withTmpDir(async (tmpDir) => {
|
return await util.withTmpDir(async (tmpDir) => {
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -36,6 +36,19 @@ function mockGetContents(content: GetContentsResponse): sinon.SinonStub<any, any
|
||||||
return spyGetContents;
|
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 => {
|
test("load empty config", async t => {
|
||||||
return await util.withTmpDir(async tmpDir => {
|
return await util.withTmpDir(async tmpDir => {
|
||||||
process.env['RUNNER_TEMP'] = 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(
|
function doInvalidInputTest(
|
||||||
testName: string,
|
testName: string,
|
||||||
inputFileContents: string,
|
inputFileContents: string,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,16 @@ const QUERIES_USES_PROPERTY = 'uses';
|
||||||
const PATHS_IGNORE_PROPERTY = 'paths-ignore';
|
const PATHS_IGNORE_PROPERTY = 'paths-ignore';
|
||||||
const PATHS_PROPERTY = 'paths';
|
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.
|
* Format of the config file supplied by the user.
|
||||||
*/
|
*/
|
||||||
|
|
@ -38,7 +48,7 @@ export interface Config {
|
||||||
/**
|
/**
|
||||||
* Set of languages to run analysis for.
|
* Set of languages to run analysis for.
|
||||||
*/
|
*/
|
||||||
languages: string[];
|
languages: Language[];
|
||||||
/**
|
/**
|
||||||
* Map from language to query files.
|
* Map from language to query files.
|
||||||
* Will only contain .ql files and not other kinds of 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;
|
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
|
* 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
|
// Translate between GitHub's API names for languages and ours
|
||||||
const codeqlLanguages = {
|
const codeqlLanguages: {[lang: string]: Language} = {
|
||||||
'C': 'cpp',
|
'C': 'cpp',
|
||||||
'C++': 'cpp',
|
'C++': 'cpp',
|
||||||
'C#': 'csharp',
|
'C#': 'csharp',
|
||||||
|
|
@ -423,10 +442,10 @@ async function getLanguagesInRepo(): Promise<string[]> {
|
||||||
let repo = repo_nwo[1];
|
let repo = repo_nwo[1];
|
||||||
|
|
||||||
core.debug(`GitHub repo ${owner} ${repo}`);
|
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,
|
owner,
|
||||||
repo
|
repo
|
||||||
}));
|
});
|
||||||
|
|
||||||
core.debug("Languages API response: " + JSON.stringify(response));
|
core.debug("Languages API response: " + JSON.stringify(response));
|
||||||
|
|
||||||
|
|
@ -434,7 +453,7 @@ async function getLanguagesInRepo(): Promise<string[]> {
|
||||||
// When we pick a language to autobuild we want to pick the most popular traced language
|
// 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
|
// 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
|
// 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) {
|
for (let lang in response.data) {
|
||||||
if (lang in codeqlLanguages) {
|
if (lang in codeqlLanguages) {
|
||||||
languages.add(codeqlLanguages[lang]);
|
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
|
* If no languages could be detected from either the workflow or the repository
|
||||||
* then throw an error.
|
* then throw an error.
|
||||||
*/
|
*/
|
||||||
async function getLanguages(): Promise<string[]> {
|
async function getLanguages(): Promise<Language[]> {
|
||||||
|
|
||||||
// Obtain from action input 'languages' if set
|
// Obtain from action input 'languages' if set
|
||||||
let languages = core.getInput('languages', { required: false })
|
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
|
// If the languages parameter was not given and no languages were
|
||||||
// detected then fail here as this is a workflow configuration error.
|
// detected then fail here as this is a workflow configuration error.
|
||||||
if (languages.length === 0) {
|
if (languages.length === 0) {
|
||||||
throw new Error("Did not detect any languages to analyze. " +
|
throw new Error(getNoLanguagesError());
|
||||||
"Please update input in workflow or check that GitHub detects the correct languages in your repository.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return languages;
|
// 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 checkedLanguages;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue