Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 2 additions & 1 deletion packages/plugin-typescript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
"type": "module",
"dependencies": {
"@code-pushup/models": "0.59.0",
"@code-pushup/utils": "0.59.0"
"@code-pushup/utils": "0.59.0",
"zod": "^3.23.8"
},
"peerDependencies": {
"typescript": ">=4.0.0"
Expand Down
20 changes: 20 additions & 0 deletions packages/plugin-typescript/src/lib/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { z } from 'zod';
import { AUDITS, DEFAULT_TS_CONFIG } from './constants.js';

const auditSlugs = AUDITS.map(({ slug }) => slug) as [string, ...string[]];
export const typescriptPluginConfigSchema = z.object({
tsconfig: z
.string({
description: 'Path to the TsConfig',
})
.default(DEFAULT_TS_CONFIG),
onlyAudits: z
.array(z.enum(auditSlugs), {
description: 'Array with specific TsCodes to measure',
})
.optional(),
});

export type TypescriptPluginOptions = z.infer<
typeof typescriptPluginConfigSchema
>;
Comment thread
BioPhoton marked this conversation as resolved.
69 changes: 69 additions & 0 deletions packages/plugin-typescript/src/lib/schema.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { describe, expect, it } from 'vitest';
import {
type TypescriptPluginOptions,
typescriptPluginConfigSchema,
} from './schema.js';

describe('typescriptPluginConfigSchema', () => {
const tsConfigPath = 'tsconfig.json';

it('accepts a empty configuration', () => {
expect(() => typescriptPluginConfigSchema.parse({})).not.toThrow();
});

it('accepts a configuration with tsConfigPath set', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
} satisfies TypescriptPluginOptions),
).not.toThrow();
});

it('accepts a configuration with tsConfigPath and empty onlyAudits', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: [],
} satisfies TypescriptPluginOptions),
).not.toThrow();
});
Comment thread
BioPhoton marked this conversation as resolved.

it('accepts a configuration with tsConfigPath and full onlyAudits', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: [
'syntax-errors',
'semantic-errors',
'configuration-errors',
],
} satisfies TypescriptPluginOptions),
).not.toThrow();
});

it('throws for invalid onlyAudits', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
onlyAudits: 123,
}),
).toThrow('invalid_type');
});

it('throws for invalid onlyAudits items', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: [123, true],
}),
).toThrow('invalid_type');
});

it('throws for unknown audit slug', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: ['unknown-audit'],
}),
).toThrow(/unknown-audit/);
});
});
58 changes: 58 additions & 0 deletions packages/plugin-typescript/src/lib/typescript-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createRequire } from 'node:module';
import type { PluginConfig } from '@code-pushup/models';
import { stringifyError } from '@code-pushup/utils';
import { DEFAULT_TS_CONFIG, TYPESCRIPT_PLUGIN_SLUG } from './constants.js';
import { createRunnerFunction } from './runner/runner.js';
import type { DiagnosticsOptions } from './runner/ts-runner.js';
import { typescriptPluginConfigSchema } from './schema.js';
import type { AuditSlug } from './types.js';
import { getAudits, getGroups, logSkippedAudits } from './utils.js';

const packageJson = createRequire(import.meta.url)(
'../../package.json',
) as typeof import('../../package.json');

export type FilterOptions = { onlyAudits?: AuditSlug[] };
export type TypescriptPluginOptions = Partial<DiagnosticsOptions> &
FilterOptions;
Comment thread
BioPhoton marked this conversation as resolved.
Outdated

export async function typescriptPlugin(
options?: TypescriptPluginOptions,
): Promise<PluginConfig> {
const { tsconfig = DEFAULT_TS_CONFIG, onlyAudits } = parseOptions(
options ?? {},
);

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

logSkippedAudits(filteredAudits);

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

function parseOptions(
tsPluginOptions: TypescriptPluginOptions,
): TypescriptPluginOptions {
try {
return typescriptPluginConfigSchema.parse(tsPluginOptions) as FilterOptions;
} catch (error) {
throw new Error(
`Error parsing TypeScript Plugin options: ${stringifyError(error)}`,
);
}
}
Comment thread
BioPhoton marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { expect } from 'vitest';
import { pluginConfigSchema } from '@code-pushup/models';
import { AUDITS, GROUPS } from './constants.js';
import type { TypescriptPluginOptions } from './types.js';
import { typescriptPlugin } from './typescript-plugin.js';

describe('typescriptPlugin-config-object', () => {
it('should create valid plugin config without options', async () => {
const pluginConfig = await typescriptPlugin();

expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();

const { audits, groups } = pluginConfig;
expect(audits).toHaveLength(AUDITS.length);
expect(groups).toBeDefined();
expect(groups!).toHaveLength(GROUPS.length);
});

it('should create valid plugin config', async () => {
const pluginConfig = await typescriptPlugin({
tsconfig: 'mocked-away/tsconfig.json',
onlyAudits: ['syntax-errors', 'semantic-errors', 'configuration-errors'],
});

expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();

const { audits, groups } = pluginConfig;
expect(audits).toHaveLength(3);
expect(groups).toBeDefined();
expect(groups!).toHaveLength(2);
});

it('should throw for invalid valid params', async () => {
await expect(() =>
typescriptPlugin({
tsconfig: 42,
} as unknown as TypescriptPluginOptions),
).rejects.toThrow(/invalid_type/);
});
});
5 changes: 5 additions & 0 deletions packages/plugin-typescript/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { kebabCaseToCamelCase } from '@code-pushup/utils';
import { AUDITS, GROUPS, TYPESCRIPT_PLUGIN_SLUG } from './constants.js';
import type { FilterOptions, TypescriptPluginOptions } from './types.js';

/**
* It filters the audits by the slugs
*
* @param slugs
*/
export function filterAuditsBySlug(slugs?: string[]) {
return ({ slug }: { slug: string }) => {
if (slugs && slugs.length > 0) {
Expand Down