Merge PR #708: TypeScript interface/enum/type-alias/const/new_expression extraction

This commit is contained in:
Safi
2026-05-07 11:07:24 +01:00
3 changed files with 95 additions and 8 deletions
+1
View File
@@ -4,6 +4,7 @@ Full release notes with details on each version: [GitHub Releases](https://githu
## 0.7.9 (unreleased)
- Feat: TypeScript extraction parity -- interface, enum, type alias, and module-level const nodes extracted; new_expression emits calls edges; parity with Java/C# class_types
- Feat: Quarto (`.qmd`) file support -- routed through existing Markdown extractor; Quarto executable code blocks (` ```{python} `) extracted as code nodes
- Feat: optional Google Workspace shortcut export for headless extraction -- `graphify extract ./docs --google-workspace` converts `.gdoc`, `.gsheet`, and `.gslides` files into Markdown sidecars with the `gws` CLI before semantic extraction; account email pseudonymized via SHA256 hash; `[google]` extra adds Sheets table rendering support
- Feat: AWS Bedrock backend -- `graphify extract ./docs --backend bedrock`; credentials via standard AWS provider chain (AWS_PROFILE, AWS_REGION, IAM roles, SSO); model via GRAPHIFY_BEDROCK_MODEL (default anthropic.claude-3-5-sonnet-20241022-v2:0); `[bedrock]` extra adds boto3
+25 -8
View File
@@ -778,16 +778,14 @@ def _require_imports_js(node, source: bytes, file_nid: str, stem: str, edges: li
def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
nodes: list, edges: list, seen_ids: set, function_bodies: list,
parent_class_nid: str | None, add_node_fn, add_edge_fn) -> bool:
"""Handle lexical_declaration (arrow functions, CJS requires) for JS/TS.
Returns True if handled (caller should not descend further).
"""
"""Handle lexical_declaration (arrow functions, CJS requires, module-level const literals) for JS/TS. Returns True if handled."""
if node.type in ("lexical_declaration", "variable_declaration"):
# CJS require imports — emit edges, do not block other lexical_declaration handling
require_found = _require_imports_js(node, source, file_nid, stem, edges, str_path)
# Arrow function declarations (existing behavior, lexical_declaration only)
# Arrow function declarations and module-level const literals (lexical_declaration only)
arrow_found = False
const_found = False
if node.type == "lexical_declaration":
for child in node.children:
if child.type == "variable_declarator":
@@ -804,8 +802,22 @@ def _js_extra_walk(node, source: bytes, file_nid: str, stem: str, str_path: str,
if body:
function_bodies.append((func_nid, body))
arrow_found = True
elif value and value.type in (
"object", "array", "as_expression", "call_expression", "new_expression",
):
# Module-level const with literal/object/array/factory value
name_node = child.child_by_field_name("name")
if name_node:
const_name = _read_text(name_node, source)
line = child.start_point[0] + 1
const_nid = _make_id(stem, const_name)
add_node_fn(const_nid, const_name, line)
add_edge_fn(file_nid, const_nid, "contains", line)
const_found = True
if arrow_found:
return True
if const_found:
return True
if require_found:
return True
return False
@@ -872,7 +884,7 @@ _JS_CONFIG = LanguageConfig(
class_types=frozenset({"class_declaration"}),
function_types=frozenset({"function_declaration", "method_definition"}),
import_types=frozenset({"import_statement"}),
call_types=frozenset({"call_expression"}),
call_types=frozenset({"call_expression", "new_expression"}),
call_function_field="function",
call_accessor_node_types=frozenset({"member_expression"}),
call_accessor_field="property",
@@ -883,10 +895,15 @@ _JS_CONFIG = LanguageConfig(
_TS_CONFIG = LanguageConfig(
ts_module="tree_sitter_typescript",
ts_language_fn="language_typescript",
class_types=frozenset({"class_declaration"}),
class_types=frozenset({
"class_declaration",
"interface_declaration", # parity with Java/C#
"enum_declaration", # named enums
"type_alias_declaration", # named type aliases
}),
function_types=frozenset({"function_declaration", "method_definition"}),
import_types=frozenset({"import_statement"}),
call_types=frozenset({"call_expression"}),
call_types=frozenset({"call_expression", "new_expression"}),
call_function_field="function",
call_accessor_node_types=frozenset({"member_expression"}),
call_accessor_field="property",
+69
View File
@@ -0,0 +1,69 @@
// Test fixture for upstream PR — exercises every new extraction path.
//
// Expected nodes after this PR:
// - IUserRepository (interface)
// - UserStatus (enum) + Active, Inactive (members)
// - UserId (type_alias)
// - USER_REPOSITORY (const, value=call_expression)
// - DEFAULT_ROLES (const, value=array)
// - USER_CONFIG (const, value=object)
// - UserService (class — already extracted by current code)
// - UserModule (class — already extracted)
//
// Expected edges after this PR:
// - UserService.create() --instantiates--> User
// - UserService.bulkCreate() --instantiates--> Array
// - UserModule --provides--> UserService
// - UserModule --provides--> USER_REPOSITORY (via { provide, useClass } detection — optional)
// - UserModule --exports--> UserService
import { Module, Injectable } from '@nestjs/common';
import type { User } from './user.entity';
export interface IUserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<void>;
}
export enum UserStatus {
Active = 'ACTIVE',
Inactive = 'INACTIVE',
Suspended = 'SUSPENDED',
}
export type UserId = string;
export const USER_REPOSITORY = Symbol('USER_REPOSITORY');
export const DEFAULT_ROLES = ['admin', 'editor', 'user'] as const;
export const USER_CONFIG = {
maxRetries: 3,
timeoutMs: 5000,
features: {
twoFactor: true,
sso: false,
},
};
@Injectable()
export class UserService {
constructor(private repo: IUserRepository) {}
create(name: string): User {
return new User(name);
}
bulkCreate(names: string[]): User[] {
return names.map((n) => new User(n));
}
}
@Module({
providers: [
UserService,
{ provide: USER_REPOSITORY, useClass: PrismaUserRepository },
],
exports: [UserService],
})
export class UserModule {}