Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

tsgolintはいかにしてtypescript-goの非公開APIを呼び出しているのか

Avatar for syumai syumai
December 05, 2025

 tsgolintはいかにしてtypescript-goの非公開APIを呼び出しているのか

layerx.go #3 の発表資料です!
https://layerx.connpass.com/event/372984/

Avatar for syumai

syumai

December 05, 2025
Tweet

More Decks by syumai

Other Decks in Programming

Transcript

  1. 自己紹介 syumai LayerX所属 ソフトウェアエンジニア ECMAScript 仕様輪読会 / Asakusa.go 主催 Software

    Design 2023年12月号から2025年2月号まで Cloudflare Workersの連載をしました Twitter (現𝕏): @__syumai Website: https://syum.ai
  2. その1: git submoduleによるtypescript-goの設置 git submoduleで typescript-go のリポジトリを丸ごと tsgolint リポジトリ直 下に置いている

    go get しないことで自由度を上げている (コード生成、パッチ当てなど) submoduleを明示的に更新しないと壊れない (Go Modulesで管理するとうっかり上がることがあるかもしれない?) . ├── cmd │ └── tsgolint ├── go.mod ├── go.sum ├── go.work ├── go.work.sum ├── internal ├── README.md └── typescript-go
  3. internal packageにlinkするための shim package tsgolint 直下に shim packageを設置 中身は typescript-go

    の internal package配下に対応 公開したいものがここに置かれる ./shim ├── ast ├── bundled ├── checker ├── compiler ├── core ├── scanner ├── tsoptions ├── tspath └── vfs
  4. shimファイルの中身 (例) 例は shim/ast/shim.go internalな型をそのままexportしている // Code generated by tools/gen_shims.

    DO NOT EDIT. package ast import "github.com/microsoft/typescript-go/internal/ast" /* 中略 */ type AccessExpression = ast.AccessExpression type AccessorDeclaration = ast.AccessorDeclaration
  5. shimファイルの中身 (例) 同じく shim/ast/shim.go の例 関数はbody無しで、 go:linkname でlinkしてexportしている github.com/microsoft/typescript-go/shim/ast/shim/ast.CanHaveDecorators を呼んだら、

    github.com/microsoft/typescript-go/internal/ast.CanHaveDecorators のロ ジックが呼ばれる構造 //go:linkname CanHaveDecorators github.com/microsoft/typescript-go/internal/ast.CanHaveDecorators func CanHaveDecorators(node *ast.Node) bool //go:linkname CanHaveIllegalDecorators github.com/microsoft/typescript-go/internal/ast.CanHaveIllegalDecorators func CanHaveIllegalDecorators(node *ast.Node) bool //go:linkname CanHaveIllegalModifiers github.com/microsoft/typescript-go/internal/ast.CanHaveIllegalModifiers func CanHaveIllegalModifiers(node *ast.Node) bool //go:linkname CanHaveModifiers github.com/microsoft/typescript-go/internal/ast.CanHaveModifiers func CanHaveModifiers(node *ast.Node) bool
  6. shimファイルの中身 (例) shim/checker/shim.go の例 非公開メソッドも go:linkname を駆使して公開している type Checker =

    checker.Checker //go:linkname Checker_getResolvedSignature github.com/microsoft/typescript-go/internal/checker.(*Checker).getResolvedSignature func Checker_getResolvedSignature(recv *checker.Checker, node *ast.Node, candidatesOutArray *[]*checker.Signature, checkMode checker.CheckMode) *checker.Signature //go:linkname Checker_getTypeOfSymbol github.com/microsoft/typescript-go/internal/checker.(*Checker).getTypeOfSymbol func Checker_getTypeOfSymbol(recv *checker.Checker, symbol *ast.Symbol) *checker.Type わかりやすさのために改行していますが、実際は改行なしです
  7. go:linkname って非推奨じゃなかった? Goのランタイムへの go:linkname が封じられただけで、サードパーティーの moduleに対しては普通に使える runtime packageなどへの go:linkname はNG

    github.com/microsoft/typescript-go への go:linkname は何も問題ない 詳しい経緯はANDPADさんの「Go界隈で巻き起こった go:linkname 騒動につい て」を参照ください https://tech.andpad.co.jp/entry/2024/06/20/140000
  8. 実際の呼び出し例 internal/rules/no_confusing_void_expression/no_confusing_void_expression.go shimが無いと呼び出せない機能でlintルールを実装 package no_confusing_void_expression import ( "github.com/microsoft/typescript-go/shim/ast" "github.com/microsoft/typescript-go/shim/checker" "github.com/microsoft/typescript-go/shim/scanner"

    "github.com/typescript-eslint/tsgolint/internal/rule" "github.com/typescript-eslint/tsgolint/internal/utils" ) /* 中略 */ return utils.Some(callSignatures, func(s *checker.Signature) bool { returnType := checker.Checker_getReturnTypeOfSignature(ctx.TypeChecker, s) return utils.Some(utils.UnionTypeParts(returnType), utils.IsIntrinsicVoidType) })
  9. Go Moduleのreplaceで shim を typescript-go の中に入れる go.mod の例 module github.com/typescript-eslint/tsgolint

    go 1.24.1 replace ( github.com/microsoft/typescript-go/shim/ast => ./shim/ast github.com/microsoft/typescript-go/shim/bundled => ./shim/bundled github.com/microsoft/typescript-go/shim/checker => ./shim/checker github.com/microsoft/typescript-go/shim/compiler => ./shim/compiler github.com/microsoft/typescript-go/shim/core => ./shim/core github.com/microsoft/typescript-go/shim/scanner => ./shim/scanner github.com/microsoft/typescript-go/shim/tsoptions => ./shim/tsoptions github.com/microsoft/typescript-go/shim/tspath => ./shim/tspath github.com/microsoft/typescript-go/shim/vfs => ./shim/vfs github.com/microsoft/typescript-go/shim/vfs/cachedvfs => ./shim/vfs/cachedvfs github.com/microsoft/typescript-go/shim/vfs/osvfs => ./shim/vfs/osvfs )
  10. なぜ shim を  typescript-go に入れる必要があるのか? 恐らく internal package配下の型情報をexportするのが主目的 shim が

    typescript-go 内なので typescript-go の internal が見える go:linkname での関数の公開にも、引数・戻り値の型情報は必要 さっきの shim/ast/shim.go の例 // Code generated by tools/gen_shims. DO NOT EDIT. package ast import "github.com/microsoft/typescript-go/internal/ast" /* 中略 */ type AccessExpression = ast.AccessExpression type AccessorDeclaration = ast.AccessorDeclaration
  11. shim の生成 tools/gen_shims で実装されている ASTを解析して、 internal package配下で公開の型・関数を自動で shim にexport 非公開のメソッド、関数などのexportに使える

    extra-shim.json という設 定ファイルもある 手間のかかるexportの作業を全自動化している 実装の詳細は見れてないです