TypeScript:检查类型兼容性

时间:2018-02-19 08:16:36

标签: typescript types

从TypeScript类型的两个表示形式,可以是字符串:

A:

{
    a: string
    b: number
}

B:

{
    a: string
}

如何以编程方式测试它们是否兼容,回答“我可以将类型A的变量分配给类型B的值吗?”的问题。 (或相反亦然)。

该功能如下所示:

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
// ...

最简单的方法是什么?

编辑:用例是我有自定义用户生成的类型接口,我想检查它们是否在设计时兼容。 TypeScript只是用于表达这些类型的语法,但这是断言类型匹配的挑战的次要因素。它可以是任何其他类型的系统。

1 个答案:

答案 0 :(得分:3)

您可以将编译器作为npm包引入(只需运行npm install typescript)并在代码中使用编译器。下面的解决方案,只是创建一个小程序来测试两种类型的兼容性。性能可能是一个问题,因此请尝试使用您的真实用例,看看它是否可以接受:

import * as ts from 'typescript'

// Cache declarations (lib.d.ts for example) to improve performance
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?) => {
        // You can add more virtual files, or let the host default read the from the disk
        if (fileName === "compatCheck.ts") {
            return ts.createSourceFile(fileName, `
type Source = ${source};
type Target = ${target};

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

        // Try to get source file from cache, will perfrom better if we reuse the parsed source file.
        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

修改

如果您不解析任何默认的lib.d.ts并且只提供编译器工作所需的最小类型,那么应该性能更好的版本。这是一个非常小的集(ArrayBooleanNumberFunctionIArgumentsObjectRegExp和串)。我们也不能包含这些类型中的任何方法,只提供一个简单的定义来保持类型不兼容。如果您需要任何其他类型,则必须明确添加,但这应该足够简单。如果你想允许在这些类型上使用任何方法,你还需要添加这些方法,但我对你的用例的理解是,你只是想比较接口的兼容性,所以这不应该是一个问题:

import * as ts from "typescript";

// Cache declarations (lib.d.ts for example) to improve 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?) => {
        // You can add more virtual files, or let the host default read the from the disk
        if (fileName === "compatCheck.ts") {
            return ts.createSourceFile(fileName, `
// Compiler Reuired 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);
        }

        // Try to get source file from cache, will perfrom better if we reuse the parsed source file.
        if (sourceFileCache[fileName] === undefined) {
            return sourceFileCache[fileName] = originalGetSourceFile(fileName, languageVersion, onError, shouldCreateNewSourceFile);
        } else {
            return sourceFileCache[fileName];
        }
    }

    let program = ts.createProgram(["compatCheck.ts"], {
        noLib: true // Don't parse any default lib, we provide our own 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