|
|
@@ -1,903 +1,119 @@
|
|
|
-// npx jest src/services/tree-sitter/__tests__/parseSourceCodeDefinitions.tsx.test.ts
|
|
|
-
|
|
|
-import * as fs from "fs/promises"
|
|
|
-import * as path from "path"
|
|
|
-
|
|
|
-import { describe, expect, it, jest, beforeEach } from "@jest/globals"
|
|
|
-
|
|
|
-import { initializeTreeSitter, testParseSourceCodeDefinitions, debugLog } from "./helpers"
|
|
|
-
|
|
|
-// Sample component content with enhanced TypeScript language constructs
|
|
|
-const sampleTsxContent = `
|
|
|
-// Original components
|
|
|
-interface VSCodeCheckboxProps {
|
|
|
- checked: boolean
|
|
|
- onChange: (checked: boolean) => void
|
|
|
- label?: string
|
|
|
- disabled?: boolean
|
|
|
-}
|
|
|
-
|
|
|
-export const VSCodeCheckbox: React.FC<VSCodeCheckboxProps> = ({
|
|
|
- checked,
|
|
|
- onChange,
|
|
|
- label,
|
|
|
- disabled
|
|
|
-}) => {
|
|
|
- return <div>Checkbox</div>
|
|
|
-}
|
|
|
-
|
|
|
-interface TemperatureControlProps {
|
|
|
- isCustomTemperature: boolean
|
|
|
- setIsCustomTemperature: (value: boolean) => void
|
|
|
- inputValue: number | null
|
|
|
- setInputValue: (value: number | null) => void
|
|
|
- value?: number
|
|
|
- maxValue: number
|
|
|
-}
|
|
|
-
|
|
|
-const TemperatureControl = ({
|
|
|
- isCustomTemperature,
|
|
|
- setIsCustomTemperature,
|
|
|
- inputValue,
|
|
|
- setInputValue,
|
|
|
- value,
|
|
|
- maxValue
|
|
|
-}: TemperatureControlProps) => {
|
|
|
- return (
|
|
|
- <>
|
|
|
- <VSCodeCheckbox
|
|
|
- checked={isCustomTemperature}
|
|
|
- onChange={(e) => {
|
|
|
- setIsCustomTemperature(e.target.checked)
|
|
|
- if (!e.target.checked) {
|
|
|
- setInputValue(null)
|
|
|
- } else {
|
|
|
- setInputValue(value ?? 0)
|
|
|
- }
|
|
|
- }}>
|
|
|
- <label>Use Custom Temperature</label>
|
|
|
- </VSCodeCheckbox>
|
|
|
-
|
|
|
- <Slider
|
|
|
- min={0}
|
|
|
- max={maxValue}
|
|
|
- value={[inputValue ?? 0]}
|
|
|
- onValueChange={([value]) => setInputValue(value)}
|
|
|
- />
|
|
|
- </>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-// Utility Types
|
|
|
-type User = {
|
|
|
- id: string;
|
|
|
- username: string;
|
|
|
- password: string;
|
|
|
- email: string;
|
|
|
-}
|
|
|
-
|
|
|
-// Partial - Makes all properties optional
|
|
|
-type PartialUser = Partial<User>;
|
|
|
-
|
|
|
-// Required - Makes all properties required
|
|
|
-type RequiredConfig = Required<{theme?: string, showHeader?: boolean}>;
|
|
|
-
|
|
|
-// Readonly - Makes all properties readonly
|
|
|
-type ReadonlyState = Readonly<{count: number, status: string}>;
|
|
|
-
|
|
|
-// Function Overloads
|
|
|
-function process(value: string): string;
|
|
|
-function process(value: number): number;
|
|
|
-function process(value: boolean): boolean;
|
|
|
-function process(value: any): any {
|
|
|
- return value;
|
|
|
-}
|
|
|
-
|
|
|
-// Async Function
|
|
|
-async function fetchData(url: string): Promise<Response> {
|
|
|
- const response = await fetch(url);
|
|
|
- return response;
|
|
|
-}
|
|
|
-
|
|
|
-// Async Arrow Function
|
|
|
-const fetchUser = async (id: string): Promise<User> => {
|
|
|
- const response = await fetch(\`/api/users/\${id}\`);
|
|
|
- return response.json();
|
|
|
-};
|
|
|
-
|
|
|
-// Class with Members and Properties
|
|
|
-class AdvancedComponent {
|
|
|
- // Public property
|
|
|
- public name: string;
|
|
|
-
|
|
|
- // Private property
|
|
|
- private _count: number = 0;
|
|
|
-
|
|
|
- // Protected property
|
|
|
- protected status: 'active' | 'inactive' = 'active';
|
|
|
-
|
|
|
- // Readonly property
|
|
|
- readonly id: string;
|
|
|
-
|
|
|
- // Static property
|
|
|
- static defaultProps = {
|
|
|
- theme: 'light',
|
|
|
- showHeader: true
|
|
|
- };
|
|
|
-
|
|
|
- // Constructor
|
|
|
- constructor(name: string, id: string) {
|
|
|
- this.name = name;
|
|
|
- this.id = id;
|
|
|
- }
|
|
|
-
|
|
|
- // Getter method
|
|
|
- get count(): number {
|
|
|
- return this._count;
|
|
|
- }
|
|
|
-
|
|
|
- // Setter method
|
|
|
- set count(value: number) {
|
|
|
- if (value >= 0) {
|
|
|
- this._count = value;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Public method
|
|
|
- public updateName(newName: string): void {
|
|
|
- this.name = newName;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// React Hooks and Context
|
|
|
-import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
|
-
|
|
|
-// Create a context
|
|
|
-const ThemeContext = createContext({
|
|
|
- theme: 'light',
|
|
|
- toggleTheme: () => {}
|
|
|
-});
|
|
|
-
|
|
|
-// Context provider and consumer
|
|
|
-const ThemeProvider = ThemeContext.Provider;
|
|
|
-const ThemeConsumer = ThemeContext.Consumer;
|
|
|
-
|
|
|
-// Custom hook using context
|
|
|
-function useTheme() {
|
|
|
- const context = useContext(ThemeContext);
|
|
|
- if (!context) {
|
|
|
- throw new Error('useTheme must be used within a ThemeProvider');
|
|
|
- }
|
|
|
- return context;
|
|
|
-}
|
|
|
-
|
|
|
-// Component using hooks
|
|
|
-function ThemeToggler() {
|
|
|
- // useState hook
|
|
|
- const [theme, setTheme] = useState('light');
|
|
|
-
|
|
|
- // useEffect hook
|
|
|
- useEffect(() => {
|
|
|
- document.body.dataset.theme = theme;
|
|
|
- return () => {
|
|
|
- delete document.body.dataset.theme;
|
|
|
- };
|
|
|
- }, [theme]);
|
|
|
-
|
|
|
- return (
|
|
|
- <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
|
|
|
- Switch to {theme === 'light' ? 'dark' : 'light'} theme
|
|
|
- </button>
|
|
|
- );
|
|
|
-}
|
|
|
-
|
|
|
-// Decorator Example
|
|
|
-@Component({
|
|
|
- selector: 'app-root',
|
|
|
- template: '<div>App Component</div>'
|
|
|
-})
|
|
|
-class AppComponent {
|
|
|
- title = 'My App';
|
|
|
-
|
|
|
- @Input()
|
|
|
- data: string[] = [];
|
|
|
-}
|
|
|
-
|
|
|
-// Enum Declaration
|
|
|
-enum LogLevel {
|
|
|
- Error = 1,
|
|
|
- Warning = 2,
|
|
|
- Info = 3,
|
|
|
- Debug = 4
|
|
|
-}
|
|
|
-
|
|
|
-// Namespace Declaration
|
|
|
-namespace Validation {
|
|
|
- export function isValidEmail(email: string): boolean {
|
|
|
- return email.includes('@');
|
|
|
- }
|
|
|
-
|
|
|
- export function isValidPhone(phone: string): boolean {
|
|
|
- return phone.length >= 10;
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// Complex Nested Components and Member Expressions
|
|
|
-export const ComplexComponent = () => {
|
|
|
- return (
|
|
|
- <CustomHeader
|
|
|
- title="Test"
|
|
|
- subtitle={
|
|
|
- <span className="text-gray-500">
|
|
|
- Nested <strong>content</strong>
|
|
|
- </span>
|
|
|
- }
|
|
|
- />
|
|
|
- );
|
|
|
-};
|
|
|
-
|
|
|
-export const NestedSelectors = () => (
|
|
|
- <section>
|
|
|
- <Select.Option>
|
|
|
- <Group.Item>
|
|
|
- <Text.Body>Deeply nested</Text.Body>
|
|
|
- </Group.Item>
|
|
|
- </Select.Option>
|
|
|
- </section>
|
|
|
-);
|
|
|
-
|
|
|
-// Template Literal Types
|
|
|
-type EventName<T extends string> = \`on\${Capitalize<T>}\`;
|
|
|
-type CSSProperty<T extends string> = \`--\${T}\` | \`-webkit-\${T}\` | \`-moz-\${T}\` | \`-ms-\${T}\`;
|
|
|
-type RouteParams<T extends string> = T extends \`\${string}:\${infer Param}/\${infer Rest}\`
|
|
|
- ? { [K in Param | keyof RouteParams<Rest>]: string }
|
|
|
- : T extends \`\${string}:\${infer Param}\`
|
|
|
- ? { [K in Param]: string }
|
|
|
- : {};
|
|
|
-
|
|
|
-// Conditional Types
|
|
|
-type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
|
-type Parameters<T> = T extends (...args: infer P) => any ? P : never;
|
|
|
-type InstanceType<T> = T extends new (...args: any[]) => infer R ? R : never;
|
|
|
-type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
|
|
|
-
|
|
|
-// Generic Components with Constraints
|
|
|
-type ComplexProps<T> = {
|
|
|
- data: T[];
|
|
|
- render: (item: T) => React.ReactNode;
|
|
|
-};
|
|
|
-
|
|
|
-export const GenericList = <T extends { id: string }>({
|
|
|
- data,
|
|
|
- render
|
|
|
-}: ComplexProps<T>) => (
|
|
|
- <div>
|
|
|
- {data.map(item => render(item))}
|
|
|
- </div>
|
|
|
-);
|
|
|
-
|
|
|
-export const ConditionalComponent = ({ condition }) =>
|
|
|
- condition ? (
|
|
|
- <PrimaryContent>
|
|
|
- <h1>Main Content</h1>
|
|
|
- </PrimaryContent>
|
|
|
- ) : (
|
|
|
- <FallbackContent />
|
|
|
- );
|
|
|
-
|
|
|
-// Dictionary Interface with Constrained Key Types
|
|
|
-interface Dictionary<K extends string | number, V> {
|
|
|
- get(key: K): V | undefined;
|
|
|
- set(key: K, value: V): void;
|
|
|
- has(key: K): boolean;
|
|
|
-}
|
|
|
-
|
|
|
-type KeyValuePair<K extends string | number, V> = {
|
|
|
- key: K;
|
|
|
- value: V;
|
|
|
-};
|
|
|
-`
|
|
|
-
|
|
|
-// We'll use the debug test to test the parser directly
|
|
|
-
|
|
|
-// Mock file system operations
|
|
|
-jest.mock("fs/promises")
|
|
|
-const mockedFs = jest.mocked(fs)
|
|
|
-
|
|
|
-// Mock fileExistsAtPath to return true for our test paths
|
|
|
-jest.mock("../../../utils/fs", () => ({
|
|
|
- fileExistsAtPath: jest.fn().mockImplementation(() => Promise.resolve(true)),
|
|
|
-}))
|
|
|
-
|
|
|
-// Mock loadRequiredLanguageParsers
|
|
|
-// Mock the loadRequiredLanguageParsers function
|
|
|
-jest.mock("../languageParser", () => ({
|
|
|
- loadRequiredLanguageParsers: jest.fn(),
|
|
|
-}))
|
|
|
-
|
|
|
-// Sample component content is imported from helpers.ts
|
|
|
-
|
|
|
-// Add a test that uses the real parser with a debug approach
|
|
|
-// This test MUST run before tests to trigger initializeTreeSitter
|
|
|
-describe("treeParserDebug", () => {
|
|
|
- // Run this test to debug tree-sitter parsing
|
|
|
- it("should debug tree-sitter parsing directly using example from debug-tsx-tree.js", async () => {
|
|
|
- jest.unmock("fs/promises")
|
|
|
-
|
|
|
- // Initialize tree-sitter
|
|
|
- const TreeSitter = await initializeTreeSitter()
|
|
|
-
|
|
|
- // Create test file content
|
|
|
- const sampleCode = sampleTsxContent
|
|
|
-
|
|
|
- // Create parser and query
|
|
|
- const parser = new TreeSitter()
|
|
|
- const wasmPath = path.join(process.cwd(), "dist/tree-sitter-tsx.wasm")
|
|
|
- const tsxLang = await TreeSitter.Language.load(wasmPath)
|
|
|
- parser.setLanguage(tsxLang)
|
|
|
- const tree = parser.parse(sampleCode)
|
|
|
- // console.log("Parsed tree:", tree.rootNode.toString())
|
|
|
+/*
|
|
|
+TODO: The following structures can be parsed by tree-sitter but lack query support:
|
|
|
+
|
|
|
+1. React Hooks:
|
|
|
+ (call_expression
|
|
|
+ function: (member_expression
|
|
|
+ object: (identifier) @react
|
|
|
+ property: [(property_identifier) @hook_name]))
|
|
|
+ - Affects useState, useEffect, useRef, useCallback, useMemo
|
|
|
+ - Currently visible in parse tests but no query patterns exist
|
|
|
+
|
|
|
+2. Context Providers/Consumers:
|
|
|
+ (jsx_element
|
|
|
+ open_tag: (jsx_opening_element
|
|
|
+ name: (member_expression
|
|
|
+ object: (identifier) @context
|
|
|
+ property: [(property_identifier) @provider
|
|
|
+ (property_identifier) @consumer])))
|
|
|
+ - Can be parsed as JSX elements but no specific capture patterns
|
|
|
+
|
|
|
+3. React Event Handlers:
|
|
|
+ (arrow_function
|
|
|
+ parameters: (formal_parameters
|
|
|
+ (required_parameter
|
|
|
+ pattern: (identifier)
|
|
|
+ type: (type_annotation
|
|
|
+ (generic_type
|
|
|
+ name: (nested_type_identifier
|
|
|
+ module: (identifier) @react
|
|
|
+ name: (type_identifier) @event_type)))))
|
|
|
+ - Parsed but no specific patterns for React synthetic events
|
|
|
+*/
|
|
|
+
|
|
|
+import { describe, expect, it, jest, beforeAll } from "@jest/globals"
|
|
|
+import { testParseSourceCodeDefinitions, mockedFs } from "./helpers"
|
|
|
+import sampleTsxContent from "./fixtures/sample-tsx"
|
|
|
+
|
|
|
+describe("parseSourceCodeDefinitionsForFile with TSX", () => {
|
|
|
+ // Cache test results at the top of the describe block
|
|
|
+ let result: string
|
|
|
+
|
|
|
+ beforeAll(async () => {
|
|
|
+ // Set up mock for file system operations
|
|
|
+ jest.mock("fs/promises")
|
|
|
+ mockedFs.readFile.mockResolvedValue(Buffer.from(sampleTsxContent))
|
|
|
|
|
|
- expect(tree).toBeDefined()
|
|
|
+ // Cache the parse result for use in all tests
|
|
|
+ const parseResult = await testParseSourceCodeDefinitions("test.tsx", sampleTsxContent, {
|
|
|
+ language: "tsx",
|
|
|
+ wasmFile: "tree-sitter-tsx.wasm",
|
|
|
+ })
|
|
|
+ expect(parseResult).toBeDefined()
|
|
|
+ expect(typeof parseResult).toBe("string")
|
|
|
+ result = parseResult as string
|
|
|
})
|
|
|
|
|
|
- it("should successfully parse basic components", async function () {
|
|
|
- const testFile = "/test/components.tsx"
|
|
|
- const result = await testParseSourceCodeDefinitions(testFile, sampleTsxContent)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("# components.tsx")
|
|
|
- expect(result).toContain("export const VSCodeCheckbox: React.FC<VSCodeCheckboxProps>")
|
|
|
- expect(result).toContain("const TemperatureControl")
|
|
|
+ // Type Definition Tests
|
|
|
+ it("should capture interface declarations", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*interface StandardInterfaceProps/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*interface PropsDefinitionExample/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*interface ClassComponentState/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*interface GenericComponentProps<T>/)
|
|
|
})
|
|
|
|
|
|
- it("should detect complex nested components and member expressions", async function () {
|
|
|
- const complexContent = `
|
|
|
- export const ComplexComponent = () => {
|
|
|
- return (
|
|
|
- <CustomHeader
|
|
|
- title="Test"
|
|
|
- subtitle={
|
|
|
- <span className="text-gray-500">
|
|
|
- Nested <strong>content</strong>
|
|
|
- </span>
|
|
|
- }
|
|
|
- />
|
|
|
- );
|
|
|
- };
|
|
|
-
|
|
|
- export const NestedSelectors = () => (
|
|
|
- <section>
|
|
|
- <Select.Option>
|
|
|
- <Group.Item>
|
|
|
- <Text.Body>Deeply nested</Text.Body>
|
|
|
- </Group.Item>
|
|
|
- </Select.Option>
|
|
|
- </section>
|
|
|
- );
|
|
|
- `
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/complex.tsx", complexContent)
|
|
|
-
|
|
|
- // Check component declarations - these are the only ones reliably detected
|
|
|
- expect(result).toContain("ComplexComponent")
|
|
|
- expect(result).toContain("NestedSelectors")
|
|
|
-
|
|
|
- // The current implementation doesn't reliably detect JSX usage
|
|
|
- // These tests are commented out until the implementation is improved
|
|
|
- // expect(result).toContain("CustomHeader")
|
|
|
- // expect(result).toMatch(/Select\.Option|Option/)
|
|
|
- // expect(result).toMatch(/Group\.Item|Item/)
|
|
|
- // expect(result).toMatch(/Text\.Body|Body/)
|
|
|
+ it("should capture type alias declarations", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*type StandardTypeAlias/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*type UserType/)
|
|
|
})
|
|
|
|
|
|
- it("should parse decorators with arguments", async function () {
|
|
|
- const decoratorContent = `
|
|
|
- /**
|
|
|
- * Component decorator with configuration
|
|
|
- * Defines a web component with template and styling
|
|
|
- * @decorator
|
|
|
- */
|
|
|
- @Component({
|
|
|
- selector: 'app-user-profile',
|
|
|
- templateUrl: './user-profile.component.html',
|
|
|
- styleUrls: [
|
|
|
- './user-profile.component.css',
|
|
|
- './user-profile.theme.css'
|
|
|
- ],
|
|
|
- providers: [
|
|
|
- UserService,
|
|
|
- { provide: ErrorHandler, useClass: CustomErrorHandler }
|
|
|
- ]
|
|
|
- })
|
|
|
- export class UserProfileComponent {
|
|
|
- // Add more lines to ensure it meets MIN_COMPONENT_LINES requirement
|
|
|
- private name: string;
|
|
|
- private age: number;
|
|
|
-
|
|
|
- constructor() {
|
|
|
- this.name = 'Default User';
|
|
|
- this.age = 30;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Get user information
|
|
|
- * @returns User info as string
|
|
|
- */
|
|
|
- getUserInfo(): string {
|
|
|
- return "Name: " + this.name + ", Age: " + this.age;
|
|
|
- }
|
|
|
- }
|
|
|
- `
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(decoratorContent))
|
|
|
-
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/decorator.tsx", decoratorContent)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("@Component")
|
|
|
- expect(result).toContain("UserProfileComponent")
|
|
|
+ // Component Definition Tests
|
|
|
+ it("should capture function component declarations", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*function StandardFunctionComponent/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*function GenericListComponent<T>/)
|
|
|
})
|
|
|
-})
|
|
|
-
|
|
|
-it("should parse template literal types", async function () {
|
|
|
- const templateLiteralTypeContent = `
|
|
|
- /**
|
|
|
- * EventName type for DOM events
|
|
|
- * Creates a union type of all possible event names with 'on' prefix
|
|
|
- * Used for strongly typing event handlers
|
|
|
- * @template T - Base event name
|
|
|
- */
|
|
|
- type EventName<T extends string> = \`on\${Capitalize<T>}\`;
|
|
|
-
|
|
|
- /**
|
|
|
- * CSS Property type using template literals
|
|
|
- * Creates property names for CSS-in-JS libraries
|
|
|
- * Combines prefixes with property names
|
|
|
- * @template T - Base property name
|
|
|
- */
|
|
|
- type CSSProperty<T extends string> = \`--\${T}\` | \`-webkit-\${T}\` | \`-moz-\${T}\` | \`-ms-\${T}\`;
|
|
|
-
|
|
|
- /**
|
|
|
- * Route parameter extraction type
|
|
|
- * Extracts named parameters from URL patterns
|
|
|
- * Used in routing libraries for type-safe route parameters
|
|
|
- * @template T - Route pattern with parameters
|
|
|
- */
|
|
|
- type RouteParams<T extends string> = T extends \`\${string}:\${infer Param}/\${infer Rest}\`
|
|
|
- ? { [K in Param | keyof RouteParams<Rest>]: string }
|
|
|
- : T extends \`\${string}:\${infer Param}\`
|
|
|
- ? { [K in Param]: string }
|
|
|
- : {};
|
|
|
-
|
|
|
- /**
|
|
|
- * String manipulation utility types
|
|
|
- * Various template literal types for string operations
|
|
|
- * @template T - Input string type
|
|
|
- */
|
|
|
- type StringOps<T extends string> = {
|
|
|
- Uppercase: Uppercase<T>;
|
|
|
- Lowercase: Lowercase<T>;
|
|
|
- Capitalize: Capitalize<T>;
|
|
|
- Uncapitalize: Uncapitalize<T>;
|
|
|
- };
|
|
|
- `
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(templateLiteralTypeContent))
|
|
|
-
|
|
|
- // Run the test to see if template literal types are already supported
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/template-literal-type.tsx", templateLiteralTypeContent)
|
|
|
- debugLog("Template literal type parsing result:", result)
|
|
|
-
|
|
|
- // Check if the result contains the type declarations
|
|
|
- expect(result).toBeDefined()
|
|
|
-
|
|
|
- // The test shows that template literal types are already partially supported
|
|
|
- // We can see that RouteParams and StringOps are being captured
|
|
|
- expect(result).toContain("RouteParams<T")
|
|
|
- expect(result).toContain("StringOps<T")
|
|
|
-
|
|
|
- debugLog("Template literal types are already partially supported by the parser!")
|
|
|
-
|
|
|
- // Note: EventName and CSSProperty types aren't fully captured in the output,
|
|
|
- // but this is likely due to the minimum line requirement (MIN_COMPONENT_LINES = 4)
|
|
|
- // as mentioned in the task description (index.ts requires blocks to have at least 5 lines)
|
|
|
-})
|
|
|
-
|
|
|
-it("should parse conditional types", async function () {
|
|
|
- const conditionalTypeContent = `
|
|
|
- /**
|
|
|
- * Extract return type from function type
|
|
|
- * Uses conditional types to determine the return type of a function
|
|
|
- * @template T - Function type to extract return type from
|
|
|
- */
|
|
|
- type ReturnType<T> = T extends
|
|
|
- // Function type with any arguments
|
|
|
- (...args: any[]) =>
|
|
|
- // Using infer to capture the return type
|
|
|
- infer R
|
|
|
- // If the condition is true, return the inferred type
|
|
|
- ? R
|
|
|
- // Otherwise return never
|
|
|
- : never;
|
|
|
-
|
|
|
- /**
|
|
|
- * Extract parameter types from function type
|
|
|
- * Uses conditional types to determine the parameter types of a function
|
|
|
- * @template T - Function type to extract parameter types from
|
|
|
- */
|
|
|
- type Parameters<T> = T extends
|
|
|
- // Function type with inferred parameters
|
|
|
- (...args: infer P) =>
|
|
|
- // Any return type
|
|
|
- any
|
|
|
- // If the condition is true, return the parameter types
|
|
|
- ? P
|
|
|
- // Otherwise return never
|
|
|
- : never;
|
|
|
-
|
|
|
- /**
|
|
|
- * Extract instance type from constructor type
|
|
|
- * Uses conditional types to determine what type a constructor creates
|
|
|
- * @template T - Constructor type to extract instance type from
|
|
|
- */
|
|
|
- type InstanceType<T> = T extends
|
|
|
- // Constructor type with any arguments
|
|
|
- new (...args: any[]) =>
|
|
|
- // Using infer to capture the instance type
|
|
|
- infer R
|
|
|
- // If the condition is true, return the inferred type
|
|
|
- ? R
|
|
|
- // Otherwise return never
|
|
|
- : never;
|
|
|
-
|
|
|
- /**
|
|
|
- * Determine if a type is a function
|
|
|
- * Uses conditional types to check if a type is callable
|
|
|
- * @template T - Type to check
|
|
|
- */
|
|
|
- type IsFunction<T> = T extends
|
|
|
- // Function type with any arguments and return type
|
|
|
- (...args: any[]) =>
|
|
|
- any
|
|
|
- // If the condition is true, return true
|
|
|
- ? true
|
|
|
- // Otherwise return false
|
|
|
- : false;
|
|
|
- `
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(conditionalTypeContent))
|
|
|
-
|
|
|
- // First run without adding the query pattern to see if it's already implemented
|
|
|
- const initialResult = await testParseSourceCodeDefinitions("/test/conditional-type.tsx", conditionalTypeContent)
|
|
|
- debugLog("Initial result before adding query pattern:", initialResult)
|
|
|
-
|
|
|
- // Save the initial line count to compare later
|
|
|
- const initialLineCount = initialResult ? initialResult.split("\n").length : 0
|
|
|
|
|
|
- // Now check if the new query pattern improves the output
|
|
|
- const updatedResult = await testParseSourceCodeDefinitions("/test/conditional-type.tsx", conditionalTypeContent)
|
|
|
- debugLog("Updated result after adding query pattern:", updatedResult)
|
|
|
-
|
|
|
- // Compare results
|
|
|
- const updatedLineCount = updatedResult ? updatedResult.split("\n").length : 0
|
|
|
- expect(updatedResult).toBeDefined()
|
|
|
-
|
|
|
- // Check if the feature is already implemented
|
|
|
- if (initialResult && initialResult.includes("ReturnType<T>") && initialResult.includes("Parameters<T>")) {
|
|
|
- debugLog("Conditional types are already supported by the parser!")
|
|
|
- // If the feature is already implemented, we don't need to check if the updated result is better
|
|
|
- expect(true).toBe(true)
|
|
|
- } else {
|
|
|
- // If the feature wasn't already implemented, check if our changes improved it
|
|
|
- expect(updatedLineCount).toBeGreaterThan(initialLineCount)
|
|
|
- expect(updatedResult).toContain("ReturnType<T>")
|
|
|
- expect(updatedResult).toContain("Parameters<T>")
|
|
|
- }
|
|
|
-})
|
|
|
-
|
|
|
-it("should detect TypeScript interfaces and HOCs", async function () {
|
|
|
- const tsContent = `
|
|
|
- interface Props {
|
|
|
- title: string;
|
|
|
- items: Array<{
|
|
|
- id: number;
|
|
|
- label: string;
|
|
|
- }>;
|
|
|
- }
|
|
|
-
|
|
|
- const withLogger = <P extends object>(
|
|
|
- WrappedComponent: React.ComponentType<P>
|
|
|
- ) => {
|
|
|
- return class WithLogger extends React.Component<P> {
|
|
|
- render() {
|
|
|
- return <WrappedComponent {...this.props} />;
|
|
|
- }
|
|
|
- };
|
|
|
- };
|
|
|
-
|
|
|
- export const EnhancedComponent = withLogger(BaseComponent);
|
|
|
- `
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/hoc.tsx", tsContent)
|
|
|
-
|
|
|
- // Check interface and type definitions - these are reliably detected
|
|
|
- expect(result).toContain("Props")
|
|
|
- expect(result).toContain("withLogger")
|
|
|
-
|
|
|
- // The current implementation doesn't reliably detect class components in HOCs
|
|
|
- // These tests are commented out until the implementation is improved
|
|
|
- // expect(result).toMatch(/WithLogger|WrappedComponent/)
|
|
|
- // expect(result).toContain("EnhancedComponent")
|
|
|
- // expect(result).toMatch(/React\.Component|Component/)
|
|
|
-})
|
|
|
-
|
|
|
-it("should detect wrapped components with any wrapper function", async function () {
|
|
|
- const wrappedContent = `
|
|
|
- // Custom component wrapper
|
|
|
- const withLogger = (Component) => (props) => {
|
|
|
- console.log('Rendering:', props)
|
|
|
- return <Component {...props} />
|
|
|
- }
|
|
|
-
|
|
|
- // Component with multiple wrappers including React utilities
|
|
|
- export const MemoInput = React.memo(
|
|
|
- React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
- (props, ref) => (
|
|
|
- <input ref={ref} {...props} />
|
|
|
- )
|
|
|
- )
|
|
|
- );
|
|
|
-
|
|
|
- // Custom HOC
|
|
|
- export const EnhancedButton = withLogger(
|
|
|
- ({ children, ...props }) => (
|
|
|
- <button {...props}>
|
|
|
- {children}
|
|
|
- </button>
|
|
|
- )
|
|
|
- );
|
|
|
-
|
|
|
- // Another custom wrapper
|
|
|
- const withTheme = (Component) => (props) => {
|
|
|
- const theme = useTheme()
|
|
|
- return <Component {...props} theme={theme} />
|
|
|
- }
|
|
|
-
|
|
|
- // Multiple custom wrappers
|
|
|
- export const ThemedButton = withTheme(
|
|
|
- withLogger(
|
|
|
- ({ theme, children, ...props }) => (
|
|
|
- <button style={{ color: theme.primary }} {...props}>
|
|
|
- {children}
|
|
|
- </button>
|
|
|
- )
|
|
|
- )
|
|
|
- );
|
|
|
- `
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/wrapped.tsx", wrappedContent)
|
|
|
-
|
|
|
- // Should detect all component definitions regardless of wrapper
|
|
|
- expect(result).toContain("MemoInput")
|
|
|
- expect(result).toContain("EnhancedButton")
|
|
|
- expect(result).toContain("ThemedButton")
|
|
|
- expect(result).toContain("withLogger")
|
|
|
- expect(result).toContain("withTheme")
|
|
|
-
|
|
|
- // Also check that we get some output
|
|
|
- expect(result).toBeDefined()
|
|
|
-})
|
|
|
-
|
|
|
-it("should handle conditional and generic components", async function () {
|
|
|
- const genericContent = `
|
|
|
- type ComplexProps<T> = {
|
|
|
- data: T[];
|
|
|
- render: (item: T) => React.ReactNode;
|
|
|
- };
|
|
|
-
|
|
|
- export const GenericList = <T extends { id: string }>({
|
|
|
- data,
|
|
|
- render
|
|
|
- }: ComplexProps<T>) => (
|
|
|
- <div>
|
|
|
- {data.map(item => render(item))}
|
|
|
- </div>
|
|
|
- );
|
|
|
-
|
|
|
- export const ConditionalComponent = ({ condition }) =>
|
|
|
- condition ? (
|
|
|
- <PrimaryContent>
|
|
|
- <h1>Main Content</h1>
|
|
|
- </PrimaryContent>
|
|
|
- ) : (
|
|
|
- <FallbackContent />
|
|
|
- );
|
|
|
- `
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/generic.tsx", genericContent)
|
|
|
-
|
|
|
- // Check type and component declarations - these are reliably detected
|
|
|
- expect(result).toContain("ComplexProps")
|
|
|
- expect(result).toContain("GenericList")
|
|
|
- expect(result).toContain("ConditionalComponent")
|
|
|
-
|
|
|
- // The current implementation doesn't reliably detect components in conditional expressions
|
|
|
- // These tests are commented out until the implementation is improved
|
|
|
- // expect(result).toMatch(/PrimaryContent|Primary/)
|
|
|
- // expect(result).toMatch(/FallbackContent|Fallback/)
|
|
|
-
|
|
|
- // Check standard HTML elements (should not be captured)
|
|
|
- expect(result).not.toContain("div")
|
|
|
- expect(result).not.toContain("h1")
|
|
|
-})
|
|
|
-
|
|
|
-it("should parse switch/case statements", async function () {
|
|
|
- const switchCaseContent = `
|
|
|
- function handleTemperature(value: number) {
|
|
|
- switch (value) {
|
|
|
- case 0:
|
|
|
- // Handle freezing temperature
|
|
|
- logTemperature("Freezing");
|
|
|
- updateDisplay("Ice warning");
|
|
|
- notifyUser("Cold weather alert");
|
|
|
- setHeating(true);
|
|
|
- return "Freezing";
|
|
|
-
|
|
|
- case 25:
|
|
|
- // Handle room temperature
|
|
|
- logTemperature("Normal");
|
|
|
- updateComfortMetrics();
|
|
|
- setHeating(false);
|
|
|
- setCooling(false);
|
|
|
- return "Room temperature";
|
|
|
-
|
|
|
- default:
|
|
|
- // Handle unknown temperature
|
|
|
- logTemperature("Unknown");
|
|
|
- runDiagnostics();
|
|
|
- checkSensors();
|
|
|
- updateSystemStatus();
|
|
|
- return "Unknown temperature";
|
|
|
- }
|
|
|
- }
|
|
|
- `
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(switchCaseContent))
|
|
|
-
|
|
|
- // Inspect the tree structure to see the actual node names
|
|
|
- // await inspectTreeStructure(switchCaseContent)
|
|
|
-
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/switch-case.tsx", switchCaseContent)
|
|
|
- debugLog("Switch Case Test Result:", result)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("handleTemperature")
|
|
|
- // Check for case statements in the output
|
|
|
- expect(result).toContain("case 0:")
|
|
|
- expect(result).toContain("case 25:")
|
|
|
-})
|
|
|
-
|
|
|
-it("should parse namespace declarations", async function () {
|
|
|
- const namespaceContent = `
|
|
|
- /**
|
|
|
- * Validation namespace containing various validation functions
|
|
|
- * @namespace
|
|
|
- * @description Contains reusable validation logic
|
|
|
- */
|
|
|
- namespace Validation {
|
|
|
- /**
|
|
|
- * Validates email addresses according to RFC 5322
|
|
|
- * @param email - The email address to validate
|
|
|
- * @returns boolean indicating if the email is valid
|
|
|
- */
|
|
|
- export function isValidEmail(email: string): boolean {
|
|
|
- // Email validation logic
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Validates phone numbers in international format
|
|
|
- * @param phone - The phone number to validate
|
|
|
- * @returns boolean indicating if the phone number is valid
|
|
|
- */
|
|
|
- export function isValidPhone(phone: string): boolean {
|
|
|
- // Phone validation logic
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
- `
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(namespaceContent))
|
|
|
-
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/namespace.tsx", namespaceContent)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("namespace Validation")
|
|
|
- expect(result).toContain("isValidEmail")
|
|
|
- expect(result).toContain("isValidPhone")
|
|
|
-})
|
|
|
-
|
|
|
-it("should parse generic type declarations with constraints", async function () {
|
|
|
- const genericTypeContent = `
|
|
|
- /**
|
|
|
- * Dictionary interface with constrained key types
|
|
|
- */
|
|
|
- interface Dictionary<K extends string | number, V> {
|
|
|
- /**
|
|
|
- * Gets a value by its key
|
|
|
- * @param key - The key to look up
|
|
|
- * @returns The value associated with the key, or undefined
|
|
|
- */
|
|
|
- get(key: K): V | undefined;
|
|
|
-
|
|
|
- /**
|
|
|
- * Sets a value for a key
|
|
|
- * @param key - The key to set
|
|
|
- * @param value - The value to associate with the key
|
|
|
- */
|
|
|
- set(key: K, value: V): void;
|
|
|
-
|
|
|
- /**
|
|
|
- * Checks if the dictionary contains a key
|
|
|
- * @param key - The key to check
|
|
|
- */
|
|
|
- has(key: K): boolean;
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * Type alias with constrained generic parameters
|
|
|
- */
|
|
|
- type KeyValuePair<K extends string | number, V> = {
|
|
|
- key: K;
|
|
|
- value: V;
|
|
|
- }
|
|
|
- `
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(genericTypeContent))
|
|
|
-
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/generic-type.tsx", genericTypeContent)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("interface Dictionary<K extends string | number, V>")
|
|
|
- expect(result).toContain("type KeyValuePair<K extends string | number, V>")
|
|
|
-})
|
|
|
-
|
|
|
-describe("parseSourceCodeDefinitions", () => {
|
|
|
- const testFilePath = "/test/TemperatureControl.tsx"
|
|
|
-
|
|
|
- beforeEach(() => {
|
|
|
- // Reset mocks
|
|
|
- jest.clearAllMocks()
|
|
|
+ it("should capture arrow function components", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*export const ArrowFunctionComponent/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const JSXElementsExample/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const EventHandlersComponent/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const HooksStateComponent/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const HooksUsageComponent/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const GenericComponentUsage/)
|
|
|
+ })
|
|
|
|
|
|
- // Mock file existence check
|
|
|
- mockedFs.access.mockResolvedValue(undefined)
|
|
|
+ it("should capture class components", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*class StandardClassComponent extends React.Component/)
|
|
|
+ })
|
|
|
|
|
|
- // Mock file reading
|
|
|
- mockedFs.readFile.mockResolvedValue(Buffer.from(sampleTsxContent))
|
|
|
+ it("should capture higher order components", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*function withLogging<P extends object>/)
|
|
|
})
|
|
|
|
|
|
- it("should parse interface definitions", async function () {
|
|
|
- const result = await testParseSourceCodeDefinitions(testFilePath, sampleTsxContent)
|
|
|
- expect(result).toContain("interface VSCodeCheckboxProps")
|
|
|
+ // JSX Elements Tests
|
|
|
+ it("should capture JSX elements", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*<div className="jsx-elements-container">/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*<Input/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*<UI.Button/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*<StandardFunctionComponent/)
|
|
|
})
|
|
|
|
|
|
- // Tests for parsing functionality with tree-sitter
|
|
|
- it("should parse React component definitions", async function () {
|
|
|
- const result = await testParseSourceCodeDefinitions(testFilePath, sampleTsxContent)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("VSCodeCheckbox")
|
|
|
- expect(result).toContain("VSCodeCheckboxProps")
|
|
|
+ it("should capture React hooks usage", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const \[data, setData\] = React\.useState/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const counter = React\.useRef/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*React\.useEffect\(\(\)/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const fetchData = React\.useCallback/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const memoizedValue = React\.useMemo/)
|
|
|
})
|
|
|
|
|
|
- it("should parse enum declarations", async function () {
|
|
|
- const enumContent = `
|
|
|
- /**
|
|
|
- * Log levels for application logging
|
|
|
- * Used throughout the application to control log output
|
|
|
- * @enum {number}
|
|
|
- */
|
|
|
- enum LogLevel {
|
|
|
- /** Critical errors that need immediate attention */
|
|
|
- Error = 1,
|
|
|
- /** Warning messages for potential issues */
|
|
|
- Warning = 2,
|
|
|
- /** Informational messages about normal operation */
|
|
|
- Info = 3,
|
|
|
- /** Detailed debug information */
|
|
|
- Debug = 4
|
|
|
- }
|
|
|
- `
|
|
|
+ it("should capture event handlers", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const handleClick =/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const handleChange =/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*const handleSubmit =/)
|
|
|
+ })
|
|
|
|
|
|
- const result = await testParseSourceCodeDefinitions("/test/enums.tsx", enumContent)
|
|
|
- expect(result).toBeDefined()
|
|
|
- expect(result).toContain("LogLevel")
|
|
|
- // Test that the enum name is captured
|
|
|
- expect(result).toContain("enum LogLevel")
|
|
|
+ it("should capture generic component declarations", () => {
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*function GenericListComponent<T>/)
|
|
|
+ expect(result).toMatch(/\d+--\d+ \|\s*interface GenericComponentProps<T>/)
|
|
|
})
|
|
|
})
|