Implement support for language aliasing
This commit is contained in:
parent
e982de4fb4
commit
74714a34ca
15 changed files with 121 additions and 61 deletions
|
|
@ -212,6 +212,9 @@ export interface ResolveLanguagesOutput {
|
|||
}
|
||||
|
||||
export interface BetterResolveLanguagesOutput {
|
||||
aliases?: {
|
||||
[alias: string]: string;
|
||||
};
|
||||
extractors: {
|
||||
[language: string]: [
|
||||
{
|
||||
|
|
@ -330,6 +333,11 @@ export const CODEQL_VERSION_RESOLVE_ENVIRONMENT = "2.13.4";
|
|||
*/
|
||||
export const CODEQL_VERSION_LANGUAGE_BASELINE_CONFIG = "2.14.2";
|
||||
|
||||
/**
|
||||
* Versions 2.14.4+ of the CodeQL CLI support language aliasing.
|
||||
*/
|
||||
export const CODEQL_VERSION_LANGUAGE_ALIASING = "2.14.4";
|
||||
|
||||
/**
|
||||
* Set up CodeQL CLI access.
|
||||
*
|
||||
|
|
@ -710,11 +718,20 @@ export async function getCodeQLForCmd(
|
|||
}
|
||||
},
|
||||
async betterResolveLanguages() {
|
||||
const extraArgs: string[] = [];
|
||||
|
||||
if (
|
||||
await util.codeQlVersionAbove(this, CODEQL_VERSION_LANGUAGE_ALIASING)
|
||||
) {
|
||||
extraArgs.push("--extractor-include-aliases");
|
||||
}
|
||||
|
||||
const codeqlArgs = [
|
||||
"resolve",
|
||||
"languages",
|
||||
"--format=betterjson",
|
||||
"--extractor-options-verbosity=4",
|
||||
...extraArgs,
|
||||
...getExtraOptionsFromEnv(["resolve", "languages"]),
|
||||
];
|
||||
const output = await runTool(cmd, codeqlArgs);
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import * as api from "./api-client";
|
|||
import {
|
||||
CodeQL,
|
||||
CODEQL_VERSION_GHES_PACK_DOWNLOAD,
|
||||
CODEQL_VERSION_LANGUAGE_ALIASING,
|
||||
CODEQL_VERSION_SECURITY_EXPERIMENTAL_SUITE,
|
||||
ResolveQueriesOutput,
|
||||
} from "./codeql";
|
||||
|
|
@ -19,12 +20,7 @@ import {
|
|||
logCodeScanningConfigInCli,
|
||||
useCodeScanningConfigInCli,
|
||||
} from "./feature-flags";
|
||||
import {
|
||||
Language,
|
||||
LanguageOrAlias,
|
||||
parseLanguage,
|
||||
resolveAlias,
|
||||
} from "./languages";
|
||||
import { Language, parseLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import { RepositoryNwo } from "./repository";
|
||||
import { downloadTrapCaches } from "./trap-caching";
|
||||
|
|
@ -874,7 +870,7 @@ export function getNoLanguagesError(): string {
|
|||
}
|
||||
|
||||
export function getUnknownLanguagesError(languages: string[]): string {
|
||||
return `Did not recognise the following languages: ${languages.join(", ")}`;
|
||||
return `Did not recognize the following languages: ${languages.join(", ")}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -884,7 +880,7 @@ export function getUnknownLanguagesError(languages: string[]): string {
|
|||
export async function getLanguagesInRepo(
|
||||
repository: RepositoryNwo,
|
||||
logger: Logger,
|
||||
): Promise<LanguageOrAlias[]> {
|
||||
): Promise<Language[]> {
|
||||
logger.debug(`GitHub repo ${repository.owner} ${repository.repo}`);
|
||||
const response = await api.getApiClient().rest.repos.listLanguages({
|
||||
owner: repository.owner,
|
||||
|
|
@ -897,7 +893,7 @@ export async function getLanguagesInRepo(
|
|||
// 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
|
||||
const languages: Set<LanguageOrAlias> = new Set();
|
||||
const languages: Set<Language> = new Set();
|
||||
for (const lang of Object.keys(response.data as Record<string, number>)) {
|
||||
const parsedLang = parseLanguage(lang);
|
||||
if (parsedLang !== undefined) {
|
||||
|
|
@ -930,13 +926,22 @@ export async function getLanguages(
|
|||
logger,
|
||||
);
|
||||
|
||||
let languages = rawLanguages.map(resolveAlias);
|
||||
|
||||
let languages = rawLanguages;
|
||||
if (autodetected) {
|
||||
const availableLanguages = await codeQL.resolveLanguages();
|
||||
languages = languages.filter((value) => value in availableLanguages);
|
||||
const supportedLanguages = Object.keys(await codeQL.resolveLanguages());
|
||||
|
||||
languages = languages
|
||||
.map(parseLanguage)
|
||||
.filter((value) => value && supportedLanguages.includes(value))
|
||||
.map((value) => value as Language);
|
||||
|
||||
logger.info(`Automatically detected languages: ${languages.join(", ")}`);
|
||||
} else {
|
||||
const aliases = await getLanguageAliases(codeQL);
|
||||
if (aliases) {
|
||||
languages = languages.map((lang) => aliases[lang] || lang);
|
||||
}
|
||||
|
||||
logger.info(`Languages from configuration: ${languages.join(", ")}`);
|
||||
}
|
||||
|
||||
|
|
@ -950,7 +955,6 @@ export async function getLanguages(
|
|||
const parsedLanguages: Language[] = [];
|
||||
const unknownLanguages: string[] = [];
|
||||
for (const language of languages) {
|
||||
// We know this is not an alias since we resolved it above.
|
||||
const parsedLanguage = parseLanguage(language) as Language;
|
||||
if (parsedLanguage === undefined) {
|
||||
unknownLanguages.push(language);
|
||||
|
|
@ -968,6 +972,19 @@ export async function getLanguages(
|
|||
return parsedLanguages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the set of languages supported by CodeQL, along with their aliases if supported by the
|
||||
* version of the CLI.
|
||||
*/
|
||||
export async function getLanguageAliases(
|
||||
codeql: CodeQL,
|
||||
): Promise<{ [alias: string]: string } | undefined> {
|
||||
if (await codeQlVersionAbove(codeql, CODEQL_VERSION_LANGUAGE_ALIASING)) {
|
||||
return (await codeql.betterResolveLanguages()).aliases;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the set of languages in the current repository without checking to
|
||||
* see if these languages are actually supported by CodeQL.
|
||||
|
|
|
|||
|
|
@ -20,15 +20,15 @@ test("parseLanguage", async (t) => {
|
|||
t.deepEqual(parseLanguage("python"), Language.python);
|
||||
|
||||
// Aliases
|
||||
t.deepEqual(parseLanguage("c"), "c");
|
||||
t.deepEqual(parseLanguage("c++"), "c++");
|
||||
t.deepEqual(parseLanguage("c#"), "c#");
|
||||
t.deepEqual(parseLanguage("kotlin"), "kotlin");
|
||||
t.deepEqual(parseLanguage("typescript"), "typescript");
|
||||
t.deepEqual(parseLanguage("c"), Language.cpp);
|
||||
t.deepEqual(parseLanguage("c++"), Language.cpp);
|
||||
t.deepEqual(parseLanguage("c#"), Language.csharp);
|
||||
t.deepEqual(parseLanguage("kotlin"), Language.java);
|
||||
t.deepEqual(parseLanguage("typescript"), Language.javascript);
|
||||
|
||||
// spaces and case-insensitivity
|
||||
t.deepEqual(parseLanguage(" \t\nCsHaRp\t\t"), Language.csharp);
|
||||
t.deepEqual(parseLanguage(" \t\nkOtLin\t\t"), "kotlin");
|
||||
t.deepEqual(parseLanguage(" \t\nkOtLin\t\t"), Language.java);
|
||||
|
||||
// Not matches
|
||||
t.deepEqual(parseLanguage("foo"), undefined);
|
||||
|
|
|
|||
|
|
@ -19,23 +19,16 @@ export const LANGUAGE_ALIASES: { [lang: string]: Language } = {
|
|||
typescript: Language.javascript,
|
||||
};
|
||||
|
||||
export type LanguageOrAlias = Language | keyof typeof LANGUAGE_ALIASES;
|
||||
|
||||
export function resolveAlias(lang: LanguageOrAlias): Language {
|
||||
return LANGUAGE_ALIASES[lang] || lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate from user input or GitHub's API names for languages to CodeQL's
|
||||
* names for languages. This does not translate a language alias to the actual
|
||||
* language used by CodeQL.
|
||||
* names for languages.
|
||||
*
|
||||
* @param language The language to translate.
|
||||
* @returns A language supported by CodeQL, an alias for a language, or
|
||||
* `undefined` if the input language cannot be parsed into a langauge supported
|
||||
* `undefined` if the input language cannot be parsed into a language supported
|
||||
* by CodeQL.
|
||||
*/
|
||||
export function parseLanguage(language: string): LanguageOrAlias | undefined {
|
||||
export function parseLanguage(language: string): Language | undefined {
|
||||
// Normalise to lower case
|
||||
language = language.trim().toLowerCase();
|
||||
|
||||
|
|
@ -47,7 +40,7 @@ export function parseLanguage(language: string): LanguageOrAlias | undefined {
|
|||
// Check language aliases, but return the original language name,
|
||||
// the alias will be resolved later.
|
||||
if (language in LANGUAGE_ALIASES) {
|
||||
return language;
|
||||
return LANGUAGE_ALIASES[language];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import { getGitHubVersion } from "./api-client";
|
||||
import { CommandInvocationError } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { Language, resolveAlias } from "./languages";
|
||||
import { Language, parseLanguage } from "./languages";
|
||||
import { getActionsLogger } from "./logging";
|
||||
import { runResolveBuildEnvironment } from "./resolve-environment";
|
||||
import {
|
||||
|
|
@ -29,7 +29,6 @@ const ENVIRONMENT_OUTPUT_NAME = "environment";
|
|||
async function run() {
|
||||
const startedAt = new Date();
|
||||
const logger = getActionsLogger();
|
||||
const language: Language = resolveAlias(getRequiredInput("language"));
|
||||
|
||||
try {
|
||||
if (
|
||||
|
|
@ -45,6 +44,16 @@ async function run() {
|
|||
return;
|
||||
}
|
||||
|
||||
const language: Language | undefined = parseLanguage(
|
||||
getRequiredInput("language"),
|
||||
);
|
||||
|
||||
if (language === undefined) {
|
||||
throw new Error(
|
||||
`Did not recognize the language "${getRequiredInput("language")}".`,
|
||||
);
|
||||
}
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue