TypeCheckerをいじっていて詰まったところメモ
最近はtypescript向けeslintプラグインを作る目的でTypeCheckerをこねこねしています。この辺の日本語情報は全然見つからなかったので、何かの役に立つかもの思いを込めてメモを残しておきます。
// eslintプラグイン作成環境で、typecheckerインスタンスを作成
import { ESLintUtils } from "@typescript-eslint/utils";
const createRule = ESLintUtils.RuleCreator((name) => `https://npmjs.com/package/${name}`);
createRule({
create(context) {
const parserServices = context.sourceCode.parserServices;
const checker = parserServices?.program?.getTypeChecker() // これ
}
// 省略
})
// typechecker の型定義インポート
import { TypeChecker } from "typescript";
格闘した結果はだいたいここにあります
type Type = []
の扱い
[]
は neverやanyの配列ではなく長さゼロのタプルなので、 checker.isArrayType
が trueになりません。検出するときは、checker.isTupleType
などを使う必要あります。なお、checker.isArrayLikeType
を使うと配列だろうがタプルだろうが両方検知できます。
複雑な型(大掛かりなユニオンなど)を解体すると type SampleArray = {name: age}[] | []
みたいな型が現れることがありますが、あれは配列とタプルのユニオンなんですね。
ちなみに、 isArrayType
や isArrayLikeType
、isTupleType
がtrueだったとしても、型に is のガードは効きません。githubを見た限りだと、これを嫌って自前の型ガードをつけている方がちらほらいそうです。
jsxのattributes
TypeScriptのASTでjsxのattributeを参照する場合、おそらく JSXOpeningElement
を確認し、 node.attributes
から attributeの配列を確認することになると思われます。
const Component = (props: { name: string; age: number }) => <></>;
const taro = { name: "taro", age: 10 };
const app = <Component {...taro} name="jiro" />;
このとき node.attributes
には、 [taroの情報が含まれたspread表現のattribute , name="jiro"の情報が含まれたattribute]
の2つが含まれます。
ただし、これを扱う上で2つ課題があり、いくらか頭を悩ませることになりました。
- attribute の typeには
JSXAttribute
とJSXSpreadAttribute
の2種類があり、情報形式が結構違う- 型定義上、
JSXOpeningElement.attributes: (TSESTree.JSXAttribute | TSESTree.JSXSpreadAttribute)[]
となっている
- 型定義上、
- 最終的にjsxに渡される型を取得する方法は(おそらく)存在しない
- 上の例の場合、 Componentに最終的に渡されるのは
{name:"jiro", age: 10}
のオブジェクト型ですが、これをTypeCheckerで取得することはできなさそうです
- 上の例の場合、 Componentに最終的に渡されるのは
無理やり型情報をまとめようとしたら以下のようになりました。各attributeのts.Type情報を取得し、プロパティ名をkeyにしてマージしていきます。
本当はts.Type型でobjectをまとめたかったのですが、自分にはRecord型にするのが精一杯でした。うまい方法をご存知の方いましたら教えてください。
const allProps: Record<string, ts.Type> = {};
node.attributes.forEach((attribute) => {
if (attribute.type === "JSXSpreadAttribute") {
const attrNode = parserServices.esTreeNodeToTSNodeMap.get(
attribute.argument,
);
const attrType = checker.getTypeAtLocation(attrNode);
attrType.getProperties().forEach((prop) => {
allProps[prop.name] = checker.getTypeOfSymbolAtLocation(
prop,
attrNode,
);
});
} else if (attribute.type === "JSXAttribute") {
const attrName = attribute.name.name;
if (typeof attrName !== "string") return;
const attrNode =
parserServices.esTreeNodeToTSNodeMap!.get(attribute);
const attrType = checker.getTypeAtLocation(attrNode);
allProps[attrName] = attrType;
}
});
TypeCheckerの型定義が足りない
ts.TypeCheckerには数多くのメソッドが定義されていますが、それでも実際に使えるはずのcheckerメソッドが全て使えるわけではなさそうです。実際に使えるcheckerメソッドは、ts.Typeをconsole等で出力すると確認できます。
定義されていないメソッドを使うには、やむを得ない選択ではありますが、自分で拡張する必要がありそうです。
export type Checker = ts.TypeChecker & {
getElementTypeOfArrayType: (type: ts.Type) => ts.Type | undefined;
getPromisedTypeOfPromise: (type: ts.Type) => ts.Type | undefined;
};
ちなみに、シンプルなオブジェクトをconsoleで見てみると、170近いメソッドを確認できます
ts.Typeをconsole.logで見た時のcheckerオブジェクト
TypeObject {
checker: {
getNodeCount: [Function: getNodeCount],
getIdentifierCount: [Function: getIdentifierCount],
getSymbolCount: [Function: getSymbolCount],
getTypeCount: [Function: getTypeCount],
getInstantiationCount: [Function: getInstantiationCount],
getRelationCacheSizes: [Function: getRelationCacheSizes],
isUndefinedSymbol: [Function: isUndefinedSymbol],
isArgumentsSymbol: [Function: isArgumentsSymbol],
isUnknownSymbol: [Function: isUnknownSymbol],
getMergedSymbol: [Function: getMergedSymbol],
getDiagnostics: [Function: getDiagnostics2],
getGlobalDiagnostics: [Function: getGlobalDiagnostics],
getRecursionIdentity: [Function: getRecursionIdentity],
getUnmatchedProperties: [GeneratorFunction: getUnmatchedProperties],
getTypeOfSymbolAtLocation: [Function: getTypeOfSymbolAtLocation],
getTypeOfSymbol: [Function: getTypeOfSymbol],
getSymbolsOfParameterPropertyDeclaration: [Function: getSymbolsOfParameterPropertyDeclaration],
getDeclaredTypeOfSymbol: [Function: getDeclaredTypeOfSymbol],
getPropertiesOfType: [Function: getPropertiesOfType],
getPropertyOfType: [Function: getPropertyOfType],
getPrivateIdentifierPropertyOfType: [Function: getPrivateIdentifierPropertyOfType],
getTypeOfPropertyOfType: [Function: getTypeOfPropertyOfType],
getIndexInfoOfType: [Function: getIndexInfoOfType],
getIndexInfosOfType: [Function: getIndexInfosOfType],
getIndexInfosOfIndexSymbol: [Function: getIndexInfosOfIndexSymbol],
getSignaturesOfType: [Function: getSignaturesOfType],
getIndexTypeOfType: [Function: getIndexTypeOfType],
getIndexType: [Function: getIndexType],
getBaseTypes: [Function: getBaseTypes],
getBaseTypeOfLiteralType: [Function: getBaseTypeOfLiteralType],
getWidenedType: [Function: getWidenedType],
getTypeFromTypeNode: [Function: getTypeFromTypeNode],
getParameterType: [Function: getTypeAtPosition],
getParameterIdentifierInfoAtPosition: [Function: getParameterIdentifierInfoAtPosition],
getPromisedTypeOfPromise: [Function: getPromisedTypeOfPromise],
getAwaitedType: [Function: getAwaitedType],
getReturnTypeOfSignature: [Function: getReturnTypeOfSignature],
isNullableType: [Function: isNullableType],
getNullableType: [Function: getNullableType],
getNonNullableType: [Function: getNonNullableType],
getNonOptionalType: [Function: removeOptionalTypeMarker],
getTypeArguments: [Function: getTypeArguments],
typeToTypeNode: [Function: typeToTypeNode],
indexInfoToIndexSignatureDeclaration: [Function: indexInfoToIndexSignatureDeclaration],
signatureToSignatureDeclaration: [Function: signatureToSignatureDeclaration],
symbolToEntityName: [Function: symbolToEntityName],
symbolToExpression: [Function: symbolToExpression],
symbolToNode: [Function: symbolToNode],
symbolToTypeParameterDeclarations: [Function: symbolToTypeParameterDeclarations],
symbolToParameterDeclaration: [Function: symbolToParameterDeclaration],
typeParameterToDeclaration: [Function: typeParameterToDeclaration],
getSymbolsInScope: [Function: getSymbolsInScope],
getSymbolAtLocation: [Function: getSymbolAtLocation],
getIndexInfosAtLocation: [Function: getIndexInfosAtLocation],
getShorthandAssignmentValueSymbol: [Function: getShorthandAssignmentValueSymbol],
getExportSpecifierLocalTargetSymbol: [Function: getExportSpecifierLocalTargetSymbol],
getExportSymbolOfSymbol: [Function: getExportSymbolOfSymbol],
getTypeAtLocation: [Function: getTypeAtLocation],
getTypeOfAssignmentPattern: [Function: getTypeOfAssignmentPattern],
getPropertySymbolOfDestructuringAssignment: [Function: getPropertySymbolOfDestructuringAssignment],
signatureToString: [Function: signatureToString],
typeToString: [Function: typeToString],
symbolToString: [Function: symbolToString],
typePredicateToString: [Function: typePredicateToString],
writeSignature: [Function: writeSignature],
writeType: [Function: writeType],
writeSymbol: [Function: writeSymbol],
writeTypePredicate: [Function: writeTypePredicate],
getAugmentedPropertiesOfType: [Function: getAugmentedPropertiesOfType],
getRootSymbols: [Function: getRootSymbols],
getSymbolOfExpando: [Function: getSymbolOfExpando],
getContextualType: [Function: getContextualType],
getContextualTypeForObjectLiteralElement: [Function: getContextualTypeForObjectLiteralElement],
getContextualTypeForArgumentAtIndex: [Function: getContextualTypeForArgumentAtIndex],
getContextualTypeForJsxAttribute: [Function: getContextualTypeForJsxAttribute],
isContextSensitive: [Function: isContextSensitive],
getTypeOfPropertyOfContextualType: [Function: getTypeOfPropertyOfContextualType],
getFullyQualifiedName: [Function: getFullyQualifiedName],
getResolvedSignature: [Function: getResolvedSignature],
getCandidateSignaturesForStringLiteralCompletions: [Function: getCandidateSignaturesForStringLiteralCompletions],
getResolvedSignatureForSignatureHelp: [Function: getResolvedSignatureForSignatureHelp],
getExpandedParameters: [Function: getExpandedParameters],
hasEffectiveRestParameter: [Function: hasEffectiveRestParameter],
containsArgumentsReference: [Function: containsArgumentsReference],
getConstantValue: [Function: getConstantValue],
isValidPropertyAccess: [Function: isValidPropertyAccess],
isValidPropertyAccessForCompletions: [Function: isValidPropertyAccessForCompletions],
getSignatureFromDeclaration: [Function: getSignatureFromDeclaration],
isImplementationOfOverload: [Function: isImplementationOfOverload],
getImmediateAliasedSymbol: [Function: getImmediateAliasedSymbol],
getAliasedSymbol: [Function: resolveAlias],
getEmitResolver: [Function: getEmitResolver],
getExportsOfModule: [Function: getExportsOfModuleAsArray],
getExportsAndPropertiesOfModule: [Function: getExportsAndPropertiesOfModule],
forEachExportAndPropertyOfModule: [Function: forEachExportAndPropertyOfModule],
getSymbolWalker: [Function: getSymbolWalker],
getAmbientModules: [Function: getAmbientModules],
getJsxIntrinsicTagNamesAt: [Function: getJsxIntrinsicTagNamesAt],
isOptionalParameter: [Function: isOptionalParameter],
tryGetMemberInModuleExports: [Function: tryGetMemberInModuleExports],
tryGetMemberInModuleExportsAndProperties: [Function: tryGetMemberInModuleExportsAndProperties],
tryFindAmbientModule: [Function: tryFindAmbientModule],
tryFindAmbientModuleWithoutAugmentations: [Function: tryFindAmbientModuleWithoutAugmentations],
getApparentType: [Function: getApparentType],
getUnionType: [Function: getUnionType],
isTypeAssignableTo: [Function: isTypeAssignableTo],
createAnonymousType: [Function: createAnonymousType],
createSignature: [Function: createSignature],
createSymbol: [Function: createSymbol],
createIndexInfo: [Function: createIndexInfo],
getAnyType: [Function: getAnyType],
getStringType: [Function: getStringType],
getStringLiteralType: [Function: getStringLiteralType],
getNumberType: [Function: getNumberType],
getNumberLiteralType: [Function: getNumberLiteralType],
getBigIntType: [Function: getBigIntType],
createPromiseType: [Function: createPromiseType],
createArrayType: [Function: createArrayType],
getElementTypeOfArrayType: [Function: getElementTypeOfArrayType],
getBooleanType: [Function: getBooleanType],
getFalseType: [Function: getFalseType],
getTrueType: [Function: getTrueType],
getVoidType: [Function: getVoidType],
getUndefinedType: [Function: getUndefinedType],
getNullType: [Function: getNullType],
getESSymbolType: [Function: getESSymbolType],
getNeverType: [Function: getNeverType],
getOptionalType: [Function: getOptionalType],
getPromiseType: [Function: getPromiseType],
getPromiseLikeType: [Function: getPromiseLikeType],
getAsyncIterableType: [Function: getAsyncIterableType],
isSymbolAccessible: [Function: isSymbolAccessible],
isArrayType: [Function: isArrayType],
isTupleType: [Function: isTupleType],
isArrayLikeType: [Function: isArrayLikeType],
isEmptyAnonymousObjectType: [Function: isEmptyAnonymousObjectType],
isTypeInvalidDueToUnionDiscriminant: [Function: isTypeInvalidDueToUnionDiscriminant],
getExactOptionalProperties: [Function: getExactOptionalProperties],
getAllPossiblePropertiesOfTypes: [Function: getAllPossiblePropertiesOfTypes],
getSuggestedSymbolForNonexistentProperty: [Function: getSuggestedSymbolForNonexistentProperty],
getSuggestionForNonexistentProperty: [Function: getSuggestionForNonexistentProperty],
getSuggestedSymbolForNonexistentJSXAttribute: [Function: getSuggestedSymbolForNonexistentJSXAttribute],
getSuggestedSymbolForNonexistentSymbol: [Function: getSuggestedSymbolForNonexistentSymbol],
getSuggestionForNonexistentSymbol: [Function: getSuggestionForNonexistentSymbol],
getSuggestedSymbolForNonexistentModule: [Function: getSuggestedSymbolForNonexistentModule],
getSuggestionForNonexistentExport: [Function: getSuggestionForNonexistentExport],
getSuggestedSymbolForNonexistentClassMember: [Function: getSuggestedSymbolForNonexistentClassMember],
getBaseConstraintOfType: [Function: getBaseConstraintOfType],
getDefaultFromTypeParameter: [Function: getDefaultFromTypeParameter],
resolveName: [Function: resolveName],
getJsxNamespace: [Function: getJsxNamespace],
getJsxFragmentFactory: [Function: getJsxFragmentFactory],
getAccessibleSymbolChain: [Function: getAccessibleSymbolChain],
getTypePredicateOfSignature: [Function: getTypePredicateOfSignature],
resolveExternalModuleName: [Function: resolveExternalModuleName],
resolveExternalModuleSymbol: [Function: resolveExternalModuleSymbol],
tryGetThisTypeAt: [Function: tryGetThisTypeAt],
getTypeArgumentConstraint: [Function: getTypeArgumentConstraint],
getSuggestionDiagnostics: [Function: getSuggestionDiagnostics],
runWithCancellationToken: [Function: runWithCancellationToken],
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias: [Function: getLocalTypeParametersOfClassOrInterfaceOrTypeAlias],
isDeclarationVisible: [Function: isDeclarationVisible],
isPropertyAccessible: [Function: isPropertyAccessible],
getTypeOnlyAliasDeclaration: [Function: getTypeOnlyAliasDeclaration],
getMemberOverrideModifierStatus: [Function: getMemberOverrideModifierStatus],
isTypeParameterPossiblyReferenced: [Function: isTypeParameterPossiblyReferenced],
typeHasCallOrConstructSignatures: [Function: typeHasCallOrConstructSignatures]
}
},
これに対し、typescript 5.4.5 の ts.TypeCheckerに定義されたメソッドは85個だけでした。結構足りないです。どうして足りないのかはちょっと分かりません。有識者さんいましたら教えてください。