<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: akamabe</title>
    <description>The latest articles on DEV Community by akamabe (@akamabe).</description>
    <link>https://dev.to/akamabe</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F621734%2Fd95199e0-1f34-49b9-bc8e-7dc20f5a1bea.png</url>
      <title>DEV Community: akamabe</title>
      <link>https://dev.to/akamabe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akamabe"/>
    <language>en</language>
    <item>
      <title>Building a TypeScript Code Radar: What an AST Reveals That Text Search Misses</title>
      <dc:creator>akamabe</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:17:50 +0000</pubDate>
      <link>https://dev.to/akamabe/building-a-typescript-code-radar-what-an-ast-reveals-that-text-search-misses-231d</link>
      <guid>https://dev.to/akamabe/building-a-typescript-code-radar-what-an-ast-reveals-that-text-search-misses-231d</guid>
      <description>&lt;p&gt;Today I started experimenting with &lt;strong&gt;Tree-sitter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I had heard the name before, but I had never actually used it. My goal was simple: take a TypeScript file, parse it, and understand what Tree-sitter sees that a normal text search does not.&lt;/p&gt;

&lt;p&gt;The file I used was a tiny NestJS controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UseGuards&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthGuard&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./auth.guard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;OrdersService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./orders.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Controller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;UseGuards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthGuard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;OrdersService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findOne&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first, I printed Tree-sitter's syntax tree directly.&lt;/p&gt;

&lt;p&gt;The result was technically correct, but almost unreadable: one huge nested expression containing nodes such as &lt;code&gt;import_statement&lt;/code&gt;, &lt;code&gt;class_declaration&lt;/code&gt;, &lt;code&gt;decorator&lt;/code&gt;, &lt;code&gt;call_expression&lt;/code&gt;, and &lt;code&gt;identifier&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So I wrote a small recursive tree printer.&lt;/p&gt;

&lt;p&gt;That was the moment Tree-sitter started to make sense.&lt;/p&gt;

&lt;p&gt;Instead of seeing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Controller('orders')
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;as a sequence of characters, Tree-sitter sees something structurally closer to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;decorator
  call_expression
    identifier = "Controller"
    arguments
      string = "'orders'"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrdersController&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes a structure containing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class_declaration
  type_identifier = "OrdersController"
  class_body
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction is much more important than it initially looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Text search asks: "Does this string exist?"
&lt;/h2&gt;

&lt;p&gt;A regular text search can easily find:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But it doesn't inherently know whether the string is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a decorator,&lt;/li&gt;
&lt;li&gt;a comment,&lt;/li&gt;
&lt;li&gt;part of a string,&lt;/li&gt;
&lt;li&gt;an identifier,&lt;/li&gt;
&lt;li&gt;or something unrelated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An AST gives that text &lt;strong&gt;context&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of searching for the characters &lt;code&gt;OrdersController&lt;/code&gt;, I can search for a node whose type is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class_declaration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and then ask Tree-sitter for its &lt;code&gt;name&lt;/code&gt; field.&lt;/p&gt;

&lt;p&gt;My first structural extraction ended up producing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ type: 'class_declaration', name: 'OrdersController' }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I then inspected the surrounding decorator nodes and extracted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ name: 'Controller', arguments: [ "'orders'" ] }
{ name: 'UseGuards', arguments: [ 'AuthGuard' ] }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we're no longer dealing with source code as text.&lt;/p&gt;

&lt;p&gt;We're turning code into &lt;strong&gt;facts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Conceptually, the pipeline is becoming:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TypeScript source
      ↓
Tree-sitter
      ↓
Syntax tree
      ↓
Structural extraction
      ↓
Facts about the code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That opens up much more interesting possibilities.&lt;/p&gt;

&lt;p&gt;For example, an analyzer could eventually answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which classes are NestJS controllers?&lt;/li&gt;
&lt;li&gt;Which routes use guards?&lt;/li&gt;
&lt;li&gt;Which controllers are missing authorization guards?&lt;/li&gt;
&lt;li&gt;Which services does a controller depend on?&lt;/li&gt;
&lt;li&gt;How are modules connected?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One important limitation
&lt;/h2&gt;

&lt;p&gt;Tree-sitter understands &lt;strong&gt;syntax&lt;/strong&gt;, not application semantics.&lt;/p&gt;

&lt;p&gt;If I write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;SomethingThatDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tree-sitter can still correctly recognize it as a decorator and function call.&lt;/p&gt;

&lt;p&gt;It does not know whether that decorator exists in NestJS or whether my application will actually compile.&lt;/p&gt;

&lt;p&gt;That separation was useful to understand:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parsing tells me what the code structurally is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Other layers — the TypeScript compiler, framework knowledge, static analysis rules — determine what that structure actually means.&lt;/p&gt;

&lt;p&gt;This was only a small experiment, but I now understand why Tree-sitter is used as a foundation for editors, code navigation tools, and static analyzers.&lt;/p&gt;

&lt;p&gt;Tomorrow, I'll move from inspecting the AST manually to extracting &lt;strong&gt;imports, classes, and decorators into normalized facts&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That should be the first real step toward turning this experiment into a small TypeScript code radar.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>algorithms</category>
      <category>nestjs</category>
      <category>staticanalysis</category>
    </item>
  </channel>
</rss>
