diff --git a/package-lock.json b/package-lock.json index d443b58ae..b585767f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@code-pushup/portal-client": "^0.1.2", - "@nx/nx-darwin-x64": "^16.9.1", + "@nx/nx-darwin-x64": "^16.10.0", "@swc/helpers": "~0.5.0", "bundle-require": "^4.0.1", "chalk": "^5.3.0", diff --git a/packages/core/src/lib/implementation/json-to-gql.ts b/packages/core/src/lib/implementation/json-to-gql.ts index 540213501..637d8b986 100644 --- a/packages/core/src/lib/implementation/json-to-gql.ts +++ b/packages/core/src/lib/implementation/json-to-gql.ts @@ -1,10 +1,10 @@ import { CategoryConfigRefType, - IssueSeverity, IssueSourceType, + IssueSeverity as PortalIssueSeverity, SaveReportMutationVariables, } from '@code-pushup/portal-client'; -import { Issue, Report } from '@code-pushup/models'; +import { IssueSeverity as CliIssueSeverity, Report } from '@code-pushup/models'; export function jsonToGql(report: Report) { return { @@ -71,13 +71,13 @@ export function jsonToGql(report: Report) { >; } -function transformSeverity(severity: Issue['severity']): IssueSeverity { +function transformSeverity(severity: CliIssueSeverity): PortalIssueSeverity { switch (severity) { case 'info': - return IssueSeverity.Info; + return PortalIssueSeverity.Info; case 'error': - return IssueSeverity.Error; + return PortalIssueSeverity.Error; case 'warning': - return IssueSeverity.Warning; + return PortalIssueSeverity.Warning; } } diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 51b094a14..7477a4021 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -1,4 +1,8 @@ -export { CategoryConfig, categoryConfigSchema } from './lib/category-config'; +export { + CategoryRef, + CategoryConfig, + categoryConfigSchema, +} from './lib/category-config'; export { CoreConfig, coreConfigSchema, @@ -14,10 +18,12 @@ export { } from './lib/persist-config'; export { Audit, + AuditGroupRef, AuditGroup, AuditOutput, AuditOutputs, Issue, + IssueSeverity, PluginConfig, auditGroupSchema, auditOutputsSchema, diff --git a/packages/models/src/lib/category-config.ts b/packages/models/src/lib/category-config.ts index 6ede923e9..cf6d6248a 100644 --- a/packages/models/src/lib/category-config.ts +++ b/packages/models/src/lib/category-config.ts @@ -13,22 +13,26 @@ type _RefsList = { plugin?: string; }[]; +const categoryRefSchema = weightedRefSchema( + 'Weighted references to audits and/or groups for the category', + 'Slug of an audit or group (depending on `type`)', +).merge( + z.object({ + type: z.enum(['audit', 'group'], { + description: + 'Discriminant for reference kind, affects where `slug` is looked up', + }), + plugin: slugSchema( + 'Plugin slug (plugin should contain referenced audit or group)', + ), + }), +); + +export type CategoryRef = z.infer; + export const categoryConfigSchema = scorableSchema( 'Category with a score calculated from audits and groups from various plugins', - weightedRefSchema( - 'Weighted references to audits and/or groups for the category', - 'Slug of an audit or group (depending on `type`)', - ).merge( - z.object({ - type: z.enum(['audit', 'group'], { - description: - 'Discriminant for reference kind, affects where `slug` is looked up', - }), - plugin: slugSchema( - 'Plugin slug (plugin should contain referenced audit or group)', - ), - }), - ), + categoryRefSchema, getDuplicateRefsInCategoryMetrics, duplicateRefsInCategoryMetricsErrorMsg, ) @@ -60,6 +64,7 @@ export function duplicateRefsInCategoryMetricsErrorMsg(metrics: _RefsList) { duplicateRefs, )}`; } + function getDuplicateRefsInCategoryMetrics(metrics: _RefsList) { return hasDuplicateStrings( metrics.map(({ slug, type, plugin }) => `${type} :: ${plugin} / ${slug}`), diff --git a/packages/models/src/lib/implementation/schemas.ts b/packages/models/src/lib/implementation/schemas.ts index 0701ff0d0..19fe88963 100644 --- a/packages/models/src/lib/implementation/schemas.ts +++ b/packages/models/src/lib/implementation/schemas.ts @@ -69,6 +69,10 @@ export function titleSchema(description = 'Descriptive name') { return z.string({ description }).max(256); } +/** + * Used for categories, plugins and audits + * @param options + */ export function metaSchema(options?: { titleDescription?: string; descriptionDescription?: string; @@ -116,16 +120,6 @@ export function fileNameSchema(description: string) { .min(1, { message: 'file name is invalid' }); } -/** - * Schema for a weight - * @param description - */ -export function weightSchema( - description = 'Coefficient for the given score (use weight 0 if only for display)', -) { - return positiveIntSchema(description); -} - /** * Schema for a positiveInt * @param description @@ -152,6 +146,16 @@ export function packageVersionSchema(options?: { ); } +/** + * Schema for a weight + * @param description + */ +export function weightSchema( + description = 'Coefficient for the given score (use weight 0 if only for display)', +) { + return positiveIntSchema(description); +} + export function weightedRefSchema( description: string, slugDescription: string, diff --git a/packages/models/src/lib/plugin-config.ts b/packages/models/src/lib/plugin-config.ts index e537a7c5e..7030f8de0 100644 --- a/packages/models/src/lib/plugin-config.ts +++ b/packages/models/src/lib/plugin-config.ts @@ -70,13 +70,15 @@ export const auditSchema = z export type Audit = z.infer; +const auditGroupRef = weightedRefSchema( + 'Weighted references to audits', + "Reference slug to an audit within this plugin (e.g. 'max-lines')", +); +export type AuditGroupRef = z.infer; export const auditGroupSchema = scorableSchema( 'An audit group aggregates a set of audits into a single score which can be referenced from a category. ' + 'E.g. the group slug "performance" groups audits and can be referenced in a category as "[plugin-slug]#group:[group-slug]")', - weightedRefSchema( - 'Weighted references to audits', - "Reference slug to an audit within this plugin (e.g. 'max-lines')", - ), + auditGroupRef, getDuplicateRefsInGroups, duplicateRefsInGroupsErrorMsg, ).merge( @@ -183,12 +185,14 @@ const sourceFileLocationSchema = z.object( { description: 'Source file location' }, ); +export const issueSeveritySchema = z.enum(['info', 'warning', 'error'], { + description: 'Severity level', +}); +export type IssueSeverity = z.infer; export const issueSchema = z.object( { message: z.string({ description: 'Descriptive error message' }).max(128), - severity: z.enum(['info', 'warning', 'error'], { - description: 'Severity level', - }), + severity: issueSeveritySchema, source: sourceFileLocationSchema.optional(), }, { description: 'Issue information' }, diff --git a/packages/models/test/fixtures/eslint-plugin.mock.ts b/packages/models/test/fixtures/eslint-plugin.mock.ts index 649f2e2af..ba5b3cedf 100644 --- a/packages/models/test/fixtures/eslint-plugin.mock.ts +++ b/packages/models/test/fixtures/eslint-plugin.mock.ts @@ -1,10 +1,5 @@ import { join } from 'node:path'; -import type { - Audit, - CategoryConfig, - PluginConfig, - PluginReport, -} from '../../src'; +import type { Audit, CategoryRef, PluginConfig, PluginReport } from '../../src'; import { echoRunnerConfig } from './echo-runner-config.mock'; import { ESLINT_AUDITS_MAP } from './eslint-audits.mock'; @@ -48,10 +43,7 @@ export function eslintPluginReport(): PluginReport { type ESLintAuditSlug = keyof typeof ESLINT_AUDITS_MAP; -export function eslintAuditRef( - slug: ESLintAuditSlug, - weight = 1, -): CategoryConfig['refs'][number] { +export function eslintAuditRef(slug: ESLintAuditSlug, weight = 1): CategoryRef { return { type: 'audit', plugin: 'eslint', diff --git a/packages/plugin-eslint/src/lib/runner/transform.ts b/packages/plugin-eslint/src/lib/runner/transform.ts index d930e19d7..0e212c4f1 100644 --- a/packages/plugin-eslint/src/lib/runner/transform.ts +++ b/packages/plugin-eslint/src/lib/runner/transform.ts @@ -1,5 +1,6 @@ import type { Linter } from 'eslint'; import type { AuditOutput, Issue } from '@code-pushup/models'; +import { IssueSeverity } from '@code-pushup/models'; import { compareIssueSeverity, countOccurrences, @@ -71,7 +72,7 @@ function convertIssue(issue: LintIssue): Issue { }; } -function convertSeverity(severity: Linter.Severity): Issue['severity'] { +function convertSeverity(severity: Linter.Severity): IssueSeverity { switch (severity) { case 2: return 'error'; diff --git a/packages/utils/src/lib/report.spec.ts b/packages/utils/src/lib/report.spec.ts index 5539bcb57..fdd404991 100644 --- a/packages/utils/src/lib/report.spec.ts +++ b/packages/utils/src/lib/report.spec.ts @@ -1,5 +1,5 @@ import { describe, expect } from 'vitest'; -import { CategoryConfig, Issue } from '@code-pushup/models'; +import { CategoryRef, IssueSeverity } from '@code-pushup/models'; import { calcDuration, compareIssueSeverity, @@ -98,7 +98,7 @@ describe('formatCount', () => { describe('countWeightedRefs', () => { it('should calc weighted refs only', () => { - const refs: CategoryConfig['refs'] = [ + const refs: CategoryRef[] = [ { slug: 'a1', weight: 0, @@ -119,16 +119,16 @@ describe('countWeightedRefs', () => { describe('compareIssueSeverity', () => { it('should order severities in logically ascending order when used as compareFn with .sort()', () => { expect( - (['error', 'info', 'warning'] satisfies Issue['severity'][]).sort( + (['error', 'info', 'warning'] satisfies IssueSeverity[]).sort( compareIssueSeverity, ), - ).toEqual(['info', 'warning', 'error'] satisfies Issue['severity'][]); + ).toEqual(['info', 'warning', 'error'] satisfies IssueSeverity[]); }); }); describe('sumRefs', () => { it('should sum refs correctly', () => { - const refs: CategoryConfig['refs'] = [ + const refs: CategoryRef[] = [ { slug: 'a1', weight: 0, diff --git a/packages/utils/src/lib/report.ts b/packages/utils/src/lib/report.ts index 9832727fe..c4745ecb0 100644 --- a/packages/utils/src/lib/report.ts +++ b/packages/utils/src/lib/report.ts @@ -1,4 +1,4 @@ -import { CategoryConfig, Issue } from '@code-pushup/models'; +import { CategoryRef, IssueSeverity } from '@code-pushup/models'; import { pluralize } from './utils'; export const FOOTER_PREFIX = 'Made with ❤️ by'; @@ -34,17 +34,17 @@ export function formatCount(count: number, name: string) { return `${count} ${text}`; } -export function countWeightedRefs(refs: CategoryConfig['refs']) { +export function countWeightedRefs(refs: CategoryRef[]) { return refs .filter(({ weight }) => weight > 0) .reduce((sum, { weight }) => sum + weight, 0); } export function compareIssueSeverity( - severity1: Issue['severity'], - severity2: Issue['severity'], + severity1: IssueSeverity, + severity2: IssueSeverity, ): number { - const levels: Record = { + const levels: Record = { info: 0, warning: 1, error: 2, @@ -53,6 +53,6 @@ export function compareIssueSeverity( } // @TODO replace with real scoring logic -export function sumRefs(refs: CategoryConfig['refs']) { +export function sumRefs(refs: CategoryRef[]) { return refs.reduce((sum, { weight }) => sum + weight, 0); } diff --git a/packages/utils/src/lib/scoring.ts b/packages/utils/src/lib/scoring.ts index 4db112a38..65441754a 100644 --- a/packages/utils/src/lib/scoring.ts +++ b/packages/utils/src/lib/scoring.ts @@ -1,7 +1,9 @@ import { AuditGroup, + AuditGroupRef, AuditReport, CategoryConfig, + CategoryRef, PluginReport, Report, } from '@code-pushup/models'; @@ -20,7 +22,7 @@ export type ScoredReport = Omit & { function groupRefToScore( audits: AuditReport[], -): (ref: AuditGroup['refs'][0]) => number { +): (ref: AuditGroupRef) => number { return ref => { const score = audits.find(audit => audit.slug === ref.slug)?.score; if (score == null) { @@ -35,8 +37,8 @@ function groupRefToScore( function categoryRefToScore( audits: EnrichedAuditReport[], groups: EnrichedScoredAuditGroup[], -): (ref: CategoryConfig['refs'][0]) => number { - return (ref: CategoryConfig['refs'][0]): number => { +): (ref: CategoryRef) => number { + return (ref: CategoryRef): number => { switch (ref.type) { case 'audit': // eslint-disable-next-line no-case-declarations diff --git a/tmp/.gitkeep b/tmp/.gitkeep new file mode 100644 index 000000000..e69de29bb