Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/plugin-typescript/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { TS_CODE_RANGE_NAMES } from './runner/ts-error-codes.js';
import type { AuditSlug } from './types.js';

export const TYPESCRIPT_PLUGIN_SLUG = 'typescript';
export const TYPESCRIPT_PLUGIN_TITLE = 'TypeScript';

export const DEFAULT_TS_CONFIG = 'tsconfig.json';

const AUDIT_DESCRIPTIONS: Record<AuditSlug, string> = {
Expand Down
4 changes: 4 additions & 0 deletions packages/plugin-typescript/src/lib/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { pluginMetaLogFormatter } from '@code-pushup/utils';
import { TYPESCRIPT_PLUGIN_TITLE } from './constants.js';

export const formatMetaLog = pluginMetaLogFormatter(TYPESCRIPT_PLUGIN_TITLE);
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { describe, expect } from 'vitest';
import type { AuditOutputs } from '@code-pushup/models';
import { type AuditOutputs, DEFAULT_PERSIST_CONFIG } from '@code-pushup/models';
import { osAgnosticAuditOutputs } from '@code-pushup/test-utils';
import { getAudits } from '../utils.js';
import { createRunnerFunction } from './runner.js';

describe('createRunnerFunction', () => {
it('should create valid audit outputs when called', async () => {

Check failure on line 8 in packages/plugin-typescript/src/lib/runner/runner.int.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
const runnerFunction = createRunnerFunction({
tsconfig:
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.all-audits.json',
expectedAudits: getAudits(),
});

const result = await runnerFunction();
const result = await runnerFunction({ persist: DEFAULT_PERSIST_CONFIG });

expect(osAgnosticAuditOutputs(result as AuditOutputs)).toMatchSnapshot();
}, 35_000);
Expand Down
26 changes: 23 additions & 3 deletions packages/plugin-typescript/src/lib/runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import type {
Issue,
RunnerFunction,
} from '@code-pushup/models';
import { pluralizeToken } from '@code-pushup/utils';
import {
formatAsciiTable,
logger,
pluralizeToken,
toSentenceCase,
} from '@code-pushup/utils';
import type { AuditSlug } from '../types.js';
import {
type DiagnosticsOptions,
Expand All @@ -19,8 +24,10 @@ export type RunnerOptions = DiagnosticsOptions & {

export function createRunnerFunction(options: RunnerOptions): RunnerFunction {
const { tsconfig, expectedAudits } = options;
return async (): Promise<AuditOutputs> => {
const diagnostics = await getTypeScriptDiagnostics({ tsconfig });

return (): AuditOutputs => {
const diagnostics = getTypeScriptDiagnostics({ tsconfig });

const result = diagnostics.reduce<
Partial<Record<CodeRangeName, Pick<AuditOutput, 'slug' | 'details'>>>
>((acc, diag) => {
Expand All @@ -37,6 +44,19 @@ export function createRunnerFunction(options: RunnerOptions): RunnerFunction {
};
}, {});

logger.debug(
formatAsciiTable(
{
columns: ['left', 'right'],
rows: Object.values(result).map(audit => [
`• ${toSentenceCase(audit.slug)}`,
audit.details?.issues?.length ?? 0,
]),
},
{ borderless: true },
),
);

return expectedAudits.map(({ slug }): AuditOutput => {
const { details } = result[slug] ?? {};

Expand Down
50 changes: 28 additions & 22 deletions packages/plugin-typescript/src/lib/runner/runner.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,28 @@
type SourceFile,
} from 'typescript';
import { beforeEach, describe, expect } from 'vitest';
import { auditOutputsSchema } from '@code-pushup/models';
import {
DEFAULT_PERSIST_CONFIG,
type RunnerArgs,
auditOutputsSchema,
} from '@code-pushup/models';
import { createRunnerFunction } from './runner.js';
import * as runnerModule from './ts-runner.js';
import * as utilsModule from './utils.js';

describe('createRunnerFunction', () => {
const getTypeScriptDiagnosticsSpy = vi.spyOn(

Check failure on line 17 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2304: Cannot find name 'vi'.
runnerModule,
'getTypeScriptDiagnostics',
);
const tSCodeToAuditSlugSpy = vi.spyOn(utilsModule, 'tsCodeToAuditSlug');

Check failure on line 21 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2304: Cannot find name 'vi'.
const getIssueFromDiagnosticSpy = vi.spyOn(

Check failure on line 22 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2304: Cannot find name 'vi'.
utilsModule,
'getIssueFromDiagnostic',
);

const runnerArgs: RunnerArgs = { persist: DEFAULT_PERSIST_CONFIG };

const semanticTsCode = 2322;
const mockSemanticDiagnostic = {
code: semanticTsCode, // "Type 'string' is not assignable to type 'number'"
Expand Down Expand Up @@ -47,51 +53,51 @@
getTypeScriptDiagnosticsSpy.mockReset();
});

it('should return empty array if no diagnostics are found', async () => {
getTypeScriptDiagnosticsSpy.mockResolvedValue([]);
it('should return empty array if no diagnostics are found', () => {

Check failure on line 56 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
getTypeScriptDiagnosticsSpy.mockReturnValue([]);
const runner = createRunnerFunction({
tsconfig: 'tsconfig.json',
expectedAudits: [],
});
await expect(runner(() => void 0)).resolves.toStrictEqual([]);
expect(runner(runnerArgs)).toStrictEqual([]);
});

it('should return empty array if no supported diagnostics are found', async () => {
getTypeScriptDiagnosticsSpy.mockResolvedValue([mockSemanticDiagnostic]);
it('should return empty array if no supported diagnostics are found', () => {

Check failure on line 65 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
getTypeScriptDiagnosticsSpy.mockReturnValue([mockSemanticDiagnostic]);
const runner = createRunnerFunction({
tsconfig: 'tsconfig.json',
expectedAudits: [],
});
await expect(runner(() => void 0)).resolves.toStrictEqual([]);
expect(runner(runnerArgs)).toStrictEqual([]);
});

it('should pass the diagnostic code to tsCodeToSlug', async () => {
getTypeScriptDiagnosticsSpy.mockResolvedValue([mockSemanticDiagnostic]);
it('should pass the diagnostic code to tsCodeToSlug', () => {

Check failure on line 74 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
getTypeScriptDiagnosticsSpy.mockReturnValue([mockSemanticDiagnostic]);
const runner = createRunnerFunction({
tsconfig: 'tsconfig.json',
expectedAudits: [],
});
await expect(runner(() => void 0)).resolves.toStrictEqual([]);
expect(runner(runnerArgs)).toStrictEqual([]);
expect(tSCodeToAuditSlugSpy).toHaveBeenCalledTimes(1);
expect(tSCodeToAuditSlugSpy).toHaveBeenCalledWith(semanticTsCode);
});

it('should pass the diagnostic to getIssueFromDiagnostic', async () => {
getTypeScriptDiagnosticsSpy.mockResolvedValue([mockSemanticDiagnostic]);
it('should pass the diagnostic to getIssueFromDiagnostic', () => {

Check failure on line 85 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
getTypeScriptDiagnosticsSpy.mockReturnValue([mockSemanticDiagnostic]);
const runner = createRunnerFunction({
tsconfig: 'tsconfig.json',
expectedAudits: [],
});
await expect(runner(() => void 0)).resolves.toStrictEqual([]);
expect(runner(runnerArgs)).toStrictEqual([]);
expect(getIssueFromDiagnosticSpy).toHaveBeenCalledTimes(1);
expect(getIssueFromDiagnosticSpy).toHaveBeenCalledWith(
mockSemanticDiagnostic,
);
});

it('should return multiple issues per audit', async () => {
it('should return multiple issues per audit', () => {

Check failure on line 98 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
const code = 2222;
getTypeScriptDiagnosticsSpy.mockResolvedValue([
getTypeScriptDiagnosticsSpy.mockReturnValue([
mockSemanticDiagnostic,
{
...mockSemanticDiagnostic,
Expand All @@ -104,7 +110,7 @@
expectedAudits: [{ slug: 'semantic-errors' }],
});

const auditOutputs = await runner(() => void 0);
const auditOutputs = runner(runnerArgs);
expect(auditOutputs).toStrictEqual([
{
slug: 'semantic-errors',
Expand All @@ -126,8 +132,8 @@
expect(() => auditOutputsSchema.parse(auditOutputs)).not.toThrow();
});

it('should return multiple audits', async () => {
getTypeScriptDiagnosticsSpy.mockResolvedValue([
it('should return multiple audits', () => {

Check failure on line 135 in packages/plugin-typescript/src/lib/runner/runner.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
getTypeScriptDiagnosticsSpy.mockReturnValue([
mockSyntacticDiagnostic,
mockSemanticDiagnostic,
]);
Expand All @@ -136,7 +142,7 @@
expectedAudits: [{ slug: 'semantic-errors' }, { slug: 'syntax-errors' }],
});

const auditOutputs = await runner(() => void 0);
const auditOutputs = runner(runnerArgs);
expect(auditOutputs).toStrictEqual([
expect.objectContaining({
slug: 'semantic-errors',
Expand All @@ -159,8 +165,8 @@
]);
});

it('should return valid AuditOutput shape', async () => {
getTypeScriptDiagnosticsSpy.mockResolvedValue([
it('should return valid AuditOutput shape', () => {
getTypeScriptDiagnosticsSpy.mockReturnValue([
mockSyntacticDiagnostic,
{
...mockSyntacticDiagnostic,
Expand All @@ -178,7 +184,7 @@
tsconfig: 'tsconfig.json',
expectedAudits: [{ slug: 'semantic-errors' }, { slug: 'syntax-errors' }],
});
const auditOutputs = await runner(() => void 0);
const auditOutputs = runner(runnerArgs);
expect(() => auditOutputsSchema.parse(auditOutputs)).not.toThrow();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { describe, expect } from 'vitest';
import { getTypeScriptDiagnostics } from './ts-runner.js';

describe('getTypeScriptDiagnostics', () => {
it('should return valid diagnostics', async () => {
await expect(
it('should return valid diagnostics', () => {
expect(
getTypeScriptDiagnostics({
tsconfig:
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig.json',
}),
).resolves.toHaveLength(8);
).toHaveLength(8);
});
});
17 changes: 12 additions & 5 deletions packages/plugin-typescript/src/lib/runner/ts-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@ import {
createProgram,
getPreEmitDiagnostics,
} from 'typescript';
import { stringifyError } from '@code-pushup/utils';
import { logger, pluralizeToken, stringifyError } from '@code-pushup/utils';
import { loadTargetConfig } from './utils.js';

export type DiagnosticsOptions = {
tsconfig: string;
};

export async function getTypeScriptDiagnostics({
export function getTypeScriptDiagnostics({
tsconfig,
}: DiagnosticsOptions): Promise<readonly Diagnostic[]> {
}: DiagnosticsOptions): readonly Diagnostic[] {
const { fileNames, options } = loadTargetConfig(tsconfig);
logger.info(
`Parsed TypeScript config file ${tsconfig}, program includes ${pluralizeToken('file', fileNames.length)}`,
);
try {
const program = createProgram(fileNames, options);
return getPreEmitDiagnostics(program);
const diagnostics = getPreEmitDiagnostics(program);
logger.info(
`TypeScript compiler found ${pluralizeToken('diagnostic', diagnostics.length)}`,
);
return diagnostics;
} catch (error) {
throw new Error(
`Can't create TS program in getDiagnostics. \n ${stringifyError(error)}`,
`Can't create TS program and get diagnostics.\n${stringifyError(error)}`,
);
}
}
16 changes: 5 additions & 11 deletions packages/plugin-typescript/src/lib/runner/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { CodeRangeName } from './types.js';

/**
* Transform the TypeScript error code to the audit slug.
* @param code - The TypeScript error code.
* @param code The TypeScript error code.
* @returns The audit slug.
* @throws Error if the code is not supported.
*/
Expand All @@ -31,7 +31,7 @@ export function tsCodeToAuditSlug(code: number): CodeRangeName {
* - ts.DiagnosticCategory.Error (2)
* - ts.DiagnosticCategory.Suggestion (3)
* - ts.DiagnosticCategory.Message (4)
* @param category - The TypeScript diagnostic category.
* @param category The TypeScript diagnostic category.
* @returns The severity of the issue.
*/
export function getSeverity(category: DiagnosticCategory): Issue['severity'] {
Expand All @@ -47,7 +47,7 @@ export function getSeverity(category: DiagnosticCategory): Issue['severity'] {

/**
* Format issue message from the TypeScript diagnostic.
* @param diag - The TypeScript diagnostic.
* @param diag The TypeScript diagnostic.
* @returns The issue message.
*/
export function getMessage(diag: Diagnostic): string {
Expand All @@ -60,7 +60,7 @@ export function getMessage(diag: Diagnostic): string {

/**
* Get the issue from the TypeScript diagnostic.
* @param diag - The TypeScript diagnostic.
* @param diag The TypeScript diagnostic.
* @returns The issue.
* @throws Error if the diagnostic is global (e.g., invalid compiler option).
*/
Expand All @@ -84,13 +84,7 @@ export function getIssueFromDiagnostic(diag: Diagnostic) {
...issue,
source: {
file: path.relative(process.cwd(), diag.file.fileName),
...(startLine
? {
position: {
startLine,
},
}
: {}),
...(startLine ? { position: { startLine } } : {}),
},
} satisfies Issue;
}
Expand Down
28 changes: 16 additions & 12 deletions packages/plugin-typescript/src/lib/typescript-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { createRequire } from 'node:module';
import { type PluginConfig, validate } from '@code-pushup/models';
import { stringifyError } from '@code-pushup/utils';
import { DEFAULT_TS_CONFIG, TYPESCRIPT_PLUGIN_SLUG } from './constants.js';
import {
DEFAULT_TS_CONFIG,
TYPESCRIPT_PLUGIN_SLUG,
TYPESCRIPT_PLUGIN_TITLE,
} from './constants.js';
import { createRunnerFunction } from './runner/runner.js';
import {
type TypescriptPluginConfig,
type TypescriptPluginOptions,
typescriptPluginConfigSchema,
} from './schema.js';
import { getAudits, getGroups, logSkippedAudits } from './utils.js';
import { getAudits, getGroups, logAuditsAndGroups } from './utils.js';

const packageJson = createRequire(import.meta.url)(
'../../package.json',
Expand All @@ -23,24 +27,24 @@ export function typescriptPlugin(
scoreTargets,
} = parseOptions(options ?? {});

const filteredAudits = getAudits({ onlyAudits });
const filteredGroups = getGroups({ onlyAudits });
const audits = getAudits({ onlyAudits });
const groups = getGroups({ onlyAudits });

logSkippedAudits(filteredAudits);
logAuditsAndGroups(audits, groups);

return {
slug: TYPESCRIPT_PLUGIN_SLUG,
packageName: packageJson.name,
version: packageJson.version,
title: 'TypeScript',
title: TYPESCRIPT_PLUGIN_TITLE,
icon: 'typescript',
description: 'Official Code PushUp TypeScript plugin.',
docsUrl: 'https://www.npmjs.com/package/@code-pushup/typescript-plugin/',
icon: 'typescript',
audits: filteredAudits,
groups: filteredGroups,
packageName: packageJson.name,
version: packageJson.version,
audits,
groups,
runner: createRunnerFunction({
tsconfig,
expectedAudits: filteredAudits,
expectedAudits: audits,
}),
...(scoreTargets && { scoreTargets }),
};
Expand Down
Loading
Loading