Skip to content

Commit

Permalink
feat(plugin-typescript): new tests, docs, readme, remove unused async…
Browse files Browse the repository at this point in the history
…, etc (#904)
  • Loading branch information
aramirezj authored Dec 25, 2024
1 parent ce4daa6 commit 68f56c8
Show file tree
Hide file tree
Showing 16 changed files with 494 additions and 106 deletions.
2 changes: 1 addition & 1 deletion code-pushup.preset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ export const typescriptPluginConfigNx = async (
};

return {
plugins: [await typescriptPlugin(opt)],
plugins: [typescriptPlugin(opt)],
categories: [
{
slug: 'typescript',
Expand Down
168 changes: 141 additions & 27 deletions packages/plugin-typescript/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@

🕵️ **Code PushUp plugin for measuring TypeScript quality with compiler diagnostics.** 🔥

---
This plugin allows you to measure and track TypeScript compiler diagnostics in your TypeScript/JavaScript project.
It analyzes your codebase using the TypeScript compiler to detect potential issues and configuration problems.

The plugin parses your TypeScript and JavaScript code and lints all audits of the official [TypeScript Compiler]().
TypeScript compiler diagnostics are mapped to Code PushUp audits in the following way:

For more infos visit the [official docs](https://developer.chrome.com/docs/typescript/overview).
- `value`: The number of issues found for a specific TypeScript configuration option -> 3
- `displayValue`: The number of issues found -> 3 issues
- `score`: Binary scoring - 1 if no issues are found, 0 if any issues exist
- Issues are mapped to audit details, containing:
- Source file location
- Error message from TypeScript compiler
- Code reference where the issue was found

## Getting started

Expand Down Expand Up @@ -50,41 +57,148 @@ For more infos visit the [official docs](https://developer.chrome.com/docs/types

4. Run the CLI with `npx code-pushup collect` and view or upload the report (refer to [CLI docs](../cli/README.md)).

### Optionally set up categories
## About documentation coverage

Reference audits (or groups) which you wish to include in custom categories (use `npx code-pushup print-config --onlyPlugins=typescript` to list audits and groups).
The TypeScript plugin analyzes your codebase using the TypeScript compiler to identify potential issues and enforce best practices. It helps ensure type safety and maintainability of your TypeScript code.

Assign weights based on what influence each Lighthouse audit has on the overall category score (assign weight 0 to only include as extra info, without influencing category score).
The plugin exports the helper `typescriptAuditRef` and `typescriptGroupRef` to reference Lighthouse category references for audits and groups.
The plugin provides multiple audits grouped into different categories like:

#### Reference audits directly with `typescriptGroupRef`
- Language and Environment - Checks configuration for TypeScript features like decorators, JSX, target version
- Type Checking - Validates strict null checks, implicit any/this, function types
- Module Resolution - Verifies module imports/exports and resolution settings
- Build/Emit Options - Checks output generation and optimization settings
- Control Flow - Analyzes code flow, unreachable code, switch statements

```ts
import { typescriptGroupRef } from './utils';
Each audit:

- Checks for specific TypeScript compiler errors and warnings
- Provides a score based on the number of issues found
- Includes detailed error messages and locations

The audits are organized into logical groups to give you a comprehensive view of your TypeScript configuration and code quality. You can:

- Use all groups for complete TypeScript analysis
- Focus on specific groups or individual audits based on your needs

## Plugin architecture

### Plugin configuration specification

The plugin accepts the following parameters:

export default {
// ...
categories: [],
};
#### TsConfigPath

Required parameter. The `tsConfigPath` option accepts a string that defines the path to your `tsconfig.json` file.

```js
typescriptPlugin({
tsConfigPath: './tsconfig.json',
}),
```

#### Reference groups with `typescriptAuditRef`
#### OnlyAudits

Optional parameter. The `onlyAudits` option allows you to specify which documentation types you want to measure. Only the specified audits will be included in the results. Example:

```js
typescriptPlugin({
tsConfigPath: './tsconfig.json',
onlyAudits: [
'no-implicit-any'
] // Only measure documentation for classes and functions
}),
```

### Audits and group

This plugin provides a list of groups to cover different TypeScript configuration options and their areas of responsibility.

The TypeScript categories are reflected as groups.
Referencing individual audits offers more granularity. However, keep maintenance costs of a higher number of audits in mind as well.
```ts
// ...
categories: [
{
slug: 'typescript',
title: 'TypeScript',
refs: [
{
slug: 'language-and-environment',
weight: 1,
type: 'group',
plugin: 'typescript'
},
// ...
],
},
// ...
],
```

Each TypeScript configuration option still has its own audit. So when you want to include a subset of configuration options or assign different weights to them, you can do so in the following way:

```ts
import { typescriptAuditRef } from './utils';
// ...
categories: [
{
slug: 'typescript',
title: 'TypeScript',
refs: [
{
type: 'audit',
plugin: 'typescript',
slug: 'no-implicit-any',
weight: 2,
},
{
type: 'audit',
plugin: 'typescript',
slug: 'no-explicit-any',
weight: 1,
},
// ...
],
},
// ...
],
```

### Audit output

export default {
// ...
categories: [
The plugin outputs a single audit that measures the overall documentation coverage percentage of your codebase.

For instance, this is an example of the plugin output:

```json
{
"packageName": "@code-pushup/typescript-plugin",
"version": "0.57.0",
"title": "Typescript",
"slug": "typescript",
"icon": "typescript",
"date": "2024-12-25T11:10:22.646Z",
"duration": 2059,
"audits": [
{
slug: 'pwa',
title: 'PWA',
isBinary: true,
refs: [typescriptAuditRef('installable-manifest', 2), typescriptAuditRef('splash-screen', 1), typescriptAuditRef('themed-omnibox', 1), typescriptAuditRef('content-width', 1), typescriptAuditRef('themed-omnibox', 2), typescriptAuditRef('viewport', 2), typescriptAuditRef('maskable-icon', 1), typescriptAuditRef('pwa-cross-browser', 0), typescriptAuditRef('pwa-page-transitions', 0), typescriptAuditRef('pwa-each-page-has-url', 0)],
},
"slug": "experimental-decorators",
"value": 0,
"score": 1,
"title": "ExperimentalDecorators",
"docsUrl": "https://www.typescriptlang.org/tsconfig/#experimentalDecorators"
}
],
};
"description": "Official Code PushUp typescript plugin.",
"docsUrl": "https://www.npmjs.com/package/@code-pushup/typescript-plugin/",
"groups": [
{
"slug": "language-and-environment",
"refs": [
{
"slug": "experimental-decorators",
"weight": 1
}
],
"title": "LanguageAndEnvironment",
"description": "Configuration options for TypeScript language features and runtime environment"
}
]
}
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"compilerOptions": {
"rootDir": "./src",
"strict": true,
"target": "ES6",
"module": "CommonJS",
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"alwaysStrict": true
},
"exclude": ["src/**/*.ts", "src/**/*.js"]
}
16 changes: 14 additions & 2 deletions packages/plugin-typescript/src/lib/config.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('TypescriptPlugin Configuration', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: ['ts-code-1065', 'ts-code-2354'],
onlyAudits: ['no-implicit-any', 'module-resolution-node'],
} satisfies TypescriptPluginOptions),
).not.toThrow();
});
Expand Down Expand Up @@ -41,7 +41,7 @@ describe('TypescriptPlugin Configuration', () => {
expect(() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: ['ts-code-1065', 'argument-expected-1011'],
onlyAudits: ['no-implicit-any', 'module-resolution-node'],
} satisfies TypescriptPluginOptions),
).not.toThrow();
});
Expand Down Expand Up @@ -71,5 +71,17 @@ describe('TypescriptPlugin Configuration', () => {
}),
).toThrow('invalid_type');
});

it('throws for unknown audit slug', () => {
expect(
() =>
typescriptPluginConfigSchema.parse({
tsConfigPath,
onlyAudits: ['unknown-audit'],
}),
// Message too large because enums validation
// eslint-disable-next-line vitest/require-to-throw-message
).toThrow();
});
});
});
13 changes: 9 additions & 4 deletions packages/plugin-typescript/src/lib/constants.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import type { Audit, Group } from '@code-pushup/models';

Check failure on line 1 in packages/plugin-typescript/src/lib/constants.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.
import { TS_ERROR_CODES } from './runner/ts-error-codes.js';
import { camelCaseToKebabCase, formatTitle } from './utils.js';
import { camelCaseToKebabCase, formatSlugToTitle } from '@code-pushup/utils';
import {
GROUPS_DESCRIPTIONS,
TS_ERROR_CODES,
} from './runner/ts-error-codes.js';

export const TYPESCRIPT_PLUGIN_SLUG = 'typescript';

export const AUDITS = Object.values(TS_ERROR_CODES)
.flatMap(i => Object.entries(i))
.reduce<Audit[]>((audits, [name]) => {
const slug = camelCaseToKebabCase(name);
const title = formatTitle(name);
const title = formatSlugToTitle(name);
return [
...audits,
{
Expand All @@ -29,7 +32,9 @@ const weights = {
export const GROUPS: Group[] = Object.entries(TS_ERROR_CODES).map(
([groupSlug, auditMap]) => ({
slug: camelCaseToKebabCase(groupSlug),
title: formatTitle(groupSlug),
title: formatSlugToTitle(groupSlug),
description:
GROUPS_DESCRIPTIONS[groupSlug as keyof typeof GROUPS_DESCRIPTIONS],
refs: Object.keys(auditMap).map(audit => ({
slug: camelCaseToKebabCase(audit),
weight: weights[audit as keyof typeof weights] ?? 1,
Expand Down
24 changes: 24 additions & 0 deletions packages/plugin-typescript/src/lib/runner/ts-error-codes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
/* eslint-disable @typescript-eslint/no-magic-numbers, unicorn/numeric-separators-style */

Check failure on line 1 in packages/plugin-typescript/src/lib/runner/ts-error-codes.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.

export const GROUPS_DESCRIPTIONS = {
languageAndEnvironment:
'Configuration options for TypeScript language features and runtime environment, including decorators, JSX support, target ECMAScript version, and class field behaviors',
interopConstraints:
'Settings that control how TypeScript interoperates with other JavaScript code, including module imports/exports and case sensitivity rules',
watchOptions:
'Configuration for TypeScript watch mode behavior, including file watching strategies and dependency tracking',
projectReferences:
'Options for managing TypeScript project references, composite projects, and build optimization settings',
moduleResolution:
'Settings that control how TypeScript finds and resolves module imports, including Node.js resolution, package.json exports/imports, and module syntax handling',
typeCheckingBehavior:
'Configuration for TypeScript type checking strictness and error reporting, including property access rules and method override checking',
controlFlowOptions:
'Settings that affect code flow analysis, including handling of unreachable code, unused labels, switch statements, and async/generator functions',
strictChecks:
'Strict type checking options that enable additional compile-time verifications, including null checks, implicit any/this, and function type checking',
buildEmitOptions:
'Configuration options that control TypeScript output generation, including whether to emit files, how to handle comments and declarations, and settings for output optimization and compatibility helpers',
};

/** This is the list of error codes that can be triggered by the TypeScript compiler.
* It's divided into: category -> compiler option -> error codes (that might trigger)
*/
export const TS_ERROR_CODES = {
languageAndEnvironment: {
experimentalDecorators: [1240, 1241, 1242, 1243, 1244, 1270, 1271, 1272],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,23 @@ describe('getDiagnostics', () => {
expect(res).toHaveLength(4);
expect(res.at(0)?.code).toBe(2307);
});

it('should throw if missing tsconfig path', async () => {
await expect(
getDiagnostics({
tsConfigPath: 'missing-tsconfig.json',
}),
).rejects.toThrow('tsconfig not found at: missing-tsconfig.json');
});

it('should throw if no files matched by the TypeScript configuration', async () => {
await expect(
getDiagnostics({
tsConfigPath:
'packages/plugin-typescript/mocks/fixtures/basic-setup/tsconfig-with-exclude.json',
}),
).rejects.toThrow(
'No files matched by the TypeScript configuration. Check your "include", "exclude" or "files" settings.',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ export async function getDiagnostics(
try {
await access(configPath);
} catch {
throw new Error(`tsconfig not found at: ${configPath}`);
throw new Error(`tsconfig not found at: ${tsConfigPath}`);
}

const configFile = (await readFile(configPath)).toString();

const { config: strictConfig } = parseConfigFileTextToJson(
configPath,
configFile,
Expand Down
Loading

0 comments on commit 68f56c8

Please sign in to comment.