Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
静的解析で実現したいことから 逆算して学ぶ TypeScript Compiler
Search
Kazushi Konosu
May 23, 2025
1.2k
5
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
静的解析で実現したいことから 逆算して学ぶ TypeScript Compiler
TSKaigi 2025の登壇資料です
Kazushi Konosu
May 23, 2025
Featured
See All Featured
Building a Scalable Design System with Sketch
lauravandoore
464
34k
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.9k
The Cult of Friendly URLs
andyhume
79
7k
Writing Fast Ruby
sferik
630
63k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.2k
30 Presentation Tips
portentint
PRO
1
410
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
290
Visualization
eitanlees
153
17k
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
460
The Curse of the Amulet
leimatthew05
3
15k
How to Think Like a Performance Engineer
csswizardry
28
2.9k
Transcript
2025-05-23 TSKaigi 2025 ੩తղੳͰ࣮ݱ͍ͨ͜͠ͱ͔Β ٯࢉֶͯ͠Ϳ TypeScript Compiler ߵ ࢘ /
Kazushi Konosu 1
ߵ ࢘ / Kazushi Konosu • Software Engineer & Engineering
Manager, LINEϠϑʔגࣜձࣾ • ڈ TypeScript Remove (tsr)ͱ͍͏OSSΛ ։ൃ͠·ͨ͠ 1.2k GitHub Stars ⭐ 2
੩తղੳ • ϓϩάϥϜΛ࣮ߦͤͣʹιʔείʔυ͔ΒใΛಡΈऔΔ͜ͱ • ྫ • ܕνΣοΫ (tsc) • Ϧϯτ
(ESLint) • ϑΥʔϚοτ (Prettier) ੩తղੳͷՄೳੑແݶେʂ 3
ߏจ ίʔυ ใ ίʔυ ߏจ 4
function add(a: number, b: number) { return a + b;
} 5
└── SourceFile "function add(a: number, b: number) { return a
+ b; }" ├── SyntaxList "function add(a: number, b: number) { return a + b; }" │ └── FunctionDeclaration "function add(a: number, b: number) { return a + b; }" │ ├── FunctionKeyword "function" │ ├── Identifier "add" │ ├── OpenParenToken "(" │ ├── SyntaxList "a: number, b: number" │ │ ├── Parameter "a: number" │ │ │ ├── Identifier "a" │ │ │ ├── ColonToken ":" │ │ │ └── NumberKeyword "number" │ │ ├── CommaToken "," │ │ └── Parameter "b: number" │ │ ├── Identifier "b" │ │ ├── ColonToken ":" │ │ └── NumberKeyword "number" │ ├── CloseParenToken ")" │ └── Block "{ return a + b; }" │ ├── FirstPunctuation "{" │ ├── SyntaxList "return a + b;" │ │ └── ReturnStatement "return a + b;" │ │ ├── ReturnKeyword "return" │ │ ├── BinaryExpression "a + b" │ │ │ ├── Identifier "a" │ │ │ ├── PlusToken "+" │ │ │ └── Identifier "b" │ │ └── SemicolonToken ";" │ └── CloseBraceToken "}" └── EndOfFileToken "" 6
ߏจ ίʔυ ใ ίʔυ ߏจ ߏจͷੜɺ୳ࡧ(traverse)ɺग़ྗͷํ๏͚ͩΘ͔Ε͍͍ʂ 7
੩తղੳͰ࣮ݱ͍ͨ͜͠ͱ͔Β ٯࢉֶͯ͠Ϳ TypeScript Compiler 8
TypeScript CompilerΛબͿཧ༝ ESTreeϕʔε TypeScript Compiler ॆ࣮ͨ͠ΤίγεςϜ ✅ ૬ରతʹॆ࣮͍ͯ͠ͳ͍ΤίγεςϜ ଟ͘ͷࢀߟࢿྉ ✅
ݶΒΕͨυΩϡϝϯτ ίʔυͷʮҙຯʯͷ׆༻͕ݶఆత ܕɺࢀরؔͳͲҙຯͷΞΫηε ✅ ෳͷґଘؔ ୯Ұͷґଘؔ ✅ 9
• ESTreeΛϕʔεʹͦΕͧΕͷπʔϧ͕ಠ࣮ࣗΛ͍ͯ͠Δ • ESLint/PrettierͷϓϥάΠϯͰࡁΉ༰Ͱͳ͍߹ɺෳࡶͳґଘ͕ؔඞཁ • ref. un fi ed.js, @typescript-eslint/parser
• ڧݻͳܕʹΑΔॻ͖ຯͷΑ͞ TypeScript CompilerΛબͿཧ༝ (2) // node: ts.Node if (ts.isIdentifier(node)) { // node: ts.Identifier } ֶशۂઢٸ͕ͩ׆༻͢ΔՁ͕͋Δ 10
֓೦ͷཧ Ξϓϩʔνͷํ๏ ࣮ફ τϐοΫ 11
֓೦ͷཧ Ξϓϩʔνͷํ๏ ࣮ફ τϐοΫ 12
֓ཁ • TypeScript CompilerɺؔܕϓϩάϥϛϯάͷӨڹΛड͚͍ͯΔ • InterfaceͱfactoryؔͷΈ߹Θͤ • interface SourceFile ͱ
createSourceFile() • interface Program ͱ createProgram() • interface TypeChecker ͱ program.getTypeChecker() 13
Node • ͯ͢ͷߏจཁૉͷϕʔεͱͳΔInterface • node.kind: ts.SyntaxKind ϓϩύςΟͰछྨΛผ • posͱendͰϑΝΠϧʹ͓͚ΔҐஔใΛอ࣋ •
.parent .getChildren() Ͱྡ͢ΔΛࢀরͰ͖Δ • ϊʔυͷੜߏจͷछྨ͝ͱʹߦ͏ • ex. ts.factory.createIdentifier() ͯ͢ͷϊʔυ֊ߏΛܗ͠ɺϓϩάϥϜͷߏจΛදݱ͢Δ 14
SourceFile • ୯ҰϑΝΠϧͷߏจΛදݱ • NodeΛܧঝͨ͠Interface • ϑΝΠϧϨϕϧͷϝλσʔλΛอ࣋ • string͔Β ts.createSourceFile()
ʹΑͬͯੜͰ͖Δ 15
Program • Programͱ • ෳSourceFileͷू߹ମ • ϓϩδΣΫτશମΛදݱ • ϑΝΠϧؒͷؔੑΛղܾ •
ܕνΣοΫͷίϯςΩετΛఏڙ • Programඞͣ͠ϑΝΠϧγεςϜʹΞΫηεඞཁͳ͍ • ts.CompilerHostΛ͢͜ͱͰΦϯϝϞϦͰϑΝΠϧΛදݱͰ͖Δ 16
TypeChecker • program.getTypeChecker() • ܕղܾͱݕূͷΤϯδϯ • ܕਪͷ࣮ߦ • γϯϘϧղܾͷఏڙ •
ܕޓੑͷνΣοΫ TypeCheckerΛ͔ͭ͏͜ͱͰɺߏจ͚ͩͰͳ͍ίʔυ͕࣋ͭҙຯΛ׆༻Ͱ͖Δ 17
Symbol & Type • Symbol checker.getSymbolAtLocation() • એݴͱࢀরΛ݁ͼ͚ͭΔ • ࢀরؔΛ
• Type checker.getTypeAtLocation() • ࣜએݴͷܕใ • ਪͷ݁Ռ 18
LanguageService • Programʹ࣌ؒ࣠ͱΑΓߴϨϕϧͳAPIΛՃͨ֓͠೦ • ߴϨϕϧAPI • languageService.findReferences() • ϑΝΠϧͷฤूঢ়ଶΛѻ͏ •
LanguageServiceಉ༷ʹts.LanguageServiceHostΛ͢͜ͱͰɺ ΦϯϝϞϦͰϑΝΠϧΛѻ͑Δ LanguageServiceVSCodeͷTypeScriptݴޠػೳͷϕʔεͳͷͰɺ VSCodeͰ͖Δ͜ͱAPI͔Βݺͼग़ͤΔ 19
֓೦ͷ͍͚ • ୯ҰϑΝΠϧͷΈ • ߏจղੳͷΈ → ts.createSourceFile()Λ༻ • ܕใඞཁ →
ProgramΛ࡞ͯ͠ program.getTypeChecker() • ෳϑΝΠϧʢϓϩδΣΫτશମʣ • Ұ͖Γ → ProgramΛ༻ • ߋ৽Λཧ͢Δඞཁ͕͋Δ → LanguageServiceΛ༻ ඞཁ࠷খݶͷબΛߦ͏͜ͱͰɺύϑΥʔϚϯεͱγϯϓϧ͞Λ࣮ݱ 20
֓೦ͷཧ Ξϓϩʔνͷํ๏ ࣮ફ τϐοΫ 21
• ts.createProgram()ts.createSourceFile()ͱ͍ͬͨ factoryؔͰίʔυ͔ΒߏจͷมͰ͖ͨ • ୳ࡧΛߦ͏ʹvisitorΛ༻ • ϊʔυͷࢠଙʹରͯ͠ɺ࠶ؼతʹvisitorΛݺͿ ղੳ 22
import ts from 'typescript'; const code = `const hello =
'world';`; const sourceFile = ts.createSourceFile('file.ts', code, ts.ScriptTarget.Latest); const variableNames: string[] = []; const visit = (node: ts.Node) => { if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) { variableNames.push(node.name.text); } ts.forEachChild(node, visit); }; sourceFile.forEachChild(visit); console.log(variableNames); VisitorύλʔϯͰใΛऩू͢Δ͜ͱ͕Ͱ͖Δ 23
ίʔυฤूɾੜ • TransformerͱPrinterΛΈ߹ΘͤΔ • Transformer • VisitorΛͬͯฤू͍ͨ͠ߏจཁૉͷϊʔυΛஔ͖͑Δ • Printer •
TransfomerΛద༻ͨ͠ߏจΛจࣈྻͱͯ͠ग़ྗ͢Δ • ϑΥʔϚοτ͕ࣦΘΕΔ 24
TransformerͱPrinterΛ͏͜ͱͰɺίʔυΛੜͰ͖Δ import ts from 'typescript'; const code = `const hello
= 'world';`; const sourceFile = ts.createSourceFile('file.ts', code, ts.ScriptTarget.Latest); const transformer = (context: ts.TransformationContext) => (node: ts.Node): ts.Node => { if (ts.isStringLiteral(node) && node.text === 'world') { return ts.factory.createStringLiteral('edited'); } return ts.visitEachChild( node, (node) => transformer(context)(node), context, ); }; const result = ts.transform(sourceFile, [transformer]); const printer = ts.createPrinter(); const transformedCode = printer.printFile( result.transformed[0] as ts.SourceFile, ); console.log(transformedCode); 25
ίʔυฤूɾੜ (2) • Transformer/PrinterͰϑΥʔϚοτ่͕ΕΔ • TextChangeͱ͍͏มߋΛѻ͏Interface • TypeScript෦ͰNodeͷҐஔใΛͱʹ TextChangeΛੜͯ͠ɺͦΕΛద༻͍ͯ͠Δ •
PublicͰͳ͍ͷͰࣗલͰ࣮͢Δඞཁ͕͋Δ interface TextSpan { start: number; length: number; } interface TextChange { span: TextSpan; newText: string; } Ref. https://github.com/microsoft/TypeScript/blob/b504a1eed45e35b5f54694a1e0a09f35d0a5663c/src/services/textChanges.ts#L1364 Ref. https://github.com/line/tsr/blob/06a0ef5e63739e78fa51aba49b63e898f5a6b7d3/lib/util/applyTextChanges.ts#L29ͨͩ͠ద༻͢ΔࡍʹվߦΛௐ͍ͯ͠Δ 26
Ξϓϩʔνͷ͍͚ త Ξϓϩʔν ߏจͷղੳ SourceFile + visitor ܕใͳͲίʔυͷҙຯʹؔ͢Δใͷ׆༻ Program +
TypeChecker + visitor ίʔυੜ SourceFile/Program + visitor + factory + Printer ϑΥʔϚοτΛҡ࣋ͨ͠ฤू SourceFile/Program + visitor + factory + ฤूϩ δοΫ ίʔυฤूʹै͢Δඞཁ͕͋Δͱ͖ LanguageService + ඞཁʹԠ্ͯ͡ه findReferencsͳͲͷߴϨϕϧͳAPI͕ඞཁ LanguageService + ඞཁʹԠ্ͯ͡ه 27
֓೦ͷཧ Ξϓϩʔνͷํ๏ ࣮ફ τϐοΫ 28
Idea: importจͷҰׅॻ͖͑ • tscon fi g pathsͷઃఆΛফ͢ɾNode.js ESMʹ४ڌ͢Δ֦ுࢠͷՃ • ༻͢ΔAPI
• ts.createSourceFile() • TypeChecker • TextChange 29
Idea: OpenAPIఆ͔ٛΒϥϯλΠϜίʔυੜ • OpenAPI͔Βܕఆ͚ٛͩͰͳ͘ɺϥϯλΠϜίʔυΛੜ͢Δ • ༻͢ΔAPI • ts.createSourceFile() • Transformer/Printer
30
·ͱΊ • TypeScript Compilerͷجຊ֓೦ • Node, SourceFile, Program, TypeChecker, LanguageService
• తʹԠͨ͡Ξϓϩʔν • Visitor, Transformer/Printer, TextChangeΛͬͨࣗલͷฤूϩδοΫ • ࣮ફͷΞΠσΞ 31
Further Reading • ެࣜCompiler APIϨϑΝϨϯε https://github.com/Microsoft/TypeScript/wiki/Using-the-Compiler-API • ίʔυ͕෦తʹͲͷΑ͏ʹදݱ͞Ε͍ͯΔ͔Λ֬ೝ͢Δ https://ts-ast-viewer.com/ •
ࠓͷൃදͷ༰͕࣮ࡍͷϥΠϒϥϦʹͲͷΑ͏ʹ׆༻͞Ε͍ͯΔ͔ΛݟΔ https://github.com/line/tsr 32
Thank you! 🧑💻 X @kazushikonosu github.com/kazushisan 33
import ts from 'typescript'; const sourceFile = ts.createSourceFile( 'add.ts', 'function
add(a: number, b: number) { return a + b; }', ts.ScriptTarget.Latest, true, ); function printNode(node: ts.Node, prefix: string = '', isLast: boolean = true) { const connector = isLast ? '└── ' : '├── '; const childPrefix = prefix + (isLast ? ' ' : '│ '); const kind = ts.SyntaxKind[node.kind]; const text = node.getText(sourceFile).replace(/\n/g, '\\n'); console.log(`${prefix}${connector}${kind} "${text}"`); const children = node.getChildren(sourceFile); children.forEach((child, index) => { printNode(child, childPrefix, index === children.length - 1); }); } printNode(sourceFile); declare var node: ts.Node if (ts.isIdentifier(node)) { node // do something } ts.createLanguageService({}) Appendix 1: ߏจΛՄࢹԽ͢Δίʔυ • P6ͷߏจΛtreeίϚϯυ෩ʹ දࣔ͢Δྫ 34
Appendix 2: tscon fi gΛಡΈࠐΉ import ts from 'typescript'; const
{ config } = ts.readConfigFile( 'path/to/tsconfig.json', ts.sys.readFile, ); const { options, fileNames } = ts.parseJsonConfigFileContent( config, ts.sys, 'path/to/project/root', ); const program = ts.createProgram(fileNames, options); • ൃදͰtscon fi g.jsonΛѻΘͣʹ TypeScript CompilerͷύϥϝʔλΛ ࢦఆ͕ͨ͠ɺtscon fi g.jsonΛಡΈࠐΜͰ ֎෦Խ͢Δ͜ͱͰ͖Δ • tscon fi g.jsonͷϑΟʔϧυAPIͰ༻ ͞Ε͍ͯͳ͍ͨΊɺ ts.CompilerOptionsΛຬͨ͢Φϒ δΣΫτʹม͢Δ࡞ۀ͕ඞཁ 35