@@ -7,6 +7,7 @@ import { expect, test } from 'bun:test'
77import { ComponentLensAnalyzer , type ScopeConfig } from '../src/analyzer'
88import {
99 createDiskSignature ,
10+ createOpenSignature ,
1011 ImportResolver ,
1112 type SourceHost ,
1213} from '../src/resolver'
@@ -1688,6 +1689,197 @@ test('codelens scope tracks source file paths for imports', async () => {
16881689 }
16891690} )
16901691
1692+ test ( 'does not color type references inside inline object types' , async ( ) => {
1693+ const project = createProject ( {
1694+ 'Button.tsx' : [
1695+ "'use client';" ,
1696+ '' ,
1697+ 'interface ButtonProps {' ,
1698+ ' label: string;' ,
1699+ '}' ,
1700+ '' ,
1701+ 'function Button(props: { children: ReactNode }) {' ,
1702+ ' return <button />;' ,
1703+ '}' ,
1704+ '' ,
1705+ 'function IconButton(props: ButtonProps) {' ,
1706+ ' return <button />;' ,
1707+ '}' ,
1708+ ] . join ( '\n' ) ,
1709+ } )
1710+
1711+ try {
1712+ const analyzer = createAnalyzer ( project . host )
1713+ const filePath = project . filePath ( 'Button.tsx' )
1714+ const source = project . readFile ( 'Button.tsx' )
1715+ const scope : ScopeConfig = {
1716+ declaration : false ,
1717+ element : false ,
1718+ export : false ,
1719+ import : false ,
1720+ type : true ,
1721+ }
1722+ const usages = await analyzer . analyzeDocument (
1723+ filePath ,
1724+ source ,
1725+ project . signature ( 'Button.tsx' ) ,
1726+ scope ,
1727+ )
1728+
1729+ expect ( usages . map ( ( u ) => u . tagName ) ) . toEqual ( [ 'ButtonProps' , 'ButtonProps' ] )
1730+ } finally {
1731+ project [ Symbol . dispose ] ( )
1732+ }
1733+ } )
1734+
1735+ test ( 'colors named type reference but not inline object type members' , async ( ) => {
1736+ const project = createProject ( {
1737+ 'Card.tsx' : [
1738+ 'interface CardProps {' ,
1739+ ' title: string;' ,
1740+ '}' ,
1741+ '' ,
1742+ 'function Card({ title }: CardProps) {' ,
1743+ ' return <div>{title}</div>;' ,
1744+ '}' ,
1745+ '' ,
1746+ 'function Badge(props: { icon: ReactElement, label: string }) {' ,
1747+ ' return <span />;' ,
1748+ '}' ,
1749+ ] . join ( '\n' ) ,
1750+ } )
1751+
1752+ try {
1753+ const analyzer = createAnalyzer ( project . host )
1754+ const filePath = project . filePath ( 'Card.tsx' )
1755+ const source = project . readFile ( 'Card.tsx' )
1756+ const scope : ScopeConfig = {
1757+ declaration : false ,
1758+ element : false ,
1759+ export : false ,
1760+ import : false ,
1761+ type : true ,
1762+ }
1763+ const usages = await analyzer . analyzeDocument (
1764+ filePath ,
1765+ source ,
1766+ project . signature ( 'Card.tsx' ) ,
1767+ scope ,
1768+ )
1769+
1770+ expect ( usages . map ( ( u ) => u . tagName ) ) . toEqual ( [ 'CardProps' , 'CardProps' ] )
1771+ } finally {
1772+ project [ Symbol . dispose ] ( )
1773+ }
1774+ } )
1775+
1776+ test ( 'findComponentDeclaration returns position for existing component' , async ( ) => {
1777+ const project = createProject ( {
1778+ 'Card.tsx' : [
1779+ "'use client';" ,
1780+ '' ,
1781+ 'export function Card() {' ,
1782+ ' return <div />;' ,
1783+ '}' ,
1784+ ] . join ( '\n' ) ,
1785+ } )
1786+
1787+ try {
1788+ const analyzer = createAnalyzer ( project . host )
1789+ const filePath = project . filePath ( 'Card.tsx' )
1790+
1791+ const result = await analyzer . findComponentDeclaration ( filePath , 'Card' )
1792+ expect ( result ) . toEqual ( { line : 2 , character : 16 } )
1793+ } finally {
1794+ project [ Symbol . dispose ] ( )
1795+ }
1796+ } )
1797+
1798+ test ( 'findComponentDeclaration returns undefined for non-existent file' , async ( ) => {
1799+ const project = createProject ( {
1800+ 'Card.tsx' : [ 'export function Card() {' , ' return <div />;' , '}' ] . join (
1801+ '\n' ,
1802+ ) ,
1803+ } )
1804+
1805+ try {
1806+ const analyzer = createAnalyzer ( project . host )
1807+ const result = await analyzer . findComponentDeclaration (
1808+ project . filePath ( 'Missing.tsx' ) ,
1809+ 'Card' ,
1810+ )
1811+ expect ( result ) . toBeUndefined ( )
1812+ } finally {
1813+ project [ Symbol . dispose ] ( )
1814+ }
1815+ } )
1816+
1817+ test ( 'findComponentDeclaration returns undefined for unknown component name' , async ( ) => {
1818+ const project = createProject ( {
1819+ 'Card.tsx' : [ 'export function Card() {' , ' return <div />;' , '}' ] . join (
1820+ '\n' ,
1821+ ) ,
1822+ } )
1823+
1824+ try {
1825+ const analyzer = createAnalyzer ( project . host )
1826+ const filePath = project . filePath ( 'Card.tsx' )
1827+
1828+ const result = await analyzer . findComponentDeclaration (
1829+ filePath ,
1830+ 'NonExistent' ,
1831+ )
1832+ expect ( result ) . toBeUndefined ( )
1833+ } finally {
1834+ project [ Symbol . dispose ] ( )
1835+ }
1836+ } )
1837+
1838+ test ( 'findComponentDeclaration returns undefined when signature is unavailable' , async ( ) => {
1839+ const project = createProject ( {
1840+ 'Card.tsx' : [ 'export function Card() {' , ' return <div />;' , '}' ] . join (
1841+ '\n' ,
1842+ ) ,
1843+ } )
1844+
1845+ try {
1846+ const host : SourceHost = {
1847+ fileExists : project . host . fileExists ,
1848+ getSignature : ( ) => undefined ,
1849+ readFile : project . host . readFile ,
1850+ }
1851+ const analyzer = createAnalyzer ( host )
1852+ const result = await analyzer . findComponentDeclaration (
1853+ project . filePath ( 'Card.tsx' ) ,
1854+ 'Card' ,
1855+ )
1856+ expect ( result ) . toBeUndefined ( )
1857+ } finally {
1858+ project [ Symbol . dispose ] ( )
1859+ }
1860+ } )
1861+
1862+ test ( 'findComponentDeclaration locates component on first line' , async ( ) => {
1863+ const project = createProject ( {
1864+ 'Hero.tsx' : [ 'function Hero() {' , ' return <div />;' , '}' ] . join ( '\n' ) ,
1865+ } )
1866+
1867+ try {
1868+ const analyzer = createAnalyzer ( project . host )
1869+ const filePath = project . filePath ( 'Hero.tsx' )
1870+
1871+ const result = await analyzer . findComponentDeclaration ( filePath , 'Hero' )
1872+ expect ( result ) . toEqual ( { line : 0 , character : 9 } )
1873+ } finally {
1874+ project [ Symbol . dispose ] ( )
1875+ }
1876+ } )
1877+
1878+ test ( 'createOpenSignature formats version string' , ( ) => {
1879+ expect ( createOpenSignature ( 42 ) ) . toBe ( 'open:42' )
1880+ expect ( createOpenSignature ( 0 ) ) . toBe ( 'open:0' )
1881+ } )
1882+
16911883function createAnalyzer ( host : SourceHost ) : ComponentLensAnalyzer {
16921884 const resolver = new ImportResolver ( host )
16931885 return new ComponentLensAnalyzer ( host , resolver )
0 commit comments