Skip to content

Commit 3b2f61b

Browse files
author
Eric Wheeler
committed
feat: add decorator pattern support to tree-sitter TypeScript parser
- Added support for parsing complex decorators with arguments - Added test case for Component decorator pattern - Enhanced TypeScript queries to capture decorator definitions Signed-off-by: Eric Wheeler <[email protected]>
1 parent 4c37421 commit 3b2f61b

File tree

2 files changed

+122
-64
lines changed

2 files changed

+122
-64
lines changed

src/services/tree-sitter/__tests__/parseSourceCodeDefinitions.test.ts

Lines changed: 110 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ describe("treeParserDebug", () => {
226226
const tsxLang = await TreeSitter.Language.load(wasmPath)
227227
parser.setLanguage(tsxLang)
228228
const tree = parser.parse(sampleCode)
229-
console.log("Parsed tree:", tree.rootNode.toString())
229+
// console.log("Parsed tree:", tree.rootNode.toString())
230230

231231
// Extract definitions using TSX query
232232
const query = tsxLang.query(tsxQuery)
@@ -282,8 +282,55 @@ describe("treeParserDebug", () => {
282282
// expect(result).toMatch(/Text\.Body|Body/)
283283
})
284284

285-
it("should detect TypeScript interfaces and HOCs", async function () {
286-
const tsContent = `
285+
it("should parse decorators with arguments", async function () {
286+
const decoratorContent = `
287+
/**
288+
* Component decorator with configuration
289+
* Defines a web component with template and styling
290+
* @decorator
291+
*/
292+
@Component({
293+
selector: 'app-user-profile',
294+
templateUrl: './user-profile.component.html',
295+
styleUrls: [
296+
'./user-profile.component.css',
297+
'./user-profile.theme.css'
298+
],
299+
providers: [
300+
UserService,
301+
{ provide: ErrorHandler, useClass: CustomErrorHandler }
302+
]
303+
})
304+
export class UserProfileComponent {
305+
// Add more lines to ensure it meets MIN_COMPONENT_LINES requirement
306+
private name: string;
307+
private age: number;
308+
309+
constructor() {
310+
this.name = 'Default User';
311+
this.age = 30;
312+
}
313+
314+
/**
315+
* Get user information
316+
* @returns User info as string
317+
*/
318+
getUserInfo(): string {
319+
return "Name: " + this.name + ", Age: " + this.age;
320+
}
321+
}
322+
`
323+
mockedFs.readFile.mockResolvedValue(Buffer.from(decoratorContent))
324+
325+
const result = await testParseSourceCodeDefinitions("/test/decorator.tsx", decoratorContent)
326+
expect(result).toBeDefined()
327+
expect(result).toContain("@Component")
328+
expect(result).toContain("UserProfileComponent")
329+
})
330+
})
331+
332+
it("should detect TypeScript interfaces and HOCs", async function () {
333+
const tsContent = `
287334
interface Props {
288335
title: string;
289336
items: Array<{
@@ -304,21 +351,21 @@ describe("treeParserDebug", () => {
304351
305352
export const EnhancedComponent = withLogger(BaseComponent);
306353
`
307-
const result = await testParseSourceCodeDefinitions("/test/hoc.tsx", tsContent)
354+
const result = await testParseSourceCodeDefinitions("/test/hoc.tsx", tsContent)
308355

309-
// Check interface and type definitions - these are reliably detected
310-
expect(result).toContain("Props")
311-
expect(result).toContain("withLogger")
356+
// Check interface and type definitions - these are reliably detected
357+
expect(result).toContain("Props")
358+
expect(result).toContain("withLogger")
312359

313-
// The current implementation doesn't reliably detect class components in HOCs
314-
// These tests are commented out until the implementation is improved
315-
// expect(result).toMatch(/WithLogger|WrappedComponent/)
316-
// expect(result).toContain("EnhancedComponent")
317-
// expect(result).toMatch(/React\.Component|Component/)
318-
})
360+
// The current implementation doesn't reliably detect class components in HOCs
361+
// These tests are commented out until the implementation is improved
362+
// expect(result).toMatch(/WithLogger|WrappedComponent/)
363+
// expect(result).toContain("EnhancedComponent")
364+
// expect(result).toMatch(/React\.Component|Component/)
365+
})
319366

320-
it("should detect wrapped components with any wrapper function", async function () {
321-
const wrappedContent = `
367+
it("should detect wrapped components with any wrapper function", async function () {
368+
const wrappedContent = `
322369
// Custom component wrapper
323370
const withLogger = (Component) => (props) => {
324371
console.log('Rendering:', props)
@@ -360,21 +407,21 @@ describe("treeParserDebug", () => {
360407
)
361408
);
362409
`
363-
const result = await testParseSourceCodeDefinitions("/test/wrapped.tsx", wrappedContent)
410+
const result = await testParseSourceCodeDefinitions("/test/wrapped.tsx", wrappedContent)
364411

365-
// Should detect all component definitions regardless of wrapper
366-
expect(result).toContain("MemoInput")
367-
expect(result).toContain("EnhancedButton")
368-
expect(result).toContain("ThemedButton")
369-
expect(result).toContain("withLogger")
370-
expect(result).toContain("withTheme")
412+
// Should detect all component definitions regardless of wrapper
413+
expect(result).toContain("MemoInput")
414+
expect(result).toContain("EnhancedButton")
415+
expect(result).toContain("ThemedButton")
416+
expect(result).toContain("withLogger")
417+
expect(result).toContain("withTheme")
371418

372-
// Also check that we get some output
373-
expect(result).toBeDefined()
374-
})
419+
// Also check that we get some output
420+
expect(result).toBeDefined()
421+
})
375422

376-
it("should handle conditional and generic components", async function () {
377-
const genericContent = `
423+
it("should handle conditional and generic components", async function () {
424+
const genericContent = `
378425
type ComplexProps<T> = {
379426
data: T[];
380427
render: (item: T) => React.ReactNode;
@@ -398,25 +445,25 @@ describe("treeParserDebug", () => {
398445
<FallbackContent />
399446
);
400447
`
401-
const result = await testParseSourceCodeDefinitions("/test/generic.tsx", genericContent)
448+
const result = await testParseSourceCodeDefinitions("/test/generic.tsx", genericContent)
402449

403-
// Check type and component declarations - these are reliably detected
404-
expect(result).toContain("ComplexProps")
405-
expect(result).toContain("GenericList")
406-
expect(result).toContain("ConditionalComponent")
450+
// Check type and component declarations - these are reliably detected
451+
expect(result).toContain("ComplexProps")
452+
expect(result).toContain("GenericList")
453+
expect(result).toContain("ConditionalComponent")
407454

408-
// The current implementation doesn't reliably detect components in conditional expressions
409-
// These tests are commented out until the implementation is improved
410-
// expect(result).toMatch(/PrimaryContent|Primary/)
411-
// expect(result).toMatch(/FallbackContent|Fallback/)
455+
// The current implementation doesn't reliably detect components in conditional expressions
456+
// These tests are commented out until the implementation is improved
457+
// expect(result).toMatch(/PrimaryContent|Primary/)
458+
// expect(result).toMatch(/FallbackContent|Fallback/)
412459

413-
// Check standard HTML elements (should not be captured)
414-
expect(result).not.toContain("div")
415-
expect(result).not.toContain("h1")
416-
})
460+
// Check standard HTML elements (should not be captured)
461+
expect(result).not.toContain("div")
462+
expect(result).not.toContain("h1")
463+
})
417464

418-
it("should parse switch/case statements", async function () {
419-
const switchCaseContent = `
465+
it("should parse switch/case statements", async function () {
466+
const switchCaseContent = `
420467
function handleTemperature(value: number) {
421468
switch (value) {
422469
case 0:
@@ -445,22 +492,22 @@ describe("treeParserDebug", () => {
445492
}
446493
}
447494
`
448-
mockedFs.readFile.mockResolvedValue(Buffer.from(switchCaseContent))
449-
450-
// Inspect the tree structure to see the actual node names
451-
// await inspectTreeStructure(switchCaseContent)
452-
453-
const result = await testParseSourceCodeDefinitions("/test/switch-case.tsx", switchCaseContent)
454-
console.log("Switch Case Test Result:", result)
455-
expect(result).toBeDefined()
456-
expect(result).toContain("handleTemperature")
457-
// Check for case statements in the output
458-
expect(result).toContain("case 0:")
459-
expect(result).toContain("case 25:")
460-
})
495+
mockedFs.readFile.mockResolvedValue(Buffer.from(switchCaseContent))
496+
497+
// Inspect the tree structure to see the actual node names
498+
// await inspectTreeStructure(switchCaseContent)
499+
500+
const result = await testParseSourceCodeDefinitions("/test/switch-case.tsx", switchCaseContent)
501+
console.log("Switch Case Test Result:", result)
502+
expect(result).toBeDefined()
503+
expect(result).toContain("handleTemperature")
504+
// Check for case statements in the output
505+
expect(result).toContain("case 0:")
506+
expect(result).toContain("case 25:")
507+
})
461508

462-
it("should parse namespace declarations", async function () {
463-
const namespaceContent = `
509+
it("should parse namespace declarations", async function () {
510+
const namespaceContent = `
464511
/**
465512
* Validation namespace containing various validation functions
466513
* @namespace
@@ -488,14 +535,13 @@ describe("treeParserDebug", () => {
488535
}
489536
}
490537
`
491-
mockedFs.readFile.mockResolvedValue(Buffer.from(namespaceContent))
538+
mockedFs.readFile.mockResolvedValue(Buffer.from(namespaceContent))
492539

493-
const result = await testParseSourceCodeDefinitions("/test/namespace.tsx", namespaceContent)
494-
expect(result).toBeDefined()
495-
expect(result).toContain("namespace Validation")
496-
expect(result).toContain("isValidEmail")
497-
expect(result).toContain("isValidPhone")
498-
})
540+
const result = await testParseSourceCodeDefinitions("/test/namespace.tsx", namespaceContent)
541+
expect(result).toBeDefined()
542+
expect(result).toContain("namespace Validation")
543+
expect(result).toContain("isValidEmail")
544+
expect(result).toContain("isValidPhone")
499545
})
500546

501547
describe("parseSourceCodeDefinitions", () => {

src/services/tree-sitter/queries/typescript.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,18 @@ export default `
6363
(enum_declaration
6464
name: (identifier) @name.definition.enum) @definition.enum
6565
66+
; Decorator definitions with decorated class
67+
(export_statement
68+
decorator: (decorator
69+
(call_expression
70+
function: (identifier) @name.definition.decorator))
71+
declaration: (class_declaration
72+
name: (type_identifier) @name.definition.decorated_class)) @definition.decorated_class
73+
74+
; Explicitly capture class name in decorated class
75+
(class_declaration
76+
name: (type_identifier) @name.definition.class) @definition.class
77+
6678
; Namespace declarations
6779
(internal_module
6880
name: (identifier) @name.definition.namespace) @definition.namespace

0 commit comments

Comments
 (0)