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

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

Avatar for Kazushi Konosu Kazushi Konosu
May 23, 2025
840

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

TSKaigi 2025の登壇資料です

Avatar for Kazushi Konosu

Kazushi Konosu

May 23, 2025
Tweet

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