Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
静的解析で実現したいことから 逆算して学ぶ TypeScript Compiler
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
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
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.3k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
5.9k
Navigating Team Friction
lara
192
16k
Rails Girls Zürich Keynote
gr2m
96
14k
Abbi's Birthday
coloredviolet
2
8.1k
Noah Learner - AI + Me: how we built a GSC Bulk Export data pipeline
techseoconnect
PRO
0
200
Building AI with AI
inesmontani
PRO
1
1.1k
4 Signs Your Business is Dying
shpigford
187
22k
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
230
The SEO Collaboration Effect
kristinabergwall1
1
490
Amusing Abliteration
ianozsvald
1
210
Statistics for Hackers
jakevdp
799
230k
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