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
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
The Spectacular Lies of Maps
axbom
PRO
1
820
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
390
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
3.5k
Site-Speed That Sticks
csswizardry
13
1.2k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
560
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
850
Building a Scalable Design System with Sketch
lauravandoore
463
34k
Un-Boring Meetings
codingconduct
0
320
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
180
Designing for humans not robots
tammielis
254
26k
Prompt Engineering for Job Search
mfonobong
0
350
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