From 40a0cda2faf3b48ce21e93c06f919f85002c0979 Mon Sep 17 00:00:00 2001 From: Ido S Date: Sat, 9 May 2026 21:19:05 +0300 Subject: [PATCH] fixed suggestions for classnames that do not match a base ui component --- src/providers/completion.test.ts | 166 +++++++++++++++++++++++++++++++ src/providers/completion.ts | 10 +- 2 files changed, 171 insertions(+), 5 deletions(-) create mode 100644 src/providers/completion.test.ts diff --git a/src/providers/completion.test.ts b/src/providers/completion.test.ts new file mode 100644 index 0000000..93aedbb --- /dev/null +++ b/src/providers/completion.test.ts @@ -0,0 +1,166 @@ +import { describe, expect, it, vi } from 'vitest' +import type { IndexManager } from '../component-detection/index-manager' +import type { BaseUiData } from '../data/types' + +vi.mock('vscode', () => { + class Position { + constructor( + public line: number, + public character: number, + ) {} + translate(_lineDelta: number, charDelta: number) { + return new Position(this.line, this.character + charDelta) + } + } + + class Range { + constructor( + public start: Position, + public end: Position, + ) {} + } + + class CompletionItem { + label: string + kind: number + detail?: string + documentation?: unknown + sortText?: string + filterText?: string + insertText?: string + range?: Range + constructor(label: string, kind: number) { + this.label = label + this.kind = kind + } + } + + class MarkdownString { + value = '' + appendMarkdown(s: string) { + this.value += s + return this + } + } + return { + Position, + Range, + CompletionItem, + MarkdownString, + CompletionItemKind: { Property: 9, EnumMember: 11, Variable: 5 }, + } +}) + +import { BaseUiCompletionProvider } from './completion' + +const MOCK_DATA: BaseUiData = { + version: '1.0.0', + components: { + Dialog: { + attributes: [ + { name: 'data-open', values: [{ value: 'true' }, { value: 'false' }] }, + ], + cssVariables: [{ name: '--dialog-z-index' }], + attributesSourceFile: undefined, + cssVarsSourceFile: undefined, + }, + Tooltip: { + attributes: [ + { name: 'data-side', values: [{ value: 'top' }, { value: 'bottom' }] }, + ], + cssVariables: [{ name: '--tooltip-z-index' }], + attributesSourceFile: undefined, + cssVarsSourceFile: undefined, + }, + }, +} + +function makeProvider() { + return new BaseUiCompletionProvider(MOCK_DATA, {} as unknown as IndexManager) +} + +function fakePosition() { + return { + line: 0, + character: 10, + translate: (_lineDelta: number, charDelta: number) => ({ + line: 0, + character: 10 + charDelta, + }), + } as unknown as import('vscode').Position +} + +describe('attributeNameCompletions', () => { + it('returns all attributes when scopeComponents is null (no scope restriction)', () => { + const provider = makeProvider() + const items = (provider as any).attributeNameCompletions( + 'data-', + fakePosition(), + null, + ) + const names = items.map((i: { label: string }) => i.label) + expect(names).toContain('data-open') + expect(names).toContain('data-side') + }) + + it('returns no attributes when scopeComponents is empty (unknown class)', () => { + const provider = makeProvider() + const items = (provider as any).attributeNameCompletions( + 'data-', + fakePosition(), + [], + ) + expect(items).toHaveLength(0) + }) + + it('filters to matching component when scopeComponents is set', () => { + const provider = makeProvider() + const items = (provider as any).attributeNameCompletions( + 'data-', + fakePosition(), + ['Dialog'], + ) + const names = items.map((i: { label: string }) => i.label) + expect(names).toContain('data-open') + expect(names).not.toContain('data-side') + }) +}) + +describe('cssVariableCompletions', () => { + it('returns all css vars when scopeComponents is null (no scope restriction)', () => { + const provider = makeProvider() + const items = (provider as any).cssVariableCompletions( + '--', + fakePosition(), + null, + false, + ) + const names = items.map((i: { label: string }) => i.label) + expect(names).toContain('--dialog-z-index') + expect(names).toContain('--tooltip-z-index') + }) + + it('returns no css vars when scopeComponents is empty (unknown class)', () => { + const provider = makeProvider() + const items = (provider as any).cssVariableCompletions( + '--', + fakePosition(), + [], + false, + ) + expect(items).toHaveLength(0) + }) + + it('filters to matching component when scopeComponents is set', () => { + const provider = makeProvider() + const items = (provider as any).cssVariableCompletions( + '--', + fakePosition(), + ['Tooltip'], + false, + ) + const names = items.map((i: { label: string }) => i.label) + expect(names).toContain('--tooltip-z-index') + expect(names).not.toContain('--dialog-z-index') + }) +}) diff --git a/src/providers/completion.ts b/src/providers/completion.ts index 626bc99..891bbaf 100644 --- a/src/providers/completion.ts +++ b/src/providers/completion.ts @@ -83,7 +83,7 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider { `[base-ui] provideCompletionItems lang=${document.languageId} ctx=${JSON.stringify(ctx)}`, ) - let scopeComponents: string[] = [] + let scopeComponents: string[] | null = null if ( ctx.kind !== 'none' && ctx.selectorScope !== null && @@ -128,14 +128,14 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider { private attributeNameCompletions( prefix: string, position: vscode.Position, - scopeComponents: string[], + scopeComponents: string[] | null, ): vscode.CompletionItem[] { const range = this.prefixRange(prefix, position) return this.attributeIndex .filter((entry) => entry.attribute.name.startsWith(prefix)) .filter( (entry) => - scopeComponents.length === 0 || + scopeComponents === null || entry.components.some((c) => scopeComponents.includes(c)), ) .map((entry) => { @@ -181,7 +181,7 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider { private cssVariableCompletions( prefix: string, position: vscode.Position, - scopeComponents: string[], + scopeComponents: string[] | null, needsVarWrapper: boolean, ): vscode.CompletionItem[] { const range = this.prefixRange(prefix, position) @@ -189,7 +189,7 @@ export class BaseUiCompletionProvider implements vscode.CompletionItemProvider { .filter((entry) => entry.cssVar.name.startsWith(prefix)) .filter( (entry) => - scopeComponents.length === 0 || + scopeComponents === null || entry.components.some((c) => scopeComponents.includes(c)), ) .map((entry) => {