Add baseline metrics for lines of code

This commit uses a third party library to estimate the lines of code in
a database that is to be analyzed by codeql.

The estimate uses the same includes and excludes globs for determining
which files should be counted.

The lines of code count is returned by language and injected into the
SARIF as `baseline` property in the `${language}/summary/lines-of-code`
metric.
This commit is contained in:
Andrew Eisenberg 2021-04-16 14:52:39 -07:00
parent 7c5b1287d5
commit 998f472183
11 changed files with 379 additions and 41 deletions

View file

@ -1 +1 @@
{"version":3,"file":"analyze-action.js","sourceRoot":"","sources":["../src/analyze-action.ts"],"names":[],"mappings":";;;;;;;;;AAAA,uCAAyB;AACzB,2CAA6B;AAE7B,oDAAsC;AAEtC,4DAA8C;AAC9C,uCAImB;AACnB,iDAAmD;AACnD,uCAA6C;AAC7C,yDAA2C;AAC3C,6CAA+B;AAU/B,KAAK,UAAU,gBAAgB,CAC7B,SAAe,EACf,KAAuC,EACvC,KAAa;;IAEb,MAAM,MAAM,GACV,OAAA,KAAK,0CAAE,wBAAwB,MAAK,SAAS,IAAI,KAAK,KAAK,SAAS;QAClE,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,SAAS,CAAC;IAChB,MAAM,gBAAgB,GAAG,MAAM,WAAW,CAAC,sBAAsB,CAC/D,QAAQ,EACR,MAAM,EACN,SAAS,QACT,KAAK,0CAAE,OAAO,QACd,KAAK,0CAAE,KAAK,CACb,CAAC;IACF,MAAM,YAAY,GAAuB;QACvC,GAAG,gBAAgB;QACnB,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;KACjB,CAAC;IACF,MAAM,WAAW,CAAC,gBAAgB,CAAC,YAAY,CAAC,CAAC;AACnD,CAAC;AAED,KAAK,UAAU,GAAG;IAChB,MAAM,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC;IAC7B,IAAI,KAAK,GAAqC,SAAS,CAAC;IACxD,IAAI,MAAM,GAAuB,SAAS,CAAC;IAC3C,IAAI;QACF,WAAW,CAAC,0BAA0B,EAAE,CAAC;QACzC,IACE,CAAC,CAAC,MAAM,WAAW,CAAC,gBAAgB,CAClC,MAAM,WAAW,CAAC,sBAAsB,CACtC,QAAQ,EACR,UAAU,EACV,SAAS,CACV,CACF,CAAC,EACF;YACA,OAAO;SACR;QACD,MAAM,MAAM,GAAG,0BAAgB,EAAE,CAAC;QAClC,MAAM,GAAG,MAAM,wBAAS,CAAC,WAAW,CAAC,qBAAqB,EAAE,EAAE,MAAM,CAAC,CAAC;QACtE,IAAI,MAAM,KAAK,SAAS,EAAE;YACxB,MAAM,IAAI,KAAK,CACb,yFAAyF,CAC1F,CAAC;SACH;QACD,MAAM,UAAU,GAAG;YACjB,IAAI,EAAE,WAAW,CAAC,gBAAgB,CAAC,OAAO,CAAC;YAC3C,GAAG,EAAE,WAAW,CAAC,mBAAmB,CAAC,mBAAmB,CAAC;SAC1D,CAAC;QACF,MAAM,SAAS,GAAG,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;QACzD,MAAM,YAAY,GAAG,MAAM,oBAAU,CACnC,SAAS,EACT,IAAI,CAAC,aAAa,CAAC,WAAW,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,EACvD,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,gBAAgB,CAAC,cAAc,CAAC,CAAC,EACrE,IAAI,CAAC,cAAc,CAAC,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,EACpE,MAAM,EACN,MAAM,CACP,CAAC;QAEF,IAAI,WAAW,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,MAAM,EAAE;YACrD,MAAM,WAAW,GAAG,MAAM,UAAU,CAAC,iBAAiB,CACpD,SAAS,EACT,MAAM,CAAC,aAAa,EACpB,UAAU,EACV,MAAM,CACP,CAAC;YACF,KAAK,GAAG,EAAE,GAAG,YAAY,EAAE,GAAG,WAAW,EAAE,CAAC;SAC7C;aAAM;YACL,MAAM,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;YACrC,KAAK,GAAG,EAAE,GAAG,YAAY,EAAE,CAAC;SAC7B;KACF;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QAEnB,IAAI,KAAK,YAAY,6BAAmB,EAAE;YACxC,KAAK,GAAG,EAAE,GAAG,KAAK,CAAC,mBAAmB,EAAE,CAAC;SAC1C;QAED,MAAM,gBAAgB,CAAC,SAAS,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAChD,OAAO;KACR;YAAS;QACR,IAAI,IAAI,CAAC,OAAO,EAAE,IAAI,MAAM,KAAK,SAAS,EAAE;YAC1C,IAAI,CAAC,IAAI,CAAC,iDAAiD,CAAC,CAAC;YAC7D,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,SAAS,EAAE;gBACvC,MAAM,iBAAiB,GAAG,IAAI,CAAC,qBAAqB,CAClD,MAAM,CAAC,OAAO,EACd,QAAQ,CACT,CAAC;gBACF,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;gBAE1D,MAAM,YAAY,GAAG,CAAC,GAAW,EAAE,EAAE;oBACnC,MAAM,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;oBAC7D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE;wBAC3B,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE;4BAClB,IAAI,CAAC,UAAU,CACb,uBAAuB,QAAQ,MAAM,KAAK,CAAC,IAAI,EAAE,CAClD,CAAC;4BACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAC/C,CAAC;4BACF,IAAI,CAAC,QAAQ,EAAE,CAAC;yBACjB;6BAAM,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE;4BAC9B,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;yBAC7C;qBACF;gBACH,CAAC,CAAC;gBACF,YAAY,CAAC,aAAa,CAAC,CAAC;aAC7B;SACF;KACF;IAED,MAAM,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,KAAK,UAAU,UAAU;IACvB,IAAI;QACF,MAAM,GAAG,EAAE,CAAC;KACb;IAAC,OAAO,KAAK,EAAE;QACd,IAAI,CAAC,SAAS,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;QAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;KACpB;AACH,CAAC;AAED,KAAK,UAAU,EAAE,CAAC"} {"version":3,"file":"analyze-action.js","sourceRoot":"","sources":["../src/analyze-action.ts"],"names":[],"mappings":"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"}

40
lib/analyze.js generated
View file

@ -12,6 +12,7 @@ const path = __importStar(require("path"));
const toolrunner = __importStar(require("@actions/exec/lib/toolrunner")); const toolrunner = __importStar(require("@actions/exec/lib/toolrunner"));
const analysisPaths = __importStar(require("./analysis-paths")); const analysisPaths = __importStar(require("./analysis-paths"));
const codeql_1 = require("./codeql"); const codeql_1 = require("./codeql");
const count_loc_1 = require("./count-loc");
const languages_1 = require("./languages"); const languages_1 = require("./languages");
const sharedEnv = __importStar(require("./shared-environment")); const sharedEnv = __importStar(require("./shared-environment"));
const upload_lib_1 = require("./upload-lib"); const upload_lib_1 = require("./upload-lib");
@ -77,6 +78,12 @@ async function finalizeDatabaseCreation(config, threadsFlag, logger) {
// Runs queries and creates sarif files in the given folder // Runs queries and creates sarif files in the given folder
async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag, config, logger) { async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag, config, logger) {
const statusReport = {}; const statusReport = {};
// count the number of lines in the background
const locPromise = count_loc_1.countLoc(path.resolve(),
// config.paths specifies external directories. the current
// directory is included in the analysis by default. Replicate
// that here.
["**"].concat(config.paths || []), config.pathsIgnore, config.languages, logger);
for (const language of config.languages) { for (const language of config.languages) {
logger.startGroup(`Analyzing ${language}`); logger.startGroup(`Analyzing ${language}`);
const queries = config.queries[language]; const queries = config.queries[language];
@ -86,7 +93,8 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
try { try {
if (queries["builtin"].length > 0) { if (queries["builtin"].length > 0) {
const startTimeBuliltIn = new Date().getTime(); const startTimeBuliltIn = new Date().getTime();
await runQueryGroup(language, "builtin", queries["builtin"], sarifFolder, undefined); const sarifFile = await runQueryGroup(language, "builtin", queries["builtin"], sarifFolder, undefined);
await injectLinesOfCode(sarifFile, language, locPromise);
statusReport[`analyze_builtin_queries_${language}_duration_ms`] = statusReport[`analyze_builtin_queries_${language}_duration_ms`] =
new Date().getTime() - startTimeBuliltIn; new Date().getTime() - startTimeBuliltIn;
} }
@ -95,12 +103,14 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
const temporarySarifFiles = []; const temporarySarifFiles = [];
for (let i = 0; i < queries["custom"].length; ++i) { for (let i = 0; i < queries["custom"].length; ++i) {
if (queries["custom"][i].queries.length > 0) { if (queries["custom"][i].queries.length > 0) {
await runQueryGroup(language, `custom-${i}`, queries["custom"][i].queries, temporarySarifDir, queries["custom"][i].searchPath); const sarifFile = await runQueryGroup(language, `custom-${i}`, queries["custom"][i].queries, temporarySarifDir, queries["custom"][i].searchPath);
temporarySarifFiles.push(path.join(temporarySarifDir, `${language}-custom-${i}.sarif`)); temporarySarifFiles.push(sarifFile);
} }
} }
if (temporarySarifFiles.length > 0) { if (temporarySarifFiles.length > 0) {
fs.writeFileSync(path.join(sarifFolder, `${language}-custom.sarif`), upload_lib_1.combineSarifFiles(temporarySarifFiles)); const sarifFile = path.join(sarifFolder, `${language}-custom.sarif`);
fs.writeFileSync(sarifFile, upload_lib_1.combineSarifFiles(temporarySarifFiles));
await injectLinesOfCode(sarifFile, language, locPromise);
statusReport[`analyze_custom_queries_${language}_duration_ms`] = statusReport[`analyze_custom_queries_${language}_duration_ms`] =
new Date().getTime() - startTimeCustom; new Date().getTime() - startTimeCustom;
} }
@ -127,6 +137,7 @@ async function runQueries(sarifFolder, memoryFlag, addSnippetsFlag, threadsFlag,
await codeql.databaseAnalyze(databasePath, sarifFile, searchPath, querySuitePath, memoryFlag, addSnippetsFlag, threadsFlag); await codeql.databaseAnalyze(databasePath, sarifFile, searchPath, querySuitePath, memoryFlag, addSnippetsFlag, threadsFlag);
logger.debug(`SARIF results for database ${language} created at "${sarifFile}"`); logger.debug(`SARIF results for database ${language} created at "${sarifFile}"`);
logger.endGroup(); logger.endGroup();
return sarifFile;
} }
} }
exports.runQueries = runQueries; exports.runQueries = runQueries;
@ -141,4 +152,25 @@ async function runAnalyze(outputDir, memoryFlag, addSnippetsFlag, threadsFlag, c
return { ...queriesStats }; return { ...queriesStats };
} }
exports.runAnalyze = runAnalyze; exports.runAnalyze = runAnalyze;
async function injectLinesOfCode(sarifFile, language, locPromise) {
const lineCounts = await locPromise;
if (language in lineCounts) {
const sarif = JSON.parse(fs.readFileSync(sarifFile, "utf8"));
if (Array.isArray(sarif.runs)) {
for (const run of sarif.runs) {
const metricId = `${language}/summary/lines-of-code`;
run.properties = run.properties || {};
run.properties.metricResults = run.properties.metricResults || [];
const metric = run.properties.metricResults.find(
// the metric id can be in either of two places
(m) => { var _a; return m.metricId === metricId || ((_a = m.metric) === null || _a === void 0 ? void 0 : _a.id) === metricId; });
// only add the baseline value if the metric already exists
if (metric) {
metric.baseline = lineCounts[language];
}
}
}
fs.writeFileSync(sarifFile, JSON.stringify(sarif));
}
}
//# sourceMappingURL=analyze.js.map //# sourceMappingURL=analyze.js.map

File diff suppressed because one or more lines are too long

80
lib/analyze.test.js generated
View file

@ -11,9 +11,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
const fs = __importStar(require("fs")); const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const ava_1 = __importDefault(require("ava")); const ava_1 = __importDefault(require("ava"));
const sinon_1 = __importDefault(require("sinon"));
const analyze_1 = require("./analyze"); const analyze_1 = require("./analyze");
const codeql_1 = require("./codeql"); const codeql_1 = require("./codeql");
const count = __importStar(require("./count-loc"));
const languages_1 = require("./languages"); const languages_1 = require("./languages");
const logging_1 = require("./logging"); const logging_1 = require("./logging");
const testing_utils_1 = require("./testing-utils"); const testing_utils_1 = require("./testing-utils");
@ -23,21 +26,53 @@ testing_utils_1.setupTests(ava_1.default);
// and correct case of builtin or custom. Also checks the correct search // and correct case of builtin or custom. Also checks the correct search
// paths are set in the database analyze invocation. // paths are set in the database analyze invocation.
ava_1.default("status report fields and search path setting", async (t) => { ava_1.default("status report fields and search path setting", async (t) => {
const mockLinesOfCode = Object.entries(languages_1.Language).reduce((obj, [lang], i) => {
// use a different line count for each languaged
obj[lang] = i + 1;
return obj;
}, {});
sinon_1.default.stub(count, "countLoc").resolves(mockLinesOfCode);
let searchPathsUsed = []; let searchPathsUsed = [];
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
testing_utils_1.setupActionsVars(tmpDir, tmpDir); testing_utils_1.setupActionsVars(tmpDir, tmpDir);
codeql_1.setCodeQL({
databaseAnalyze: async (_, sarifFile, searchPath) => {
fs.writeFileSync(sarifFile, JSON.stringify({
runs: [],
}));
searchPathsUsed.push(searchPath);
},
});
const memoryFlag = ""; const memoryFlag = "";
const addSnippetsFlag = ""; const addSnippetsFlag = "";
const threadsFlag = ""; const threadsFlag = "";
for (const language of Object.values(languages_1.Language)) { for (const language of Object.values(languages_1.Language)) {
codeql_1.setCodeQL({
databaseAnalyze: async (_, sarifFile, searchPath) => {
fs.writeFileSync(sarifFile, JSON.stringify({
runs: [
// variant 1 uses metricId
{
properties: {
metricResults: [
{
metricId: `${language}/summary/lines-of-code`,
value: 123,
},
],
},
},
// variant 2 uses metric.id
{
properties: {
metricResults: [
{
metric: {
id: `${language}/summary/lines-of-code`,
},
value: 123,
},
],
},
},
{},
],
}));
searchPathsUsed.push(searchPath);
},
});
searchPathsUsed = []; searchPathsUsed = [];
const config = { const config = {
languages: [language], languages: [language],
@ -80,6 +115,35 @@ ava_1.default("status report fields and search path setting", async (t) => {
t.true(`analyze_custom_queries_${language}_duration_ms` in customStatusReport); t.true(`analyze_custom_queries_${language}_duration_ms` in customStatusReport);
t.deepEqual(searchPathsUsed, [undefined, "/1", "/2"]); t.deepEqual(searchPathsUsed, [undefined, "/1", "/2"]);
} }
verifyLineCounts(tmpDir);
}); });
function verifyLineCounts(tmpDir) {
// eslint-disable-next-line github/array-foreach
Object.keys(languages_1.Language).forEach((lang, i) => {
verifyLineCountForFile(lang, path.join(tmpDir, `${lang}-builtin.sarif`), i + 1);
verifyLineCountForFile(lang, path.join(tmpDir, `${lang}-custom.sarif`), i + 1);
});
}
function verifyLineCountForFile(lang, filePath, lineCount) {
const sarif = JSON.parse(fs.readFileSync(filePath, "utf8"));
t.deepEqual(sarif.runs[0].properties.metricResults, [
{
metricId: `${lang}/summary/lines-of-code`,
value: 123,
baseline: lineCount,
},
]);
t.deepEqual(sarif.runs[1].properties.metricResults, [
{
metric: {
id: `${lang}/summary/lines-of-code`,
},
value: 123,
baseline: lineCount,
},
]);
// when the metric doesn't exists, it should not be added
t.deepEqual(sarif.runs[2].properties.metricResults, []);
}
}); });
//# sourceMappingURL=analyze.test.js.map //# sourceMappingURL=analyze.test.js.map

View file

@ -1 +1 @@
{"version":3,"file":"analyze.test.js","sourceRoot":"","sources":["../src/analyze.test.ts"],"names":[],"mappings":"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"} {"version":3,"file":"analyze.test.js","sourceRoot":"","sources":["../src/analyze.test.ts"],"names":[],"mappings":"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"}

54
lib/count-loc.js generated Normal file
View file

@ -0,0 +1,54 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const github_linguist_1 = require("github-linguist");
// Map from linguist language names to language prefixes used in our metrics
const supportedLanguages = {
c: "cpp",
"c++": "cpp",
"c#": "cs",
go: "go",
java: "java",
javascript: "js",
python: "py",
ruby: "rb",
typescript: "js",
};
/**
* Count the lines of code of the specified language using the include
* and exclude glob paths.
*
* @param cwd the root directory to start the count from
* @param include glob patterns to include in the search for relevant files
* @param exclude glob patterns to exclude in the search for relevant files
* @param dbLanguages list of languages to include in the results
* @param logger object to log results
*/
async function countLoc(cwd, include, exclude, dbLanguages, logger) {
const result = await new github_linguist_1.LocDir({
cwd,
include,
exclude,
}).loadInfo();
// The analysis counts LoC in all languages. We need to
// extract the languages we care about. Also, note that
// the analysis uses slightly different names for language.
const lineCounts = Object.entries(result.languages).reduce((obj, [language, { code }]) => {
const dbLanguage = supportedLanguages[language];
if (dbLanguage && dbLanguages.includes(dbLanguage)) {
obj[dbLanguage] = code + (obj[dbLanguage] || 0);
}
return obj;
}, {});
if (Object.keys(lineCounts).length) {
logger.debug("Lines of code count:");
for (const [language, count] of Object.entries(lineCounts)) {
logger.debug(` ${language}: ${count}`);
}
}
else {
logger.warning("Did not find any lines of code in database.");
}
return lineCounts;
}
exports.countLoc = countLoc;
//# sourceMappingURL=count-loc.js.map

1
lib/count-loc.js.map Normal file
View file

@ -0,0 +1 @@
{"version":3,"file":"count-loc.js","sourceRoot":"","sources":["../src/count-loc.ts"],"names":[],"mappings":";;AAAA,qDAAyC;AAIzC,4EAA4E;AAC5E,MAAM,kBAAkB,GAAG;IACzB,CAAC,EAAE,KAAK;IACR,KAAK,EAAE,KAAK;IACZ,IAAI,EAAE,IAAI;IACV,EAAE,EAAE,IAAI;IACR,IAAI,EAAE,MAAM;IACZ,UAAU,EAAE,IAAI;IAChB,MAAM,EAAE,IAAI;IACZ,IAAI,EAAE,IAAI;IACV,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;;;;;GASG;AACI,KAAK,UAAU,QAAQ,CAC5B,GAAW,EACX,OAAiB,EACjB,OAAiB,EACjB,WAAqB,EACrB,MAAc;IAEd,MAAM,MAAM,GAAG,MAAM,IAAI,wBAAM,CAAC;QAC9B,GAAG;QACH,OAAO;QACP,OAAO;KACR,CAAC,CAAC,QAAQ,EAAE,CAAC;IAEd,uDAAuD;IACvD,uDAAuD;IACvD,2DAA2D;IAC3D,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,MAAM,CACxD,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE;QAC5B,MAAM,UAAU,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,UAAU,IAAI,WAAW,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE;YAClD,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;SACjD;QACD,OAAO,GAAG,CAAC;IACb,CAAC,EACD,EAA4B,CAC7B,CAAC;IAEF,IAAI,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,EAAE;QAClC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACrC,KAAK,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;YAC1D,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,KAAK,KAAK,EAAE,CAAC,CAAC;SACzC;KACF;SAAM;QACL,MAAM,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC;KAC/D;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AArCD,4BAqCC"}

View file

@ -69,6 +69,7 @@ async function run() {
"Config file could not be found at expected location. Has the 'init' action been called?" "Config file could not be found at expected location. Has the 'init' action been called?"
); );
} }
const apiDetails = { const apiDetails = {
auth: actionsUtil.getRequiredInput("token"), auth: actionsUtil.getRequiredInput("token"),
url: actionsUtil.getRequiredEnvParam("GITHUB_SERVER_URL"), url: actionsUtil.getRequiredEnvParam("GITHUB_SERVER_URL"),

View file

@ -1,10 +1,13 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path";
import test from "ava"; import test from "ava";
import sinon from "sinon";
import { runQueries } from "./analyze"; import { runQueries } from "./analyze";
import { setCodeQL } from "./codeql"; import { setCodeQL } from "./codeql";
import { Config } from "./config-utils"; import { Config } from "./config-utils";
import * as count from "./count-loc";
import { Language } from "./languages"; import { Language } from "./languages";
import { getRunnerLogger } from "./logging"; import { getRunnerLogger } from "./logging";
import { setupTests, setupActionsVars } from "./testing-utils"; import { setupTests, setupActionsVars } from "./testing-utils";
@ -16,31 +19,63 @@ setupTests(test);
// and correct case of builtin or custom. Also checks the correct search // and correct case of builtin or custom. Also checks the correct search
// paths are set in the database analyze invocation. // paths are set in the database analyze invocation.
test("status report fields and search path setting", async (t) => { test("status report fields and search path setting", async (t) => {
const mockLinesOfCode = Object.entries(Language).reduce((obj, [lang], i) => {
// use a different line count for each languaged
obj[lang] = i + 1;
return obj;
}, {});
sinon.stub(count, "countLoc").resolves(mockLinesOfCode);
let searchPathsUsed: string[] = []; let searchPathsUsed: string[] = [];
return await util.withTmpDir(async (tmpDir) => { return await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir); setupActionsVars(tmpDir, tmpDir);
setCodeQL({
databaseAnalyze: async (
_,
sarifFile: string,
searchPath: string | undefined
) => {
fs.writeFileSync(
sarifFile,
JSON.stringify({
runs: [],
})
);
searchPathsUsed.push(searchPath!);
},
});
const memoryFlag = ""; const memoryFlag = "";
const addSnippetsFlag = ""; const addSnippetsFlag = "";
const threadsFlag = ""; const threadsFlag = "";
for (const language of Object.values(Language)) { for (const language of Object.values(Language)) {
setCodeQL({
databaseAnalyze: async (
_,
sarifFile: string,
searchPath: string | undefined
) => {
fs.writeFileSync(
sarifFile,
JSON.stringify({
runs: [
// variant 1 uses metricId
{
properties: {
metricResults: [
{
metricId: `${language}/summary/lines-of-code`,
value: 123,
},
],
},
},
// variant 2 uses metric.id
{
properties: {
metricResults: [
{
metric: {
id: `${language}/summary/lines-of-code`,
},
value: 123,
},
],
},
},
{},
],
})
);
searchPathsUsed.push(searchPath!);
},
});
searchPathsUsed = []; searchPathsUsed = [];
const config: Config = { const config: Config = {
languages: [language], languages: [language],
@ -103,5 +138,49 @@ test("status report fields and search path setting", async (t) => {
); );
t.deepEqual(searchPathsUsed, [undefined, "/1", "/2"]); t.deepEqual(searchPathsUsed, [undefined, "/1", "/2"]);
} }
verifyLineCounts(tmpDir);
}); });
function verifyLineCounts(tmpDir: string) {
// eslint-disable-next-line github/array-foreach
Object.keys(Language).forEach((lang, i) => {
verifyLineCountForFile(
lang,
path.join(tmpDir, `${lang}-builtin.sarif`),
i + 1
);
verifyLineCountForFile(
lang,
path.join(tmpDir, `${lang}-custom.sarif`),
i + 1
);
});
}
function verifyLineCountForFile(
lang: string,
filePath: string,
lineCount: number
) {
const sarif = JSON.parse(fs.readFileSync(filePath, "utf8"));
t.deepEqual(sarif.runs[0].properties.metricResults, [
{
metricId: `${lang}/summary/lines-of-code`,
value: 123,
baseline: lineCount,
},
]);
t.deepEqual(sarif.runs[1].properties.metricResults, [
{
metric: {
id: `${lang}/summary/lines-of-code`,
},
value: 123,
baseline: lineCount,
},
]);
// when the metric doesn't exists, it should not be added
t.deepEqual(sarif.runs[2].properties.metricResults, []);
}
}); });

View file

@ -6,6 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
import * as analysisPaths from "./analysis-paths"; import * as analysisPaths from "./analysis-paths";
import { getCodeQL } from "./codeql"; import { getCodeQL } from "./codeql";
import * as configUtils from "./config-utils"; import * as configUtils from "./config-utils";
import { countLoc } from "./count-loc";
import { isScannedLanguage, Language } from "./languages"; import { isScannedLanguage, Language } from "./languages";
import { Logger } from "./logging"; import { Logger } from "./logging";
import * as sharedEnv from "./shared-environment"; import * as sharedEnv from "./shared-environment";
@ -144,6 +145,18 @@ export async function runQueries(
): Promise<QueriesStatusReport> { ): Promise<QueriesStatusReport> {
const statusReport: QueriesStatusReport = {}; const statusReport: QueriesStatusReport = {};
// count the number of lines in the background
const locPromise = countLoc(
path.resolve(),
// config.paths specifies external directories. the current
// directory is included in the analysis by default. Replicate
// that here.
["**"].concat(config.paths || []),
config.pathsIgnore,
config.languages,
logger
);
for (const language of config.languages) { for (const language of config.languages) {
logger.startGroup(`Analyzing ${language}`); logger.startGroup(`Analyzing ${language}`);
@ -157,13 +170,15 @@ export async function runQueries(
try { try {
if (queries["builtin"].length > 0) { if (queries["builtin"].length > 0) {
const startTimeBuliltIn = new Date().getTime(); const startTimeBuliltIn = new Date().getTime();
await runQueryGroup( const sarifFile = await runQueryGroup(
language, language,
"builtin", "builtin",
queries["builtin"], queries["builtin"],
sarifFolder, sarifFolder,
undefined undefined
); );
await injectLinesOfCode(sarifFile, language, locPromise);
statusReport[`analyze_builtin_queries_${language}_duration_ms`] = statusReport[`analyze_builtin_queries_${language}_duration_ms`] =
new Date().getTime() - startTimeBuliltIn; new Date().getTime() - startTimeBuliltIn;
} }
@ -172,23 +187,21 @@ export async function runQueries(
const temporarySarifFiles: string[] = []; const temporarySarifFiles: string[] = [];
for (let i = 0; i < queries["custom"].length; ++i) { for (let i = 0; i < queries["custom"].length; ++i) {
if (queries["custom"][i].queries.length > 0) { if (queries["custom"][i].queries.length > 0) {
await runQueryGroup( const sarifFile = await runQueryGroup(
language, language,
`custom-${i}`, `custom-${i}`,
queries["custom"][i].queries, queries["custom"][i].queries,
temporarySarifDir, temporarySarifDir,
queries["custom"][i].searchPath queries["custom"][i].searchPath
); );
temporarySarifFiles.push( temporarySarifFiles.push(sarifFile);
path.join(temporarySarifDir, `${language}-custom-${i}.sarif`)
);
} }
} }
if (temporarySarifFiles.length > 0) { if (temporarySarifFiles.length > 0) {
fs.writeFileSync( const sarifFile = path.join(sarifFolder, `${language}-custom.sarif`);
path.join(sarifFolder, `${language}-custom.sarif`), fs.writeFileSync(sarifFile, combineSarifFiles(temporarySarifFiles));
combineSarifFiles(temporarySarifFiles) await injectLinesOfCode(sarifFile, language, locPromise);
);
statusReport[`analyze_custom_queries_${language}_duration_ms`] = statusReport[`analyze_custom_queries_${language}_duration_ms`] =
new Date().getTime() - startTimeCustom; new Date().getTime() - startTimeCustom;
} }
@ -210,7 +223,7 @@ export async function runQueries(
queries: string[], queries: string[],
destinationFolder: string, destinationFolder: string,
searchPath: string | undefined searchPath: string | undefined
): Promise<void> { ): Promise<string> {
const databasePath = util.getCodeQLDatabasePath(config.tempDir, language); const databasePath = util.getCodeQLDatabasePath(config.tempDir, language);
// Pass the queries to codeql using a file instead of using the command // Pass the queries to codeql using a file instead of using the command
// line to avoid command line length restrictions, particularly on windows. // line to avoid command line length restrictions, particularly on windows.
@ -238,6 +251,8 @@ export async function runQueries(
`SARIF results for database ${language} created at "${sarifFile}"` `SARIF results for database ${language} created at "${sarifFile}"`
); );
logger.endGroup(); logger.endGroup();
return sarifFile;
} }
} }
@ -269,3 +284,30 @@ export async function runAnalyze(
return { ...queriesStats }; return { ...queriesStats };
} }
async function injectLinesOfCode(
sarifFile: string,
language: string,
locPromise: Promise<Record<string, number>>
) {
const lineCounts = await locPromise;
if (language in lineCounts) {
const sarif = JSON.parse(fs.readFileSync(sarifFile, "utf8"));
if (Array.isArray(sarif.runs)) {
for (const run of sarif.runs) {
const metricId = `${language}/summary/lines-of-code`;
run.properties = run.properties || {};
run.properties.metricResults = run.properties.metricResults || [];
const metric = run.properties.metricResults.find(
// the metric id can be in either of two places
(m) => m.metricId === metricId || m.metric?.id === metricId
);
// only add the baseline value if the metric already exists
if (metric) {
metric.baseline = lineCounts[language];
}
}
}
fs.writeFileSync(sarifFile, JSON.stringify(sarif));
}
}

65
src/count-loc.ts Normal file
View file

@ -0,0 +1,65 @@
import { LocDir } from "github-linguist";
import { Logger } from "./logging";
// Map from linguist language names to language prefixes used in our metrics
const supportedLanguages = {
c: "cpp",
"c++": "cpp",
"c#": "cs",
go: "go",
java: "java",
javascript: "js",
python: "py",
ruby: "rb",
typescript: "js",
};
/**
* Count the lines of code of the specified language using the include
* and exclude glob paths.
*
* @param cwd the root directory to start the count from
* @param include glob patterns to include in the search for relevant files
* @param exclude glob patterns to exclude in the search for relevant files
* @param dbLanguages list of languages to include in the results
* @param logger object to log results
*/
export async function countLoc(
cwd: string,
include: string[],
exclude: string[],
dbLanguages: string[],
logger: Logger
): Promise<Record<string, number>> {
const result = await new LocDir({
cwd,
include,
exclude,
}).loadInfo();
// The analysis counts LoC in all languages. We need to
// extract the languages we care about. Also, note that
// the analysis uses slightly different names for language.
const lineCounts = Object.entries(result.languages).reduce(
(obj, [language, { code }]) => {
const dbLanguage = supportedLanguages[language];
if (dbLanguage && dbLanguages.includes(dbLanguage)) {
obj[dbLanguage] = code + (obj[dbLanguage] || 0);
}
return obj;
},
{} as Record<string, number>
);
if (Object.keys(lineCounts).length) {
logger.debug("Lines of code count:");
for (const [language, count] of Object.entries(lineCounts)) {
logger.debug(` ${language}: ${count}`);
}
} else {
logger.warning("Did not find any lines of code in database.");
}
return lineCounts;
}