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
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
The untapped power of vector embeddings
frankvandijk
2
1.8k
Utilizing Notion as your number one productivity tool
mfonobong
4
380
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
62
44k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.6k
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
1
2.1k
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.1k
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
460
Design of three-dimensional binary manipulators for pick-and-place task avoiding obstacles (IECON2024)
konakalab
0
490
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
A Modern Web Designer's Workflow
chriscoyier
698
190k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1k
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