Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript Plugin #901

Open
8 of 9 tasks
BioPhoton opened this issue Dec 23, 2024 · 0 comments Β· May be fixed by #902
Open
8 of 9 tasks

TypeScript Plugin #901

BioPhoton opened this issue Dec 23, 2024 · 0 comments Β· May be fixed by #902
Assignees
Labels
plugin-idea 🧩 ts-plugin TS config flags plugin
Milestone

Comments

@BioPhoton
Copy link
Collaborator

BioPhoton commented Dec 23, 2024

Typescript Plugin

Incremental Migration for TypeScript
Seamlessly migrate your codebase to leverage the latest TypeScript compiler features while maintaining clarity, control, and CI stability.


πŸ§ͺ Reference PR

πŸ‘‰ #902 – TypeScript Plugin PoC Implementation


Metric

TypeScript code quality based on compiler diagnostics.
Derived from diagnostic objects of category error and warning.

Property Value Description
value 48 Total number of detected issues.
displayValue 48 errors Human-readable representation of the value.
score 0 Indicates whether the audit passed (1) or failed (0).

User story

As a developer, I want to incrementally migrate my codebase to the latest TypeScript features while avoiding large-scale refactoring and CI failures.

The plugin should:

  • Analyze relevant Diagnostic data.
  • Provide actionable feedback with precise line references.
  • Help track regressions during migration.

Integration Requirements

Setup and Requirements

πŸ“¦ Package Dependencies

  • Dependencies:
    • typescript – – Required for diagnostics, already present in the project.
  • Dev Dependencies: N/A
  • Optional Dependencies: N/A

πŸ“ Configuration Files

  • tsconfig.json – Standard configuration file.
  • tsconfig.next.json – Migration-specific configuration.

Audit, groups and category maintenance

  • πŸ“‹ Audit: The audit slugs depending on the implementation could be a small set that is easy to maintain, or a TS-Code to slug manually maintained list.
  • πŸ—‚ Group: The groups are similar to audits easy depending approach. A convention aligning with compiler options might be hard. Some of the group slugs overlap with compiler options like strict which is a short hand option to enable all strict type checks. All those cases need to be maintained manually.
  • πŸ—ƒ Category: The categories could be the same as for EsLint plus one for the configuration linting.

Details maintenance

  • 🐞 Issues: The details contain only issues, but those are a bit cumbersome to set up initially. The TypeScript's compiler diagnostics return diagnostic codes, and a subset of those codes are relevant for this plugin. The irrelevant ones are easy to filter out. The relevant ones need to manually be mapped to an audit in the maintained map.
  • πŸ“ Table: N/A

Runner maintenance

The runner is simple to maintain, consisting of only a few fully typed lines.

Acceptance criteria

  • The target tsconfig.json serves as the primary source for report creation and determines the applied audits.
    • Parsed using helpers from the typescript package.
    • Default configuration is loaded based on the current typescript version.
    • Audits are derived from programmatically derived or a small set of static data.
    • Audits are filtered by onlyAudits if specified.
  • Issues are grouped by an audit slug.
    • docsUrl points to the official documentation page.
    • Unsupported diagnostic codes are logged.
  • Audits are organized into logically linked groups.

Implementation details

πŸ“Œ Key Note: There’s no way to automatically link Diagnostic results to compiler options.

  1. There is no known way to link the Diagnostic results to compiler options or audits.
  2. Diagnostic results depend on both the active tsconfig.json configuration and TypeScript internal defaults.
  3. TypeScript defaults are not exported. They are derived over internal logic that depends on the current package version.
  4. Ambiguity exists between disabled and missing compiler options.
  5. Ambiguity exists between configures and internally derived compiler options.

A draft implementation of the plugin can be found here: #902.

Setup

The plugin needs the following options:

type PluginOptions = {   tsConfigPath?: string,   onlyAudits?: AuditSlug[]} | undefined;
const pluginOptions:   PluginOptions = {
   tsConfigPath: `tsconfig.next.json`, // default is `tsconfig.json`
   onlyAudits: [ 'no-implicite-any' ]
};

Generating TypeScript Diagnostics

The typescript package exports a method to create a program. The required parameters are derived from a given tscinfig.json file:

import { type Program, createProgram} from 'typescript';
const program:Program =  createProgram(program);

The typescript package exports a method to get the Diagnostic results of a given program:

