diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5ec36b6..3bfe8f1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,10 +18,10 @@ jobs: name: test runs-on: ubuntu-latest steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: '1.3.9' - run: bun install --frozen-lockfile - run: bun run lint - run: bun run typecheck @@ -40,7 +40,7 @@ jobs: needs: - test steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: changepacks/action@main id: changepacks with: @@ -58,11 +58,11 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v6 - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: '1.3.9' - run: bun install --frozen-lockfile @@ -82,10 +82,10 @@ jobs: needs: - changepacks steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - uses: oven-sh/setup-bun@v2 with: - bun-version: latest + bun-version: '1.3.9' - run: bun install --frozen-lockfile - name: Publish to VS Code Marketplace run: bunx @vscode/vsce publish --no-dependencies diff --git a/src/analyzer.ts b/src/analyzer.ts index bacc2cf..472cfa8 100644 --- a/src/analyzer.ts +++ b/src/analyzer.ts @@ -726,6 +726,7 @@ const SK_NamespaceImport = ts.SyntaxKind.NamespaceImport const SK_NamedExports = ts.SyntaxKind.NamedExports const SK_ExportKw = ts.SyntaxKind.ExportKeyword const SK_DefaultKw = ts.SyntaxKind.DefaultKeyword +const SK_TypeLiteral = ts.SyntaxKind.TypeLiteral function isComponentIdentifier(name: string): boolean { const code = name.charCodeAt(0) @@ -829,6 +830,7 @@ function collectSourceElements( let currentComponent: string | undefined let currentComponentTracked = false + let typeLiteralDepth = 0 const visit = (node: ts.Node): void => { const nodeKind = node.kind @@ -841,6 +843,9 @@ function collectSourceElements( return } + const isTypeLiteral = nodeKind === SK_TypeLiteral + if (isTypeLiteral) typeLiteralDepth++ + const entry = componentByPos.get(node.pos) const entered = entry !== undefined && entry.end === node.end @@ -869,7 +874,7 @@ function collectSourceElements( if (jsxTag) { jsxTags.push(jsxTag) } - } else if (nodeKind === SK_TypeReference) { + } else if (nodeKind === SK_TypeReference && typeLiteralDepth === 0) { const typeName = (node as ts.TypeReferenceNode).typeName if ( typeName.kind === SK_Identifier && @@ -937,6 +942,8 @@ function collectSourceElements( ts.forEachChild(node, visit) + if (isTypeLiteral) typeLiteralDepth-- + if (entered) { currentComponent = savedComponent currentComponentTracked = savedTracked diff --git a/test/analyzer.test.ts b/test/analyzer.test.ts index 926454c..b52cea8 100644 --- a/test/analyzer.test.ts +++ b/test/analyzer.test.ts @@ -7,6 +7,7 @@ import { expect, test } from 'bun:test' import { ComponentLensAnalyzer, type ScopeConfig } from '../src/analyzer' import { createDiskSignature, + createOpenSignature, ImportResolver, type SourceHost, } from '../src/resolver' @@ -1688,6 +1689,197 @@ test('codelens scope tracks source file paths for imports', async () => { } }) +test('does not color type references inside inline object types', async () => { + const project = createProject({ + 'Button.tsx': [ + "'use client';", + '', + 'interface ButtonProps {', + ' label: string;', + '}', + '', + 'function Button(props: { children: ReactNode }) {', + ' return ;', + '}', + '', + 'function IconButton(props: ButtonProps) {', + ' return ;', + '}', + ].join('\n'), + }) + + try { + const analyzer = createAnalyzer(project.host) + const filePath = project.filePath('Button.tsx') + const source = project.readFile('Button.tsx') + const scope: ScopeConfig = { + declaration: false, + element: false, + export: false, + import: false, + type: true, + } + const usages = await analyzer.analyzeDocument( + filePath, + source, + project.signature('Button.tsx'), + scope, + ) + + expect(usages.map((u) => u.tagName)).toEqual(['ButtonProps', 'ButtonProps']) + } finally { + project[Symbol.dispose]() + } +}) + +test('colors named type reference but not inline object type members', async () => { + const project = createProject({ + 'Card.tsx': [ + 'interface CardProps {', + ' title: string;', + '}', + '', + 'function Card({ title }: CardProps) {', + ' return