Exploring TypeScript: Ensuring Compatibility of Types

Given two sets of TypeScript type definitions in string format:

Set A:

{
    a: string
    b: number
}

Set B:

{
    a: string
}

Is there a way to programmatically determine if these two sets are compatible? In other words, can we assign variables defined by Set A to values defined by Set B (or vice versa)?

An example function for this purpose could be:

function match(source: string, target: string): boolean { /** */ }

match('number', 'any') // true
match('any', 'number') // false
match('{a: string; b: number}', '{a: string; b: number}') // true
match('{a: string; b: number}', '{a: string}') // true
match('{a: string}', '{a: string; b: number}') // false
// ...

What is the most straightforward approach to achieve this?

NOTE: The scenario involves checking compatibility between custom user-defined type interfaces at design time. While TypeScript syntax is used here to represent types, the main focus is on confirming type matching rather than the specific language being used.

Answer №1

If you wish to incorporate the compiler as an npm package, simply execute the command npm install typescript. This enables you to utilize the compiler within your codebase. The following solution entails creating a small program to assess the compatibility of two types. While performance might be a concern, testing it with real-world scenarios will affirm its viability:

import * as ts from 'typescript'

// Enhance performance by caching declarations (such as lib.d.ts)
let sourceFileCache: { [name: string]: ts.SourceFile | undefined } = {};
function match(source: string, target: string): boolean {
    let host = ts.createCompilerHost({});
    let originalGetSourceFile = host.getSourceFile;
    host.getSourceFile = (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => {
        if (fileName === "compatCheck.ts") {
            return ts.createSourceFile(fileName, `
type Source = ${source};
type Target = ${target};

let source!: Source;
let target!: Target;
target = source;
`, languageVersion);
        }

        if (sourceFileCache[fileName] === undefined) {
            return sourceFileCache[fileName] = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
        } else {
            return sourceFileCache[fileName];
        }
    }

    let program = ts.createProgram(["compatCheck.ts"], {

    }, host);
    let errors = program.getSemanticDiagnostics();
    return !errors.some(e => true);
}

console.log(match('number', 'any')); // true any can be assigned to number
console.log(match('any', 'number')); // true number can be assigned to any as well 
console.log(match('{a: string; b: number}', '{a: string; b: number}')); // true
console.log(match('{a: string; b: number}', '{a: string}')); // true
console.log(match('{a: string}', '{a: string; b: number}')); // false

Edit

An alternative for enhanced performance involves excluding the parsing of default lib.d.ts and supplying solely the essential types necessary for the compiler to function. This subset consists of Array, Boolean, Number, Function, IArguments, Object, RegExp, and String. Omitting methods in these types while including a basic definition ensures type incompatibility. Additional types may be added explicitly as required. For cases where method usage is necessary, inclusion of those specific methods is advised. However, considering the primary aim of comparing interfaces, this approach is unlikely to pose any issues:

import * as ts from "typescript";

// Cache declarations (e.g., lib.d.ts) to optimize performance
let sourceFileCache: { [name: string]: ts.SourceFile | undefined } = {};
function match(source: string, target: string): boolean {
    let host = ts.createCompilerHost({});
    let originalGetSourceFile = host.getSourceFile;
    host.directoryExists = ()=> false;
    host.fileExists = fileName => fileName === "compatCheck.ts";
    host.getSourceFile = (fileName, languageVersion, onError?, shouldCreateNewSourceFile?) => {
        if (fileName === "compatCheck.ts") {
            return ts.createSourceFile(fileName, `
// Compiler Required Types
interface Array<T> { isArray: T & true }
type Boolean = { isBoolean: true }
type Function = { isFunction: true }
type IArguments = { isIArguments: true }
type Number = { isNumber: true }
type Object = { isObject: true }
type RegExp = { isRegExp: true }
type String = { isString: true }

type Source = ${source};
type Target = ${target};

let source!: Source;
let target!: Target;
target = source;
`, languageVersion);
        }

        if (sourceFileCache[fileName] === undefined) {
            return sourceFileCache[fileName] = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
        } else {
            return sourceFileCache[fileName];
        }
    }

    let program = ts.createProgram(["compatCheck.ts"], {
        noLib: true // Exclude default lib parsing, provide custom types
    }, host);
    let errors = program.getSemanticDiagnostics()
        .concat(program.getDeclarationDiagnostics())
        .concat(program.getConfigFileParsingDiagnostics())
        .concat(program.getGlobalDiagnostics())
        .concat(program.getOptionsDiagnostics())
        .concat(program.getSyntacticDiagnostics());
    return !errors.some(e => true);
}

console.log(match('number', 'any')); // true any can be assigned to number
console.log(match('any', 'number')); // true number can be assigned to any as well 
console.log(match('{a: string; b: number}', '{a: string; b: number}')); // true
console.log(match('{a: string; b: number}', '{a: string}')); // true
console.log(match('{a: string}', '{a: string; b: number}')); // false

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Utilizing Angular to Handle Undefined Variables in String Interpolation

Seeking a way to showcase data obtained from an external API on a webpage using Angular's string interpolation. If no data is retrieved or is still pending, the aim is to display 'N/A'. An attempt was made following this method, but encoun ...

Creating QR codes from raw byte data in TypeScript and Angular

I have developed a basic web application that fetches codes from an endpoint and generates a key, which is then used to create a QR Code. The key is in the form of an Uint8Array that needs to be converted into a QR Code. I am utilizing the angularx-qrcode ...

Struggling to connect the array of objects from the .ts file with the template (.html) in Angular

Inside this .ts file, I am populating the "mesMenus" array that I want to display in the .html file: export class MenusComponent{ mesMenus= new Array<Menu>(); constructor(private gMenuService:GestionMenuService){ this.gMenuService.onAdd ...

The deletion by index feature seems to be malfunctioning in Angular

Is there a way to delete an item by its ID instead of just deleting the last element using this code? I want to create input fields with a delete button next to each one simultaneously. TS: public inputs: boolean[] = []; public addNew(): void { this ...

Tips for accurately implementing the onHoverIn TS type in the React Native Web Pressable component

I'm working with React Native Web and Typescript, and I want to integrate the React Native Web Pressable component into my project. However, I encountered an issue where VSCode is showing errors for React Native Web prop types like onHoverIn. The pro ...

Crystal-clear TextField component in Office UI Fabric

Seeking advice on how to clear a masked text field from Office UI Fabric using a button. Does anyone have a solution for this? I attempted to set the value using a state, but unfortunately, it did not work as expected. ...

Jasmine is raising an error: "TypeError: Unable to access the property 'client' of an undefined object"

While running test cases for the EditFlag component in Angular, I encountered an error stating TypeError: Cannot read property 'client' of undefined. Additionally, I am looking to add a test case for a switch case function. Can someone assist me ...

Managing relationships within TypeORM's single table inheritance using a base class for targeting relations

In my application, I aim to provide users with notifications in the form of news items of various types. The relationship between User and NewsItem needs to be one-to-many, with NewsItem serving as a base class for different types of news items. Below is ...

Encountering an error when using the Vue 3 TypeScript Composition API for style binding with an asynchronous

I utilized nexttick alongside an async method to update a DOM element. However, I am encountering issues with returning the correct style type. An error message pops up stating: error TS2322: Type 'Promise<{ maxHeight: string; }>' is not ...

When interacting with a <select> element, the behavior of test script execution varies between Firefox and Chrome

I've encountered an unusual problem that I need help describing and solving. Your assistance is greatly appreciated! The issue I'm facing involves Testcafe behaving differently when running the same test script on various browsers. testcafe: ...

What is the process for setting the active state for HtmlBodyElement?

I attempted to use the following method: document.querySelector('body').setActive(); However, I encountered an error: TS2339: Property 'setActive' does not exist on type 'HTMLBodyElement'. Any suggestions on how to resolve t ...

Why do variables in an HTML file fail to update after being navigated within onAuthStateChanged?

Currently, I am working with Ionic 5 and Firebase. Everything is running smoothly until I implemented the onAuthStateChanged function to persist login for authenticated users. Here is the code snippet: this.ngFireAuth.onAuthStateChanged((user) => { ...

Ensuring that the field is empty is acceptable as long as the validators are configured to enforce

I have successfully created a form using control forms. idAnnuaire: new FormControl('',[Validators.minLength(6),Validators.maxLength(6)]), However, I am facing an issue where when the field is left empty, {{form.controls.idAnnuaire.valid }} is ...

TypeORM does not have the capability to specify the database setting within the entity decorator

As my TypeORM project grows in size and its components become more discreet yet interconnected, I am exploring ways to separate it into multiple databases while maintaining cross-database relations. To achieve this, I have been experimenting with the data ...

I'm encountering an issue with one of my routes not loading correctly in Angular 4 Universal

I have been working on implementing Universal and I believe I've made significant progress. My project is built on this seed. However, when I run "npm start", only the /about and /contact pages render successfully. The /home page does not render at al ...

Unusual Interactions between Angular and X3D Technologies

There is an unusual behavior in the x3d element inserted into an Angular (version 4) component that I have observed. The structure of my Angular project is as follows: x3d_and_angular/ app/ home/ home.component.css hom ...

Ways to avoid using a specific type in TypeScript

Imagine having a class that wraps around a value like this: class Data<T> { constructor(public val: T){} set(newVal: T) { this.val = newVal; } } const a = new Data('hello'); a.set('world'); // typeof a --> Primitiv ...

Is array.length access cached by NodeJS?

Lately, I've been pondering whether accessing the array.length getter is cached by NodeJS. I've searched for conclusive answers about JS interpretation in browsers, but since I am working on apps in Typescript, that information does not directly ...

React doesn't have file upload configured to update the state

I am working on integrating a file upload button that sends data to an API. To ensure only the button triggers the upload dialog and not the input field, I have set it up this way. Issue: The File is not being saved to state, preventing me from using a ...

Pause until the user selects either the confirm or deny option before proceeding with the next action

As a beginner in Angular, I am seeking help with my code structure. I have three files: WarningComponent (which displays a warning modal using Bootstrap), modalService (responsible for opening modals), and votingComponent. Within the votingComponent, ther ...