UNPKG

@atomist/sdm-pack-aspect

Version:

an Atomist SDM Extension Pack for visualizing drift across an organization

315 lines 12 kB
"use strict"; /* * Copyright © 2019 Atomist, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const sdm_pack_fingerprint_1 = require("@atomist/sdm-pack-fingerprint"); const _ = require("lodash"); const codeMetrics_1 = require("../aspect/common/codeMetrics"); const reviewerAspect_1 = require("../aspect/common/reviewerAspect"); const codeOfConduct_1 = require("../aspect/community/codeOfConduct"); const license_1 = require("../aspect/community/license"); const globAspect_1 = require("../aspect/compose/globAspect"); const branchCount_1 = require("../aspect/git/branchCount"); const dateUtils_1 = require("../aspect/git/dateUtils"); const gitActivity_1 = require("../aspect/git/gitActivity"); const Score_1 = require("./Score"); const scoring_1 = require("./scoring"); exports.CommunityCategory = "community"; exports.CodeCategory = "code"; /** * Use to anchor scores to penalize repositories about which we know little. * Typically weighted > default x1 */ function anchorScoreAt(score) { const scoreFingerprints = () => __awaiter(this, void 0, void 0, function* () { return { reason: `Weight to ${score} stars to penalize repositories about which we know little`, score, }; }); return { name: "anchor", category: Score_1.AlwaysIncludeCategory, scoreFingerprints, }; } exports.anchorScoreAt = anchorScoreAt; /** * Penalize repositories for not having a recent commit. * days is the number of days since the latest commit to the default branch * that will cost 1 star. */ function requireRecentCommit(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const grt = repo.analysis.fingerprints.find(fp => fp.type === gitActivity_1.GitRecencyType); if (!grt) { return undefined; } if (!grt.data.lastCommitTime) { return undefined; } const date = new Date(grt.data.lastCommitTime); const days = dateUtils_1.daysSince(date); return { score: scoring_1.adjustBy((1 - days) / (opts.days || 1)), reason: `Last commit ${days} days ago`, }; }); return { name: "recency", scoreFingerprints, baseOnly: true, }; } exports.requireRecentCommit = requireRecentCommit; /** * Limit languages used in a project */ function limitLanguages(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const cm = repo.analysis.fingerprints.find(codeMetrics_1.isCodeMetricsFingerprint); if (!cm) { return undefined; } return { score: scoring_1.adjustBy(opts.limit - cm.data.languages.length), reason: `Found ${cm.data.languages.length} languages: ${cm.data.languages.map(l => l.language.name).join(",")}`, }; }); return { name: "multi-language", scoreFingerprints, baseOnly: opts.baseOnly, }; } exports.limitLanguages = limitLanguages; /** * Penalize repositories for having too many lines of code. * The limit is the number of lines of code required to drop one star. * The chosen limit will depend on team preferences: For example, * are we trying to do microservices? */ function limitLinesOfCode(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const cm = repo.analysis.fingerprints.find(codeMetrics_1.isCodeMetricsFingerprint); if (!cm) { return undefined; } return { score: scoring_1.adjustBy(-cm.data.lines / opts.limit), reason: `Found ${cm.data.lines} total lines of code`, }; }); return { name: "total-loc", category: exports.CodeCategory, scoreFingerprints, baseOnly: opts.baseOnly, }; } exports.limitLinesOfCode = limitLinesOfCode; /** * Penalize repositories for having too many lines of code in the given language */ function limitLinesOfCodeIn(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const cm = repo.analysis.fingerprints.find(codeMetrics_1.isCodeMetricsFingerprint); if (!cm) { return undefined; } const target = cm.data.languages.find(l => l.language.name === opts.language.name); const targetLoc = target ? target.total : 0; return { score: scoring_1.adjustBy(((opts.freeAmount || 0) - targetLoc) / opts.limit), reason: `Found ${targetLoc} lines of ${opts.language.name}`, }; }); return { name: `limit-${opts.language.name} (${opts.limit})`, scoreFingerprints, }; } exports.limitLinesOfCodeIn = limitLinesOfCodeIn; /** * Penalize repositories for having an excessive number of git branches */ function penalizeForExcessiveBranches(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const branchCount = repo.analysis.fingerprints.find(f => f.type === branchCount_1.BranchCountType); if (!branchCount) { return undefined; } // You get the first 2 branches for free. After that they start to cost const score = scoring_1.adjustBy(-(branchCount.data.count - 2) / opts.branchLimit); return branchCount ? { score, reason: `${branchCount.data.count} branches: Should not have more than ${opts.branchLimit}`, } : undefined; }); return { name: branchCount_1.BranchCountType, scoreFingerprints, baseOnly: true, }; } exports.penalizeForExcessiveBranches = penalizeForExcessiveBranches; /** * Penalize repositories for having more than 1 virtual project, * as identified by a VirtualProjectFinder */ exports.PenalizeMonorepos = { scoreFingerprints: (repo) => __awaiter(void 0, void 0, void 0, function* () { const distinctPaths = _.uniq(repo.analysis.fingerprints.map(t => t.path)).length; return { score: scoring_1.adjustBy(1 - distinctPaths / 2), reason: distinctPaths > 1 ? `${distinctPaths} virtual projects: Prefer one project per repository` : "Single project in repository", }; }), name: "monorepo", scoreAll: true, }; /** * Penalize repositories without a license file */ exports.PenalizeNoLicense = { name: "require-license", category: exports.CommunityCategory, baseOnly: true, scoreFingerprints: (repo) => __awaiter(void 0, void 0, void 0, function* () { const license = repo.analysis.fingerprints.find(license_1.isLicenseFingerprint); const bad = !license || license_1.hasNoLicense(license.data); return { score: bad ? 1 : 5, reason: bad ? "Repositories should have a license" : "Repository has a license", }; }), }; /** * Penalize repositories without a code of conduct file */ exports.PenalizeNoCodeOfConduct = requireAspectOfType({ type: codeOfConduct_1.CodeOfConductType, category: exports.CommunityCategory, reason: "Repos should have a code of conduct", baseOnly: true, }); /** * Penalize repositories that don't have this type of aspect. * If data is provided, check that the sha matches the default sha-ing of this * data payload */ function requireAspectOfType(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const found = repo.analysis.fingerprints.find(fp => fp.type === opts.type && (opts.data ? fp.sha === sdm_pack_fingerprint_1.sha256(JSON.stringify(opts.data)) : true)); const score = !!found ? 5 : 1; return { score, reason: !found ? opts.reason : "Satisfactory", }; }); return { name: `${opts.type}-required`, category: opts.category, scoreFingerprints, baseOnly: opts.baseOnly, }; } exports.requireAspectOfType = requireAspectOfType; /** * Penalize repositories without matches for the glob pattern. * Depends on globAspect */ function requireGlobAspect(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const globs = repo.analysis.fingerprints.filter(globAspect_1.isGlobMatchFingerprint); const found = globs .filter(gf => gf.data.glob === opts.glob) .filter(f => f.data.matches.length > 0); const score = !!found ? 5 : 1; return { score, reason: !found ? `Should have file for ${opts.glob}` : "Satisfactory", }; }); return { name: `${opts.glob}-required`, category: opts.category, scoreFingerprints, baseOnly: opts.baseOnly, }; } exports.requireGlobAspect = requireGlobAspect; /** * Penalize for each point lost in these reviewers */ function penalizeForReviewViolations(opts) { return { name: opts.reviewerName, scoreFingerprints: (repo) => __awaiter(this, void 0, void 0, function* () { const found = reviewerAspect_1.findReviewCommentCountFingerprint(opts.reviewerName, repo.analysis.fingerprints); if (!found) { return undefined; } const score = scoring_1.adjustBy(-found.data.count / opts.violationsPerPointLost); return { score, reason: `${found.data.count} review comments found for ${opts.reviewerName}`, }; }), }; } exports.penalizeForReviewViolations = penalizeForReviewViolations; /** * Convenient function to emit scorers for all reviewers */ function penalizeForAllReviewViolations(opts) { return opts.reviewerNames.map(reviewerName => penalizeForReviewViolations({ reviewerName, violationsPerPointLost: opts.violationsPerPointLost, })); } exports.penalizeForAllReviewViolations = penalizeForAllReviewViolations; /** * Use for a file pattern or something within files we don't want */ function penalizeGlobMatches(opts) { const scoreFingerprints = (repo) => __awaiter(this, void 0, void 0, function* () { const count = globAspect_1.countGlobMatches(repo.analysis.fingerprints, opts.type); const score = scoring_1.adjustBy(-count * opts.pointsLostPerMatch); return { score, reason: `${count} matches for glob typed ${opts.type}: Should have none`, }; }); return { name: opts.name || opts.type, scoreFingerprints, }; } exports.penalizeGlobMatches = penalizeGlobMatches; //# sourceMappingURL=commonScorers.js.map