diff --git a/code-pushup.config.ts b/code-pushup.config.ts index 9af68c6fb..07984ec3e 100644 --- a/code-pushup.config.ts +++ b/code-pushup.config.ts @@ -6,6 +6,7 @@ import { jsDocsCoreConfig, jsPackagesCoreConfig, lighthouseCoreConfig, + typescriptPluginConfigNx, } from './code-pushup.preset.js'; import type { CoreConfig } from './packages/models/src/index.js'; import { mergeConfigs } from './packages/utils/src/index.js'; @@ -39,6 +40,9 @@ export default mergeConfigs( await lighthouseCoreConfig( 'https://github.com/code-pushup/cli?tab=readme-ov-file#code-pushup-cli/', ), + await typescriptPluginConfigNx({ + tsconfig: 'packages/cli/tsconfig.lib.json', + }), await eslintCoreConfigNx(), jsDocsCoreConfig([ 'packages/**/src/**/*.ts', diff --git a/code-pushup.preset.ts b/code-pushup.preset.ts index 9b9c462f9..ea8b692ce 100644 --- a/code-pushup.preset.ts +++ b/code-pushup.preset.ts @@ -21,6 +21,11 @@ import { filterGroupsByOnlyAudits } from './packages/plugin-jsdocs/src/lib/utils import lighthousePlugin, { lighthouseGroupRef, } from './packages/plugin-lighthouse/src/index.js'; +import { + type TypescriptPluginOptions, + getCategories, + typescriptPlugin, +} from './packages/plugin-typescript/src/index.js'; export const jsPackagesCategories: CategoryConfig[] = [ { @@ -168,6 +173,13 @@ export const eslintCoreConfigNx = async ( }; }; +export const typescriptPluginConfigNx = async ( + options?: TypescriptPluginOptions, +): Promise => ({ + plugins: [await typescriptPlugin(options)], + categories: getCategories(), +}); + export const coverageCoreConfigNx = async ( projectName?: string, ): Promise => { diff --git a/packages/plugin-typescript/README.md b/packages/plugin-typescript/README.md new file mode 100644 index 000000000..9ae5b9697 --- /dev/null +++ b/packages/plugin-typescript/README.md @@ -0,0 +1,164 @@ +# @code-pushup/typescript-plugin + +[![npm](https://img.shields.io/npm/v/%40code-pushup%2Ftypescript-plugin.svg)](https://www.npmjs.com/package/@code-pushup/typescript-plugin) +[![downloads](https://img.shields.io/npm/dm/%40code-pushup%2Ftypescript-plugin)](https://npmtrends.com/@code-pushup/typescript-plugin) +[![dependencies](https://img.shields.io/librariesio/release/npm/%40code-pushup/typescript-plugin)](https://www.npmjs.com/package/@code-pushup/typescript-plugin?activeTab=dependencies) + +🕵️ **Code PushUp plugin for measuring TypeScript quality with compiler diagnostics.** 🔥 + +This plugin allows you to **incrementally adopt strict compilation flags in TypeScript projects**. +It analyzes your codebase using the TypeScript compiler to detect potential issues and configuration problems. + +TypeScript compiler diagnostics are mapped to Code PushUp audits in the following way: + +- `value`: The number of issues found for a specific TypeScript configuration option (e.g. 3) +- `displayValue`: The number of issues found (e.g. "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 + +1. If you haven't already, install [@code-pushup/cli](../cli/README.md) and create a configuration file. + +2. Install as a dev dependency with your package manager: + + ```sh + npm install --save-dev @code-pushup/typescript-plugin + ``` + + ```sh + yarn add --dev @code-pushup/typescript-plugin + ``` + + ```sh + pnpm add --save-dev @code-pushup/typescript-plugin + ``` + +3. Add this plugin to the `plugins` array in your Code PushUp CLI config file (e.g. `code-pushup.config.ts`). + +By default, a root `tsconfig.json` is used to compile your codebase. Based on those compiler options, the plugin will generate audits. + +```ts +import typescriptPlugin from '@code-pushup/typescript-plugin'; + +export default { + // ... + plugins: [ + // ... + await typescriptPlugin(), + ], +}; +``` + +4. Run the CLI with `npx code-pushup collect` and view or upload the report (refer to [CLI docs](../cli/README.md)). + +## About TypeScript checks + +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. + +The plugin provides multiple audits grouped into different sets: + +- _Semantic Errors_: `semantic-errors` - Errors that occur during type checking and type inference +- _Syntax Errors_: `syntax-errors` - Errors that occur during parsing and lexing of TypeScript source code +- _Configuration Errors_: `configuration-errors` - Errors that occur when parsing TypeScript configuration files +- _Declaration and Language Service Errors_: `declaration-and-language-service-errors` - Errors that occur during TypeScript language service operations +- _Internal Errors_: `internal-errors` - Errors that occur during TypeScript internal operations +- _No Implicit Any Errors_: `no-implicit-any-errors` - Errors related to `noImplicitAny` compiler option +- _Unknown Codes_: `unknown-codes` - Errors that do not match any known TypeScript error code + +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 + +Each set is also available as group in the plugin. See more under [Audits and Groups](./docs/audits-and-groups.md). + +## Plugin architecture + +### Plugin configuration specification + +The plugin accepts the following parameters: + +| Option | Type | Default | Description | +| ---------- | -------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | +| tsconfig | string | `tsconfig.json` | A string that defines the path to your `tsconfig.json` file | +| onlyAudits | string[] | undefined | An array of audit slugs to specify which documentation types you want to measure. Only the specified audits will be included in the results | + +#### `tsconfig` + +Optional parameter. The `tsconfig` option accepts a string that defines the path to your config file and defaults to `tsconfig.json`. + +```js +await typescriptPlugin({ + tsconfig: './tsconfig.json', +}); +``` + +#### `onlyAudits` + +The `onlyAudits` option allows you to specify which documentation types you want to measure. Only the specified audits will be included in the results. All audits are included by default. Example: + +```js +await typescriptPlugin({ + onlyAudits: ['no-implicit-any'], +}); +``` + +### Optionally set up categories + +Reference audits (or groups) which you wish to include in custom categories (use `npx code-pushup print-config` to list audits and groups). + +Assign weights based on what influence each TypeScript checks should have on the overall category score (assign weight 0 to only include as extra info, without influencing category score). + +```ts +// ... +categories: [ + { + slug: 'typescript', + title: 'TypeScript', + refs: [ + { + type: 'audit', + plugin: 'typescript', + slug: 'semantic-errors', + weight: 2, + }, + { + type: 'audit', + plugin: 'typescript', + slug: 'syntax-errors', + weight: 1, + }, + // ... + ], + }, + // ... +]; +``` + +Also groups can be used: + +```ts +// ... +categories: [ + { + slug: 'typescript', + title: 'TypeScript', + refs: [ + { + slug: 'language-and-environment', + weight: 1, + type: 'group', + plugin: 'typescript', + }, + // ... + ], + }, + // ... +]; +``` diff --git a/packages/plugin-typescript/docs/audits-and-groups.md b/packages/plugin-typescript/docs/audits-and-groups.md new file mode 100644 index 000000000..5cb528c22 --- /dev/null +++ b/packages/plugin-typescript/docs/audits-and-groups.md @@ -0,0 +1,186 @@ +# 📚 Audits and Groups + +The TypeScript plugin analyzes your codebase using the TypeScript compiler to identify potential issues and enforce best practices. + +--- + +## 🛡️ Audits Overview + +The plugin manages issues through structured audits and groups. Each audit targets a specific type of error, ensuring comprehensive analysis. + +| **Audit** | **Slug** | +| ------------------------------------------- | ----------------------------------------- | +| **Semantic Errors** | `semantic-errors` | +| **Syntax Errors** | `syntax-errors` | +| **Configuration Errors** | `configuration-errors` | +| **Declaration and Language Service Errors** | `declaration-and-language-service-errors` | +| **Internal Errors** | `internal-errors` | +| **No Implicit Any Errors** | `no-implicit-any-errors` | +| **Unknown Codes** | `unknown-codes` | + +--- + +### Semantic Errors - `semantic-errors` + +Errors that occur during type checking and type inference. + +- **Slug:** `semantic-errors` +- **Title:** Semantic Errors +- **Description:** Errors that occur during type checking and type inference. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :----------------------------------------------------------------------------------- | :------------------------------------------------------------------------ | :-----: | +| 🚨 _error_ | TS2307: Cannot find module './non-existent' or its corresponding type declarations. | [`path/to/module-resolution.ts`](../path/to/module-resolution.ts) | 2 | +| 🚨 _error_ | TS2349: This expression is not callable.
Type 'Number' has no call signatures. | [`path/to/strict-function-types.ts`](../path/to/strict-function-types.ts) | 3 | +| 🚨 _error_ | TS2304: Cannot find name 'NonExistentType'. | [`path/to/cannot-find-module.ts`](../path/to/cannot-find-module.ts) | 1 | + +--- + +### Syntax Errors - `syntax-errors` + +Errors that occur during parsing and lexing of TypeScript source code. + +- **Slug:** `syntax-errors` +- **Title:** Syntax Errors +- **Description:** Errors that occur during parsing and lexing of TypeScript source code. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :------------------------------------ | :------------------------------------------------------ | :-----: | +| 🚨 _error_ | TS1136: Property assignment expected. | [`path/to/syntax-error.ts`](../path/to/syntax-error.ts) | 1 | + +--- + +### Configuration Errors - `configuration-errors` + +Errors that occur when parsing TypeScript configuration files. + +- **Slug:** `configuration-errors` +- **Title:** Configuration Errors +- **Description:** Errors that occur when parsing TypeScript configuration files. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :----------------------------------------- | :-------------------------------------------------- | :-----: | +| 🚨 _error_ | TS5023: Unknown compiler option 'invalid'. | [`path/to/tsconfig.json`](../path/to/tsconfig.json) | 1 | + +--- + +### Declaration and Language Service Errors - `declaration-and-language-service-errors` + +Errors that occur during TypeScript language service operations. + +- **Slug:** `declaration-and-language-service-errors` +- **Title:** Declaration and Language Service Errors +- **Description:** Errors that occur during TypeScript language service operations. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :------------------------------------------------------------------------------------------------------------------------------ | :------------------------------------------------------------------ | :-----: | +| 🚨 _error_ | TS4112: This member cannot have an 'override' modifier because its containing class 'Standalone' does not extend another class. | [`path/to/incorrect-modifier.ts`](../path/to/incorrect-modifier.ts) | 2 | + +--- + +### Internal Errors - `internal-errors` + +Errors that occur during TypeScript internal operations. + +- **Slug:** `internal-errors` +- **Title:** Internal Errors +- **Description:** Errors that occur during TypeScript internal operations. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :------------------------------- | :---------------------------------------------------------- | :-----: | +| 🚨 _error_ | TS9001: Internal compiler error. | [`path/to/internal-error.ts`](../path/to/internal-error.ts) | 4 | + +--- + +### No Implicit Any Errors - `no-implicit-any-errors` + +Errors related to no implicit any compiler option. + +- **Slug:** `no-implicit-any-errors` +- **Title:** No Implicit Any Errors +- **Description:** Errors related to no implicit any compiler option. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :-------------------------------------------------- | :------------------------------------------------------ | :-----: | +| 🚨 _error_ | TS7006: Parameter 'x' implicitly has an 'any' type. | [`path/to/implicit-any.ts`](../path/to/implicit-any.ts) | 5 | + +--- + +### Unknown Codes - `unknown-codes` + +Errors that do not match any known TypeScript error code. + +- **Slug:** `unknown-codes` +- **Title:** Unknown Codes +- **Description:** Errors that do not match any known TypeScript error code. +- **Scoring:** The score is based on the number of issues found. +- **Value:** The value is the number of issues found. +- **Display Value:** The display value is the number of issues found. + +#### Issues + +| Severity | Message | Source file | Line(s) | +| :--------: | :-------------------------------------- | :-------------------------------------------------------- | :-----: | +| 🚨 _error_ | TS9999: Unknown error code encountered. | [`path/to/unknown-error.ts`](../path/to/unknown-error.ts) | 6 | + +--- + +## 📂 Groups Overview + +| **Group** | **Slug** | +| ----------------- | ------------------ | +| **Problems** | `problems` | +| **Configuration** | `ts-configuration` | +| **Miscellaneous** | `miscellaneous` | + +### Problems - `problems` + +- **Description:** Syntax, semantic, and internal compiler errors are critical for identifying and preventing bugs. +- **References:** + - Syntax Errors (`syntax-errors`) + - Semantic Errors (`semantic-errors`) + - No Implicit Any Errors (`no-implicit-any-errors`) + +### Configuration - `ts-configuration` + +- **Description:** Ensures correct TypeScript project setup, minimizing risks from misconfiguration. +- **References:** + - Configuration Errors (`configuration-errors`) + +### Miscellaneous - `miscellaneous` + +- **Description:** Informational errors that may not impact development directly but are helpful for deeper insights. +- **References:** + - Unknown Codes (`unknown-codes`) + - Internal Errors (`internal-errors`) + - Declaration and Language Service Errors (`declaration-and-language-service-errors`) diff --git a/packages/plugin-typescript/src/index.ts b/packages/plugin-typescript/src/index.ts index 26c2b67c0..de384e130 100644 --- a/packages/plugin-typescript/src/index.ts +++ b/packages/plugin-typescript/src/index.ts @@ -1 +1,8 @@ export { TYPESCRIPT_PLUGIN_SLUG } from './lib/constants.js'; +export { typescriptPlugin } from './lib/typescript-plugin.js'; +export { getCategories } from './lib/utils.js'; +export { + type TypescriptPluginConfig, + type TypescriptPluginOptions, + typescriptPluginConfigSchema, +} from './lib/schema.js'; diff --git a/packages/plugin-typescript/src/lib/runner/__snapshots__/runner-function-all-audits.json b/packages/plugin-typescript/src/lib/runner/__snapshots__/runner-function-all-audits.json index d5deaabe4..919122510 100644 --- a/packages/plugin-typescript/src/lib/runner/__snapshots__/runner-function-all-audits.json +++ b/packages/plugin-typescript/src/lib/runner/__snapshots__/runner-function-all-audits.json @@ -14,6 +14,7 @@ }, ], }, + "displayValue": "1 issue", "score": 0, "slug": "syntax-errors", "value": 1, @@ -74,6 +75,7 @@ }, ], }, + "displayValue": "5 issues", "score": 0, "slug": "semantic-errors", "value": 5, @@ -93,26 +95,31 @@ }, ], }, + "displayValue": "1 issue", "score": 0, "slug": "declaration-and-language-service-errors", "value": 1, }, { + "displayValue": "0 issues", "score": 1, "slug": "internal-errors", "value": 0, }, { + "displayValue": "0 issues", "score": 1, "slug": "configuration-errors", "value": 0, }, { + "displayValue": "0 issues", "score": 1, "slug": "no-implicit-any-errors", "value": 0, }, { + "displayValue": "0 issues", "score": 1, "slug": "unknown-codes", "value": 0, diff --git a/packages/plugin-typescript/src/lib/runner/runner.ts b/packages/plugin-typescript/src/lib/runner/runner.ts index 0c12a393d..30c560895 100644 --- a/packages/plugin-typescript/src/lib/runner/runner.ts +++ b/packages/plugin-typescript/src/lib/runner/runner.ts @@ -4,6 +4,7 @@ import type { Issue, RunnerFunction, } from '@code-pushup/models'; +import { pluralize } from '@code-pushup/utils'; import type { AuditSlug } from '../types.js'; import { type DiagnosticsOptions, @@ -44,6 +45,7 @@ export function createRunnerFunction(options: RunnerOptions): RunnerFunction { slug, score: issues.length === 0 ? 1 : 0, value: issues.length, + displayValue: `${issues.length} ${pluralize('issue', issues.length)}`, ...(issues.length > 0 ? { details } : {}), } satisfies AuditOutput; }); diff --git a/packages/plugin-typescript/src/lib/runner/runner.unit.test.ts b/packages/plugin-typescript/src/lib/runner/runner.unit.test.ts index 804260e38..063ac9583 100644 --- a/packages/plugin-typescript/src/lib/runner/runner.unit.test.ts +++ b/packages/plugin-typescript/src/lib/runner/runner.unit.test.ts @@ -110,6 +110,7 @@ describe('createRunnerFunction', () => { slug: 'semantic-errors', score: 0, value: 2, + displayValue: '2 issues', details: { issues: [ expect.objectContaining({