import {  type Diagnostic,  getPreEmitDiagnostics} from 'typescript';
const diagnostics:Diagnostic[] =  getPreEmitDiagnostics(program);

All objects are identified by a unique code:

import {  type Diagnostic } from 'typescript';
const diagnostic:Diagnostic = {
  code: 2453,
  message: '...',
  category: 'error'
};

TypeScript diagnostic codes are grouped into ranges based on their source and purpose. Unfortunately this is not documented anywhere.

Here's how they are categorized:

Code Range Type Description
1XXX Syntax Errors Structural issues detected during parsing.
2XXX Semantic Errors Type-checking and type-system violations.
3XXX Suggestions Optional improvements (e.g., unused variables).
4XXX Language Service Diagnostics Used by editors (e.g., VSCode) for IntelliSense.
5XXX Internal Compiler Errors Rare, unexpected failures in the compiler.
6XXX Configuration/Options Errors Issues with tsconfig.json or compiler options.

The diagnostic messages are exposed over a undocumented and undiscoverable const names Diagnostics.

import { Diagnostics } from "typescript";
  /**
   * const Diagnostics = {
   *   Convert_to_default_import: {
   *     code: 95013,
   *     category: 3,
   *     key: 'Convert_to_default_import_95013',
   *     message: 'Convert to default import',
   *     reportsUnnecessary: undefined,
   *     elidedInCompatabilityPyramid: undefined,
   *     reportsDeprecated: undefined
   *   },
   * };
   * const [key, diagData] = codeToData(95013) // Convert_to_default_import_95013 
   */
export function codeToData(searchCode: number) {
  return Object.entries(Diagnostics as Record<string, { code: number }>).find(([_, {code}]) => code === searchCode);
}

This objects need to get linked to audits. Due to key note [1] we would have to maintain a manual map to link diagnostic errors to audits and groups. So far the only maintainable way to group diagnostics is to rely on the undocumented ranges.

export const TS_CODE_RANGE_NAMES = {
  '1': 'syntax-errors',
  '2': 'semantic-errors',
  '4': 'language-service-errors',
  '5': 'internal-errors',
  '6': 'configuration-errors',
  '9': 'unknown-codes',
} as const;

TypeScript Compiler Options

NOTE: This section is here to reflect learnings from the research

The existing compiler options configured in tsconfig.next.json are different from the ones used in the program. This internal logic is not accessible and parts of that logic has to be duplicated in the plugin source, see key notes [2].

Case 1: Internally applied defaults options - key notes [5]

tsconfig.next.json

{
  "compilerOptions": {
     "verbatimModuleSyntax": true
  }
}

calculated compiler options inside the TS program

{
  "compilerOptions": {
     "verbatimModuleSyntax": true
     "esModuleInterop": true
  }
}
Solution 1

The npx tsc --init command creates a tsconfig.json file with all defaults listed as comments.
A suggested implementation could be npx -y tsc -p=typescript@{version} --init.

generated tsconfig.json file

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */


    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
  }
}

This file gets parsed and stores as tsconfig.<version>.json under node_modules/.code-pushup/typescript-plugin/ts-defaults.

tsconfig.5.5.4.json

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,
  }
}

This data can get generated and loaded at runtime of the plugin and merge with the config retrieved form tsconfig.next.json.

Solution 2 (favoured)

The generate login from above can install the current TypeScript version on the postinstall hook fired on every npm install.
At runtime of the plugin can load this data and merge this defaults into the config retrieved form the given tsconfig.next.json.

Case 2: : Internally expanding defaults/short-hand options - key notes [5]

tsconfig.next.json

{
  "compilerOptions": {
     "strict": true
  }
}

calculated compiler options inside the TS program

{
  "compilerOptions": {
      "strict": true,
      "noImplicitAny": true,
      "strictNullChecks": true,
      "strictFunctionTypes" true,
      "strictBindCallApply": true,
      "strictPropertyInitialization": true,
      "noImplicitThis": true,
      "useUnknownInCatchVariables": true,
      "alwaysStrict": true,
  }
}

Is TypeScript strict acts as a short-hand and expands internally into multiple other options.

Solution 1

We can re-implement the internal logic to handle cases like strict etc in the plugin source.

Resources

@BioPhoton BioPhoton added 🧩 ts-plugin TS config flags plugin plugin-idea labels Dec 25, 2024
@BioPhoton BioPhoton added this to the 4. Public milestone Jan 2, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
plugin-idea 🧩 ts-plugin TS config flags plugin
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant