Upgrade to Pro — share decks privately, control downloads, hide ads and more …

静的解析で実現したいことから
逆算して学ぶ
TypeScript Compiler

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest. →
Avatar for Kazushi Konosu Kazushi Konosu
May 23, 2025
1.2k

静的解析で実現したいことから
逆算して学ぶ
TypeScript Compiler

TSKaigi 2025の登壇資料です

Avatar for Kazushi Konosu

Kazushi Konosu

May 23, 2025

Transcript

  1. ߵ૥ ࿨࢘ / Kazushi Konosu • Software Engineer & Engineering

    Manager,
 LINEϠϑʔגࣜձࣾ • ڈ೥ TypeScript Remove (tsr)ͱ͍͏OSSΛ
 ։ൃ͠·ͨ͠ 1.2k GitHub Stars ⭐ 2
  2. └── 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
  3. TypeScript CompilerΛબͿཧ༝ ESTreeϕʔε TypeScript Compiler ॆ࣮ͨ͠ΤίγεςϜ ✅ ૬ରతʹॆ࣮͍ͯ͠ͳ͍ΤίγεςϜ ଟ͘ͷࢀߟࢿྉ ✅

    ݶΒΕͨυΩϡϝϯτ ίʔυͷʮҙຯʯͷ׆༻͕ݶఆత ܕɺࢀরؔ܎ͳͲҙຯ΁ͷΞΫηε ✅ ෳ਺ͷґଘؔ܎ ୯Ұͷґଘؔ܎ ✅ 9
  4. • 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
  5. ֓ཁ • TypeScript Compiler͸ɺؔ਺ܕϓϩάϥϛϯάͷӨڹΛड͚͍ͯΔ • Interfaceͱfactoryؔ਺ͷ૊Έ߹Θͤ • interface SourceFile ͱ

    createSourceFile() • interface Program ͱ createProgram() • interface TypeChecker ͱ program.getTypeChecker() 13
  6. Node • ͢΂ͯͷߏจཁૉͷϕʔεͱͳΔInterface • node.kind: ts.SyntaxKind ϓϩύςΟͰछྨΛ൑ผ • posͱendͰϑΝΠϧ಺ʹ͓͚ΔҐஔ৘ใΛอ࣋ •

    .parent .getChildren() Ͱྡ઀͢Δ௖఺ΛࢀরͰ͖Δ • ϊʔυͷੜ੒͸ߏจͷछྨ͝ͱʹߦ͏ • ex. ts.factory.createIdentifier() ͢΂ͯͷϊʔυ͸֊૚ߏ଄Λܗ੒͠ɺϓϩάϥϜͷߏจΛදݱ͢Δ 14
  7. Program • Programͱ͸ • ෳ਺SourceFileͷू߹ମ • ϓϩδΣΫτશମΛදݱ • ϑΝΠϧؒͷؔ܎ੑΛղܾ •

    ܕνΣοΫͷίϯςΩετΛఏڙ • Program͸ඞͣ͠΋ϑΝΠϧγεςϜʹΞΫηεඞཁ͸ͳ͍ • ts.CompilerHostΛ౉͢͜ͱͰΦϯϝϞϦͰϑΝΠϧΛදݱͰ͖Δ 16
  8. TypeChecker • program.getTypeChecker() • ܕղܾͱݕূͷΤϯδϯ • ܕਪ࿦ͷ࣮ߦ • γϯϘϧղܾͷఏڙ •

    ܕޓ׵ੑͷνΣοΫ TypeCheckerΛ͔ͭ͏͜ͱͰɺߏจ͚ͩͰ͸ͳ͍ίʔυ͕࣋ͭҙຯΛ׆༻Ͱ͖Δ 17
  9. Symbol & Type • Symbol checker.getSymbolAtLocation() • એݴͱࢀরΛ݁ͼ͚ͭΔ • ࢀরؔ܎Λ௥੻

    • Type checker.getTypeAtLocation() • ࣜ΍એݴͷܕ৘ใ • ਪ࿦ͷ݁Ռ 18
  10. LanguageService • Programʹ࣌ؒ࣠ͱΑΓߴϨϕϧͳAPIΛ௥Ճͨ֓͠೦ • ߴ౓ϨϕϧAPI • languageService.findReferences() • ϑΝΠϧͷฤूঢ়ଶΛѻ͏ •

    LanguageService΋ಉ༷ʹts.LanguageServiceHostΛ౉͢͜ͱͰɺ
 ΦϯϝϞϦͰϑΝΠϧΛѻ͑Δ LanguageService͸VSCodeͷTypeScriptݴޠػೳͷϕʔεͳͷͰɺ
 VSCodeͰ͖Δ͜ͱ͸API͔Β΋ݺͼग़ͤΔ 19
  11. ֓೦ͷ࢖͍෼͚ • ୯ҰϑΝΠϧͷΈ • ߏจղੳͷΈ → ts.createSourceFile()Λ࢖༻ • ܕ৘ใ΋ඞཁ →

    ProgramΛ࡞੒ͯ͠ program.getTypeChecker() • ෳ਺ϑΝΠϧʢϓϩδΣΫτશମʣ • Ұ౓͖Γ → ProgramΛ࢖༻ • ߋ৽Λ؅ཧ͢Δඞཁ͕͋Δ → LanguageServiceΛ࢖༻ ඞཁ࠷খݶͷબ୒Λߦ͏͜ͱͰɺύϑΥʔϚϯεͱγϯϓϧ͞Λ࣮ݱ 20
  12. 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
  13. 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
  14. ίʔυฤूɾੜ੒ (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
  15. Ξϓϩʔνͷ࢖͍෼͚ ໨త Ξϓϩʔν ߏจͷղੳ SourceFile + visitor ܕ৘ใͳͲίʔυͷҙຯʹؔ͢Δ৘ใͷ׆༻ Program +

    TypeChecker + visitor ίʔυੜ੒ SourceFile/Program + visitor + factory + Printer ϑΥʔϚοτΛҡ࣋ͨ͠ฤू SourceFile/Program + visitor + factory + ฤूϩ δοΫ ίʔυฤूʹ௥ै͢Δඞཁ͕͋Δͱ͖ LanguageService + ඞཁʹԠ্ͯ͡ه findReferencsͳͲͷߴϨϕϧͳAPI͕ඞཁ LanguageService + ඞཁʹԠ্ͯ͡ه 27
  16. ·ͱΊ • TypeScript Compilerͷجຊ֓೦ • Node, SourceFile, Program, TypeChecker, LanguageService

    • ໨తʹԠͨ͡Ξϓϩʔν • Visitor, Transformer/Printer, TextChangeΛ࢖ͬͨࣗલͷฤूϩδοΫ • ࣮ફͷΞΠσΞ 31
  17. 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
  18. 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