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
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
480
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
480
Navigating Weather and Climate Data
rabernat
0
450
Bash Introduction
62gerente
615
220k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
160
Lightning Talk: Beautiful Slides for Beginners
inesmontani
PRO
2
620
Side Projects
sachag
455
43k
Writing Fast Ruby
sferik
630
63k
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
170
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Balancing Empowerment & Direction
lara
6
1.2k
Are puppies a ranking factor?
jonoalderson
1
3.8k
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