mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-29 06:32:28 +00:00
refactor: reduce explicit any types (#566)
* refactor: replace NodeProperties index signature any with unknown Change [key: string]: any to [key: string]: unknown in NodeProperties. Remove 19 redundant (node.properties as any) casts in csv-generator.ts — all accessed properties are already declared on the type. * refactor: replace any with SyntaxNode across ingestion layer Mechanical substitution — all tree-sitter AST node parameters and variables typed as any are now properly typed as SyntaxNode. - ast-helpers.ts: 13 any → SyntaxNode - parsing-processor.ts: 8 any → SyntaxNode - parse-worker.ts: 40 any → SyntaxNode/TreeSitterLanguage/Parser.Query - php.ts: 11 any → SyntaxNode Also adds TreeSitterLanguage type alias for optional grammar loading. * refactor: eliminate remaining any in ingestion layer - call-processor, call-routing, c-cpp: SyntaxNode substitutions - parse-worker: typed WorkerIncomingMessage discriminated union - worker-pool: typed WorkerOutgoingMessage + Error handler - ast-cache, import-processor: targeted cast for Tree.delete() - community-processor: graphology AbstractGraph types, LeidenModule interface for vendored leiden code Ingestion layer: 130 → 7 any warnings remaining.
This commit is contained in:
parent
6fb2d0ab34
commit
2ca7df642b
12 changed files with 185 additions and 145 deletions
|
|
@ -83,7 +83,7 @@ export type NodeProperties = {
|
|||
errorKeys?: string[];
|
||||
middleware?: string[];
|
||||
// Extensible
|
||||
[key: string]: any;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type RelationshipType =
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ export const createASTCache = (maxSize: number = 50): ASTCache => {
|
|||
try {
|
||||
// NOTE: web-tree-sitter has tree.delete(); native tree-sitter trees are GC-managed.
|
||||
// Keep this try/catch so we don't crash on either runtime.
|
||||
(tree as any).delete?.();
|
||||
(tree as unknown as { delete?: () => void }).delete?.();
|
||||
} catch (e) {
|
||||
console.warn('Failed to delete tree from WASM memory', e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ export function routeRubyCall(calledName: string, callNode: SyntaxNode): RubyCal
|
|||
// ── require / require_relative → import ─────────────────────────────────
|
||||
if (calledName === 'require' || calledName === 'require_relative') {
|
||||
const argList = callNode.childForFieldName?.('arguments');
|
||||
const stringNode = argList?.children?.find((c: any) => c.type === 'string');
|
||||
const contentNode = stringNode?.children?.find((c: any) => c.type === 'string_content');
|
||||
const stringNode = argList?.children?.find((c: SyntaxNode) => c.type === 'string');
|
||||
const contentNode = stringNode?.children?.find((c: SyntaxNode) => c.type === 'string_content');
|
||||
if (!contentNode) return SKIP_RESULT;
|
||||
|
||||
let importPath: string = contentNode.text;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
// (src/communities-leiden) because it was never published to npm.
|
||||
// We use createRequire to load the CommonJS vendored files in ESM context.
|
||||
import Graph from 'graphology';
|
||||
import type { AbstractGraph, Attributes } from 'graphology-types';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
|
|
@ -23,7 +24,22 @@ const __dirname = dirname(__filename);
|
|||
// Navigate to package root (works from both src/ and dist/)
|
||||
const leidenPath = resolve(__dirname, '..', '..', '..', 'vendor', 'leiden', 'index.cjs');
|
||||
const _require = createRequire(import.meta.url);
|
||||
const leiden = _require(leidenPath);
|
||||
/** Graphology Graph instance type (AbstractGraph from graphology-types avoids CJS/ESM interop namespace issue) */
|
||||
type GraphInstance = AbstractGraph<Attributes, Attributes, Attributes>;
|
||||
|
||||
const leiden: LeidenModule = _require(leidenPath);
|
||||
|
||||
/** Vendored Leiden algorithm module shape */
|
||||
interface LeidenModule {
|
||||
detailed: (graph: GraphInstance, options: Record<string, unknown>) => LeidenDetailedResult;
|
||||
}
|
||||
|
||||
/** Result returned by leiden.detailed() */
|
||||
interface LeidenDetailedResult {
|
||||
communities: Record<string, number>;
|
||||
count: number;
|
||||
modularity: number;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// TYPES
|
||||
|
|
@ -127,16 +143,16 @@ export const processCommunities = async (
|
|||
// The first 2 iterations capture ~95%+ of modularity; additional iterations have diminishing returns.
|
||||
// Timeout: abort after 60s for pathological graph structures.
|
||||
const LEIDEN_TIMEOUT_MS = 60_000;
|
||||
let details: any;
|
||||
let details: LeidenDetailedResult;
|
||||
try {
|
||||
details = await Promise.race([
|
||||
Promise.resolve(
|
||||
(leiden as any).detailed(graph, {
|
||||
leiden.detailed(graph, {
|
||||
resolution: isLarge ? 2.0 : 1.0,
|
||||
maxIterations: isLarge ? 3 : 0,
|
||||
}),
|
||||
),
|
||||
new Promise((_, reject) =>
|
||||
new Promise<never>((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Leiden timeout')), LEIDEN_TIMEOUT_MS),
|
||||
),
|
||||
]);
|
||||
|
|
@ -199,8 +215,12 @@ export const processCommunities = async (
|
|||
*/
|
||||
const MIN_CONFIDENCE_LARGE = 0.5;
|
||||
|
||||
const buildGraphologyGraph = (knowledgeGraph: KnowledgeGraph, isLarge: boolean): any => {
|
||||
const graph = new (Graph as any)({ type: 'undirected', allowSelfLoops: false });
|
||||
const buildGraphologyGraph = (knowledgeGraph: KnowledgeGraph, isLarge: boolean): GraphInstance => {
|
||||
const GraphCtor = Graph as unknown as new (options: {
|
||||
type: string;
|
||||
allowSelfLoops: boolean;
|
||||
}) => GraphInstance;
|
||||
const graph = new GraphCtor({ type: 'undirected', allowSelfLoops: false });
|
||||
|
||||
const symbolTypes = new Set<NodeLabel>(['Function', 'Class', 'Method', 'Interface']);
|
||||
const clusteringRelTypes = new Set(['CALLS', 'EXTENDS', 'IMPLEMENTS']);
|
||||
|
|
@ -257,7 +277,7 @@ const buildGraphologyGraph = (knowledgeGraph: KnowledgeGraph, isLarge: boolean):
|
|||
const createCommunityNodes = (
|
||||
communities: Record<string, number>,
|
||||
communityCount: number,
|
||||
graph: any,
|
||||
graph: GraphInstance,
|
||||
knowledgeGraph: KnowledgeGraph,
|
||||
): CommunityNode[] => {
|
||||
// Group node IDs by community
|
||||
|
|
@ -312,7 +332,7 @@ const createCommunityNodes = (
|
|||
const generateHeuristicLabel = (
|
||||
memberIds: string[],
|
||||
nodePathMap: Map<string, string>,
|
||||
graph: any,
|
||||
graph: GraphInstance,
|
||||
commNum: number,
|
||||
): string => {
|
||||
// Collect folder names from file paths
|
||||
|
|
@ -397,7 +417,7 @@ const findCommonPrefix = (strings: string[]): string => {
|
|||
* Estimate cohesion score (0-1) based on internal edge density.
|
||||
* Uses sampling for large communities to avoid O(N^2) cost.
|
||||
*/
|
||||
const calculateCohesion = (memberIds: string[], graph: any): number => {
|
||||
const calculateCohesion = (memberIds: string[], graph: GraphInstance): number => {
|
||||
if (memberIds.length <= 1) return 1.0;
|
||||
|
||||
const memberSet = new Set(memberIds);
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ export const processImports = async (
|
|||
console.groupEnd();
|
||||
}
|
||||
|
||||
if (wasReparsed) (tree as any).delete?.();
|
||||
if (wasReparsed) (tree as unknown as { delete?: () => void }).delete?.();
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { phpExportChecker } from '../export-detection.js';
|
|||
import { resolvePhpImport } from '../import-resolvers/php.js';
|
||||
import { extractPhpNamedBindings } from '../named-bindings/php.js';
|
||||
import { PHP_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { findDescendant, extractStringContent } from '../utils/ast-helpers.js';
|
||||
import { findDescendant, extractStringContent, type SyntaxNode } from '../utils/ast-helpers.js';
|
||||
import type { NodeLabel } from 'gitnexus-shared';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
import { phpConfig as phpFieldConfig } from '../field-extractors/configs/php.js';
|
||||
|
|
@ -128,7 +128,7 @@ const ELOQUENT_RELATIONS = new Set([
|
|||
* For a PHP property_declaration node, extract array values as a description string.
|
||||
* Returns null if not an Eloquent model property or no array values found.
|
||||
*/
|
||||
function extractPhpPropertyDescription(propName: string, propDeclNode: any): string | null {
|
||||
function extractPhpPropertyDescription(propName: string, propDeclNode: SyntaxNode): string | null {
|
||||
if (!ELOQUENT_ARRAY_PROPS.has(propName)) return null;
|
||||
|
||||
const arrayNode = findDescendant(propDeclNode, 'array_creation_expression');
|
||||
|
|
@ -138,7 +138,7 @@ function extractPhpPropertyDescription(propName: string, propDeclNode: any): str
|
|||
for (const child of arrayNode.children ?? []) {
|
||||
if (child.type !== 'array_element_initializer') continue;
|
||||
const children = child.children ?? [];
|
||||
const arrowIdx = children.findIndex((c: any) => c.type === '=>');
|
||||
const arrowIdx = children.findIndex((c: SyntaxNode) => c.type === '=>');
|
||||
if (arrowIdx !== -1) {
|
||||
const key = extractStringContent(children[arrowIdx - 1]);
|
||||
const val = extractStringContent(children[arrowIdx + 1]);
|
||||
|
|
@ -156,14 +156,14 @@ function extractPhpPropertyDescription(propName: string, propDeclNode: any): str
|
|||
* For a PHP method_declaration node, detect if it defines an Eloquent relationship.
|
||||
* Returns description like "hasMany(Post)" or null.
|
||||
*/
|
||||
function extractEloquentRelationDescription(methodNode: any): string | null {
|
||||
function findRelationCall(node: any): any {
|
||||
function extractEloquentRelationDescription(methodNode: SyntaxNode): string | null {
|
||||
function findRelationCall(node: SyntaxNode): SyntaxNode | null {
|
||||
if (node.type === 'member_call_expression') {
|
||||
const children = node.children ?? [];
|
||||
const objectNode = children.find(
|
||||
(c: any) => c.type === 'variable_name' && c.text === '$this',
|
||||
(c: SyntaxNode) => c.type === 'variable_name' && c.text === '$this',
|
||||
);
|
||||
const nameNode = children.find((c: any) => c.type === 'name');
|
||||
const nameNode = children.find((c: SyntaxNode) => c.type === 'name');
|
||||
if (objectNode && nameNode && ELOQUENT_RELATIONS.has(nameNode.text)) return node;
|
||||
}
|
||||
for (const child of node.children ?? []) {
|
||||
|
|
@ -176,17 +176,18 @@ function extractEloquentRelationDescription(methodNode: any): string | null {
|
|||
const callNode = findRelationCall(methodNode);
|
||||
if (!callNode) return null;
|
||||
|
||||
const relType = callNode.children?.find((c: any) => c.type === 'name')?.text;
|
||||
const argsNode = callNode.children?.find((c: any) => c.type === 'arguments');
|
||||
const relType = callNode.children?.find((c: SyntaxNode) => c.type === 'name')?.text;
|
||||
const argsNode = callNode.children?.find((c: SyntaxNode) => c.type === 'arguments');
|
||||
let targetModel: string | null = null;
|
||||
if (argsNode) {
|
||||
const firstArg = argsNode.children?.find((c: any) => c.type === 'argument');
|
||||
const firstArg = argsNode.children?.find((c: SyntaxNode) => c.type === 'argument');
|
||||
if (firstArg) {
|
||||
const classConstant = firstArg.children?.find(
|
||||
(c: any) => c.type === 'class_constant_access_expression',
|
||||
(c: SyntaxNode) => c.type === 'class_constant_access_expression',
|
||||
);
|
||||
if (classConstant) {
|
||||
targetModel = classConstant.children?.find((c: any) => c.type === 'name')?.text ?? null;
|
||||
targetModel =
|
||||
classConstant.children?.find((c: SyntaxNode) => c.type === 'name')?.text ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -203,7 +204,7 @@ function extractEloquentRelationDescription(methodNode: any): string | null {
|
|||
function phpDescriptionExtractor(
|
||||
nodeLabel: NodeLabel,
|
||||
nodeName: string,
|
||||
captureMap: Record<string, any>,
|
||||
captureMap: Record<string, SyntaxNode>,
|
||||
): string | undefined {
|
||||
if (nodeLabel === 'Property' && captureMap['definition.property']) {
|
||||
return extractPhpPropertyDescription(nodeName, captureMap['definition.property']) ?? undefined;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { GraphNode, GraphRelationship } from 'gitnexus-shared';
|
||||
import type { GraphNode, GraphRelationship, NodeLabel } from 'gitnexus-shared';
|
||||
import { KnowledgeGraph } from '../graph/types.js';
|
||||
import Parser from 'tree-sitter';
|
||||
import { loadParser, loadLanguage, isLanguageAvailable } from '../tree-sitter/parser-loader.js';
|
||||
|
|
@ -114,7 +114,7 @@ const processParsingWithWorkers = async (
|
|||
for (const node of result.nodes) {
|
||||
graph.addNode({
|
||||
id: node.id,
|
||||
label: node.label as any,
|
||||
label: node.label as NodeLabel,
|
||||
properties: node.properties,
|
||||
});
|
||||
}
|
||||
|
|
@ -184,10 +184,10 @@ const processParsingWithWorkers = async (
|
|||
|
||||
// Inline caches to avoid repeated parent-walks per node (same pattern as parse-worker.ts).
|
||||
// Keyed by tree-sitter node reference — cleared at the start of each file.
|
||||
const classIdCache = new Map<any, string | null>();
|
||||
const exportCache = new Map<any, boolean>();
|
||||
const classIdCache = new Map<SyntaxNode, string | null>();
|
||||
const exportCache = new Map<SyntaxNode, boolean>();
|
||||
|
||||
const cachedFindEnclosingClassId = (node: any, filePath: string): string | null => {
|
||||
const cachedFindEnclosingClassId = (node: SyntaxNode, filePath: string): string | null => {
|
||||
const cached = classIdCache.get(node);
|
||||
if (cached !== undefined) return cached;
|
||||
const result = findEnclosingClassId(node, filePath);
|
||||
|
|
@ -196,8 +196,8 @@ const cachedFindEnclosingClassId = (node: any, filePath: string): string | null
|
|||
};
|
||||
|
||||
const cachedExportCheck = (
|
||||
checker: (node: any, name: string) => boolean,
|
||||
node: any,
|
||||
checker: (node: SyntaxNode, name: string) => boolean,
|
||||
node: SyntaxNode,
|
||||
name: string,
|
||||
): boolean => {
|
||||
const cached = exportCache.get(node);
|
||||
|
|
@ -210,7 +210,7 @@ const cachedExportCheck = (
|
|||
// FieldExtractor cache for sequential path — same pattern as parse-worker.ts
|
||||
const seqFieldInfoCache = new Map<number, Map<string, FieldInfo>>();
|
||||
|
||||
function seqFindEnclosingClassNode(node: any): any | null {
|
||||
function seqFindEnclosingClassNode(node: SyntaxNode): SyntaxNode | null {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
if (CLASS_CONTAINER_TYPES.has(current.type)) return current;
|
||||
|
|
@ -221,11 +221,11 @@ function seqFindEnclosingClassNode(node: any): any | null {
|
|||
|
||||
/** Minimal no-op SymbolTable stub for FieldExtractorContext (sequential path has a real
|
||||
* SymbolTable, but it's incomplete at this stage — use the stub for safety). */
|
||||
const NOOP_SYMBOL_TABLE_SEQ: any = {
|
||||
const NOOP_SYMBOL_TABLE_SEQ = {
|
||||
lookupExactAll: () => [],
|
||||
lookupExact: () => undefined,
|
||||
lookupExactFull: () => undefined,
|
||||
};
|
||||
} as unknown as SymbolTable;
|
||||
|
||||
function seqGetFieldInfo(
|
||||
classNode: SyntaxNode,
|
||||
|
|
@ -321,7 +321,7 @@ const processParsingSequential = async (
|
|||
: null;
|
||||
|
||||
matches.forEach((match) => {
|
||||
const captureMap: Record<string, any> = {};
|
||||
const captureMap: Record<string, SyntaxNode> = {};
|
||||
|
||||
match.captures.forEach((c) => {
|
||||
captureMap[c.name] = c.node;
|
||||
|
|
@ -372,7 +372,7 @@ const processParsingSequential = async (
|
|||
|
||||
const node: GraphNode = {
|
||||
id: nodeId,
|
||||
label: nodeLabel as any,
|
||||
label: nodeLabel as NodeLabel,
|
||||
properties: {
|
||||
name: nodeName,
|
||||
filePath: file.path,
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@ const SMART_PTR_WRAPPERS = new Set(['shared_ptr', 'unique_ptr', 'weak_ptr']);
|
|||
* Unwraps type_descriptor wrappers common in tree-sitter-cpp ASTs.
|
||||
* Returns undefined if no template arguments or no type found. */
|
||||
export const extractFirstTemplateTypeArg = (parentNode: SyntaxNode): string | undefined => {
|
||||
const templateArgs = parentNode.children.find((c: any) => c.type === 'template_argument_list');
|
||||
const templateArgs = parentNode.children.find(
|
||||
(c: SyntaxNode) => c.type === 'template_argument_list',
|
||||
);
|
||||
if (!templateArgs?.firstNamedChild) return undefined;
|
||||
let argNode: any = templateArgs.firstNamedChild;
|
||||
let argNode: SyntaxNode | null = templateArgs.firstNamedChild;
|
||||
if (argNode.type === 'type_descriptor') {
|
||||
const inner = argNode.childForFieldName('type');
|
||||
if (inner) argNode = inner;
|
||||
|
|
@ -141,7 +143,7 @@ const extractInitializer: InitializerExtractor = (
|
|||
func.type === 'template_function'
|
||||
? func
|
||||
: func.type === 'qualified_identifier' || func.type === 'scoped_identifier'
|
||||
? (func.namedChildren.find((c: any) => c.type === 'template_function') ?? null)
|
||||
? (func.namedChildren.find((c: SyntaxNode) => c.type === 'template_function') ?? null)
|
||||
: null;
|
||||
if (templateFunc) {
|
||||
const nameNode = templateFunc.firstNamedChild;
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export const DEFINITION_CAPTURE_KEYS = [
|
|||
|
||||
/** Extract the definition node from a tree-sitter query capture map. */
|
||||
export const getDefinitionNodeFromCaptures = (
|
||||
captureMap: Record<string, any>,
|
||||
captureMap: Record<string, SyntaxNode>,
|
||||
): SyntaxNode | null => {
|
||||
for (const key of DEFINITION_CAPTURE_KEYS) {
|
||||
if (captureMap[key]) return captureMap[key];
|
||||
|
|
@ -149,7 +149,9 @@ export const CONTAINER_TYPE_TO_LABEL: Record<string, string> = {
|
|||
/** Check if a Kotlin function_declaration capture is inside a class_body (i.e., a method).
|
||||
* Kotlin grammar uses function_declaration for both top-level functions and class methods.
|
||||
* Returns true when the captured definition node has a class_body ancestor. */
|
||||
export function isKotlinClassMethod(captureNode: { parent?: any } | null | undefined): boolean {
|
||||
export function isKotlinClassMethod(
|
||||
captureNode: { parent?: SyntaxNode | null } | null | undefined,
|
||||
): boolean {
|
||||
let ancestor = captureNode?.parent;
|
||||
while (ancestor) {
|
||||
if (ancestor.type === 'class_body') return true;
|
||||
|
|
@ -165,7 +167,7 @@ export function isKotlinClassMethod(captureNode: { parent?: any } | null | undef
|
|||
* Returns null if the capture should be skipped (import, call, C/C++ duplicate, missing name).
|
||||
*/
|
||||
export function getLabelFromCaptures(
|
||||
captureMap: Record<string, any>,
|
||||
captureMap: Record<string, SyntaxNode>,
|
||||
provider: LanguageProvider,
|
||||
): NodeLabel | null {
|
||||
if (captureMap['import'] || captureMap['call']) return null;
|
||||
|
|
@ -204,7 +206,7 @@ export function getLabelFromCaptures(
|
|||
|
||||
/** Walk up AST to find enclosing class/struct/interface/impl, return its generateId or null.
|
||||
* For Go method_declaration nodes, extracts receiver type (e.g. `func (u *User) Save()` → User struct). */
|
||||
export const findEnclosingClassId = (node: any, filePath: string): string | null => {
|
||||
export const findEnclosingClassId = (node: SyntaxNode, filePath: string): string | null => {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
// Go: method_declaration has a receiver parameter with the struct type
|
||||
|
|
@ -213,7 +215,7 @@ export const findEnclosingClassId = (node: any, filePath: string): string | null
|
|||
if (receiver) {
|
||||
// receiver is a parameter_list: (u *User) or (u User)
|
||||
const paramDecl = receiver.namedChildren?.find?.(
|
||||
(c: any) => c.type === 'parameter_declaration',
|
||||
(c: SyntaxNode) => c.type === 'parameter_declaration',
|
||||
);
|
||||
if (paramDecl) {
|
||||
const typeNode = paramDecl.childForFieldName?.('type');
|
||||
|
|
@ -230,7 +232,7 @@ export const findEnclosingClassId = (node: any, filePath: string): string | null
|
|||
// Go: type_declaration wrapping a struct_type (type User struct { ... })
|
||||
// field_declaration → field_declaration_list → struct_type → type_spec → type_declaration
|
||||
if (current.type === 'type_declaration') {
|
||||
const typeSpec = current.children?.find((c: any) => c.type === 'type_spec');
|
||||
const typeSpec = current.children?.find((c: SyntaxNode) => c.type === 'type_spec');
|
||||
if (typeSpec) {
|
||||
const typeBody = typeSpec.childForFieldName?.('type');
|
||||
if (typeBody?.type === 'struct_type' || typeBody?.type === 'interface_type') {
|
||||
|
|
@ -246,11 +248,11 @@ export const findEnclosingClassId = (node: any, filePath: string): string | null
|
|||
// Rust impl_item: for `impl Trait for Struct {}`, pick the type after `for`
|
||||
if (current.type === 'impl_item') {
|
||||
const children = current.children ?? [];
|
||||
const forIdx = children.findIndex((c: any) => c.text === 'for');
|
||||
const forIdx = children.findIndex((c: SyntaxNode) => c.text === 'for');
|
||||
if (forIdx !== -1) {
|
||||
const nameNode = children
|
||||
.slice(forIdx + 1)
|
||||
.find((c: any) => c.type === 'type_identifier' || c.type === 'identifier');
|
||||
.find((c: SyntaxNode) => c.type === 'type_identifier' || c.type === 'identifier');
|
||||
if (nameNode) {
|
||||
return generateId('Impl', `${filePath}:${nameNode.text}`);
|
||||
}
|
||||
|
|
@ -260,7 +262,7 @@ export const findEnclosingClassId = (node: any, filePath: string): string | null
|
|||
const nameNode =
|
||||
current.childForFieldName?.('name') ??
|
||||
current.children?.find(
|
||||
(c: any) =>
|
||||
(c: SyntaxNode) =>
|
||||
c.type === 'type_identifier' ||
|
||||
c.type === 'identifier' ||
|
||||
c.type === 'name' ||
|
||||
|
|
@ -281,10 +283,10 @@ export const findEnclosingClassId = (node: any, filePath: string): string | null
|
|||
* Used for Kotlin AST traversal where visibility_modifier lives inside a modifiers sibling.
|
||||
*/
|
||||
export const findSiblingChild = (
|
||||
parent: any,
|
||||
parent: SyntaxNode,
|
||||
siblingType: string,
|
||||
childType: string,
|
||||
): any | null => {
|
||||
): SyntaxNode | null => {
|
||||
for (let i = 0; i < parent.childCount; i++) {
|
||||
const sibling = parent.child(i);
|
||||
if (sibling?.type === siblingType) {
|
||||
|
|
@ -843,7 +845,7 @@ export const extractMethodSignature = (node: SyntaxNode | null | undefined): Met
|
|||
// ============================================================================
|
||||
|
||||
/** Walk an AST node depth-first, returning the first descendant with the given type. */
|
||||
export function findDescendant(node: any, type: string): any {
|
||||
export function findDescendant(node: SyntaxNode, type: string): SyntaxNode | null {
|
||||
if (node.type === type) return node;
|
||||
for (const child of node.children ?? []) {
|
||||
const found = findDescendant(child, type);
|
||||
|
|
@ -853,9 +855,9 @@ export function findDescendant(node: any, type: string): any {
|
|||
}
|
||||
|
||||
/** Extract the text content from a string or encapsed_string AST node. */
|
||||
export function extractStringContent(node: any): string | null {
|
||||
export function extractStringContent(node: SyntaxNode | null | undefined): string | null {
|
||||
if (!node) return null;
|
||||
const content = node.children?.find((c: any) => c.type === 'string_content');
|
||||
const content = node.children?.find((c: SyntaxNode) => c.type === 'string_content');
|
||||
if (content) return content.text;
|
||||
if (node.type === 'string_content') return node.text;
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -15,22 +15,26 @@ import { createRequire } from 'node:module';
|
|||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { getProvider } from '../languages/index.js';
|
||||
import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
|
||||
import { SymbolTable } from '../symbol-table.js';
|
||||
|
||||
/** Language grammar type accepted by Parser.setLanguage(). */
|
||||
type TreeSitterLanguage = Parameters<typeof Parser.prototype.setLanguage>[0];
|
||||
|
||||
// tree-sitter-swift is an optionalDependency — may not be installed
|
||||
const _require = createRequire(import.meta.url);
|
||||
let Swift: any = null;
|
||||
let Swift: TreeSitterLanguage | null = null;
|
||||
try {
|
||||
Swift = _require('tree-sitter-swift');
|
||||
} catch {}
|
||||
|
||||
// tree-sitter-dart is an optionalDependency — may not be installed
|
||||
let Dart: any = null;
|
||||
let Dart: TreeSitterLanguage | null = null;
|
||||
try {
|
||||
Dart = _require('tree-sitter-dart');
|
||||
} catch {}
|
||||
|
||||
// tree-sitter-kotlin is an optionalDependency — may not be installed
|
||||
let Kotlin: any = null;
|
||||
let Kotlin: TreeSitterLanguage | null = null;
|
||||
try {
|
||||
Kotlin = _require('tree-sitter-kotlin');
|
||||
} catch {}
|
||||
|
|
@ -250,13 +254,18 @@ export interface ParseWorkerInput {
|
|||
content: string;
|
||||
}
|
||||
|
||||
type WorkerIncomingMessage =
|
||||
| { type: 'sub-batch'; files: ParseWorkerInput[] }
|
||||
| { type: 'flush' }
|
||||
| ParseWorkerInput[];
|
||||
|
||||
// ============================================================================
|
||||
// Worker-local parser + language map
|
||||
// ============================================================================
|
||||
|
||||
const parser = new Parser();
|
||||
|
||||
const languageMap: Record<string, any> = {
|
||||
const languageMap: Record<string, TreeSitterLanguage> = {
|
||||
[SupportedLanguages.JavaScript]: JavaScript,
|
||||
[SupportedLanguages.TypeScript]: TypeScript.typescript,
|
||||
[`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
|
||||
|
|
@ -305,9 +314,9 @@ const setLanguage = (language: SupportedLanguages, filePath: string): void => {
|
|||
// a stored null (enclosing class/function not found = top-level).
|
||||
// ============================================================================
|
||||
|
||||
const classIdCache = new Map<any, string | null>();
|
||||
const functionIdCache = new Map<any, string | null>();
|
||||
const exportCache = new Map<any, boolean>();
|
||||
const classIdCache = new Map<SyntaxNode, string | null>();
|
||||
const functionIdCache = new Map<SyntaxNode, string | null>();
|
||||
const exportCache = new Map<SyntaxNode, boolean>();
|
||||
|
||||
const clearCaches = (): void => {
|
||||
classIdCache.clear();
|
||||
|
|
@ -343,11 +352,11 @@ function findEnclosingClassNode(node: SyntaxNode): SyntaxNode | null {
|
|||
* Field extraction only uses symbolTable.lookupExactAll for optional type resolution —
|
||||
* returning [] causes the extractor to use the raw type string, which is fine for us.
|
||||
*/
|
||||
const NOOP_SYMBOL_TABLE: any = {
|
||||
const NOOP_SYMBOL_TABLE = {
|
||||
lookupExactAll: () => [],
|
||||
lookupExact: () => undefined,
|
||||
lookupExactFull: () => undefined,
|
||||
};
|
||||
} as unknown as SymbolTable;
|
||||
|
||||
/**
|
||||
* Get (or extract and cache) field info for a class node.
|
||||
|
|
@ -385,7 +394,7 @@ import type { LanguageProvider } from '../language-provider.js';
|
|||
/** Walk up AST to find enclosing function, return its generateId or null for top-level.
|
||||
* Applies provider.labelOverride so the label matches the definition phase (single source of truth). */
|
||||
const findEnclosingFunctionId = (
|
||||
node: any,
|
||||
node: SyntaxNode,
|
||||
filePath: string,
|
||||
provider: LanguageProvider,
|
||||
): string | null => {
|
||||
|
|
@ -433,7 +442,7 @@ const findEnclosingFunctionId = (
|
|||
};
|
||||
|
||||
/** Cached wrapper for findEnclosingClassId — avoids repeated parent walks. */
|
||||
const cachedFindEnclosingClassId = (node: any, filePath: string): string | null => {
|
||||
const cachedFindEnclosingClassId = (node: SyntaxNode, filePath: string): string | null => {
|
||||
const cached = classIdCache.get(node);
|
||||
if (cached !== undefined) return cached;
|
||||
|
||||
|
|
@ -444,8 +453,8 @@ const cachedFindEnclosingClassId = (node: any, filePath: string): string | null
|
|||
|
||||
/** Cached wrapper for export checking — avoids repeated parent walks per symbol. */
|
||||
const cachedExportCheck = (
|
||||
checker: (node: any, name: string) => boolean,
|
||||
node: any,
|
||||
checker: (node: SyntaxNode, name: string) => boolean,
|
||||
node: SyntaxNode,
|
||||
name: string,
|
||||
): boolean => {
|
||||
const cached = exportCache.get(node);
|
||||
|
|
@ -635,26 +644,26 @@ const RESOURCE_ACTIONS = ['index', 'create', 'store', 'show', 'edit', 'update',
|
|||
const API_RESOURCE_ACTIONS = ['index', 'store', 'show', 'update', 'destroy'];
|
||||
|
||||
/** Check if node is a scoped_call_expression with object 'Route' */
|
||||
function isRouteStaticCall(node: any): boolean {
|
||||
function isRouteStaticCall(node: SyntaxNode): boolean {
|
||||
if (node.type !== 'scoped_call_expression') return false;
|
||||
const obj = node.childForFieldName?.('object') ?? node.children?.[0];
|
||||
return obj?.text === 'Route';
|
||||
}
|
||||
|
||||
/** Get the method name from a scoped_call_expression or member_call_expression */
|
||||
function getCallMethodName(node: any): string | null {
|
||||
function getCallMethodName(node: SyntaxNode): string | null {
|
||||
const nameNode =
|
||||
node.childForFieldName?.('name') ?? node.children?.find((c: any) => c.type === 'name');
|
||||
node.childForFieldName?.('name') ?? node.children?.find((c: SyntaxNode) => c.type === 'name');
|
||||
return nameNode?.text ?? null;
|
||||
}
|
||||
|
||||
/** Get the arguments node from a call expression */
|
||||
function getArguments(node: any): any {
|
||||
return node.children?.find((c: any) => c.type === 'arguments') ?? null;
|
||||
function getArguments(node: SyntaxNode): SyntaxNode | null {
|
||||
return node.children?.find((c: SyntaxNode) => c.type === 'arguments') ?? null;
|
||||
}
|
||||
|
||||
/** Find the closure body inside arguments */
|
||||
function findClosureBody(argsNode: any): any | null {
|
||||
function findClosureBody(argsNode: SyntaxNode | null): SyntaxNode | null {
|
||||
if (!argsNode) return null;
|
||||
for (const child of argsNode.children ?? []) {
|
||||
if (child.type === 'argument') {
|
||||
|
|
@ -662,7 +671,8 @@ function findClosureBody(argsNode: any): any | null {
|
|||
if (inner.type === 'anonymous_function' || inner.type === 'arrow_function') {
|
||||
return (
|
||||
inner.childForFieldName?.('body') ??
|
||||
inner.children?.find((c: any) => c.type === 'compound_statement')
|
||||
inner.children?.find((c: SyntaxNode) => c.type === 'compound_statement') ??
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -670,7 +680,8 @@ function findClosureBody(argsNode: any): any | null {
|
|||
if (child.type === 'anonymous_function' || child.type === 'arrow_function') {
|
||||
return (
|
||||
child.childForFieldName?.('body') ??
|
||||
child.children?.find((c: any) => c.type === 'compound_statement')
|
||||
child.children?.find((c: SyntaxNode) => c.type === 'compound_statement') ??
|
||||
null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -678,7 +689,7 @@ function findClosureBody(argsNode: any): any | null {
|
|||
}
|
||||
|
||||
/** Extract first string argument from arguments node */
|
||||
function extractFirstStringArg(argsNode: any): string | null {
|
||||
function extractFirstStringArg(argsNode: SyntaxNode | null): string | null {
|
||||
if (!argsNode) return null;
|
||||
for (const child of argsNode.children ?? []) {
|
||||
const target = child.type === 'argument' ? child.children?.[0] : child;
|
||||
|
|
@ -691,7 +702,7 @@ function extractFirstStringArg(argsNode: any): string | null {
|
|||
}
|
||||
|
||||
/** Extract middleware from arguments — handles string or array */
|
||||
function extractMiddlewareArg(argsNode: any): string[] {
|
||||
function extractMiddlewareArg(argsNode: SyntaxNode | null): string[] {
|
||||
if (!argsNode) return [];
|
||||
for (const child of argsNode.children ?? []) {
|
||||
const target = child.type === 'argument' ? child.children?.[0] : child;
|
||||
|
|
@ -705,7 +716,7 @@ function extractMiddlewareArg(argsNode: any): string[] {
|
|||
for (const el of target.children ?? []) {
|
||||
if (el.type === 'array_element_initializer') {
|
||||
const str = el.children?.find(
|
||||
(c: any) => c.type === 'string' || c.type === 'encapsed_string',
|
||||
(c: SyntaxNode) => c.type === 'string' || c.type === 'encapsed_string',
|
||||
);
|
||||
const val = str ? extractStringContent(str) : null;
|
||||
if (val) items.push(val);
|
||||
|
|
@ -718,25 +729,25 @@ function extractMiddlewareArg(argsNode: any): string[] {
|
|||
}
|
||||
|
||||
/** Extract Controller::class from arguments */
|
||||
function extractClassArg(argsNode: any): string | null {
|
||||
function extractClassArg(argsNode: SyntaxNode | null): string | null {
|
||||
if (!argsNode) return null;
|
||||
for (const child of argsNode.children ?? []) {
|
||||
const target = child.type === 'argument' ? child.children?.[0] : child;
|
||||
if (target?.type === 'class_constant_access_expression') {
|
||||
return target.children?.find((c: any) => c.type === 'name')?.text ?? null;
|
||||
return target.children?.find((c: SyntaxNode) => c.type === 'name')?.text ?? null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Extract controller class name from arguments: [Controller::class, 'method'] or 'Controller@method' */
|
||||
function extractControllerTarget(argsNode: any): {
|
||||
function extractControllerTarget(argsNode: SyntaxNode | null): {
|
||||
controller: string | null;
|
||||
method: string | null;
|
||||
} {
|
||||
if (!argsNode) return { controller: null, method: null };
|
||||
|
||||
const args: any[] = [];
|
||||
const args: (SyntaxNode | undefined)[] = [];
|
||||
for (const child of argsNode.children ?? []) {
|
||||
if (child.type === 'argument') args.push(child.children?.[0]);
|
||||
else if (child.type !== '(' && child.type !== ')' && child.type !== ',') args.push(child);
|
||||
|
|
@ -750,14 +761,14 @@ function extractControllerTarget(argsNode: any): {
|
|||
if (handlerNode.type === 'array_creation_expression') {
|
||||
let controller: string | null = null;
|
||||
let method: string | null = null;
|
||||
const elements: any[] = [];
|
||||
const elements: SyntaxNode[] = [];
|
||||
for (const el of handlerNode.children ?? []) {
|
||||
if (el.type === 'array_element_initializer') elements.push(el);
|
||||
}
|
||||
if (elements[0]) {
|
||||
const classAccess = findDescendant(elements[0], 'class_constant_access_expression');
|
||||
if (classAccess) {
|
||||
controller = classAccess.children?.find((c: any) => c.type === 'name')?.text ?? null;
|
||||
controller = classAccess.children?.find((c: SyntaxNode) => c.type === 'name')?.text ?? null;
|
||||
}
|
||||
}
|
||||
if (elements[1]) {
|
||||
|
|
@ -778,7 +789,8 @@ function extractControllerTarget(argsNode: any): {
|
|||
|
||||
// Class reference: UserController::class (invokable controller)
|
||||
if (handlerNode.type === 'class_constant_access_expression') {
|
||||
const controller = handlerNode.children?.find((c: any) => c.type === 'name')?.text ?? null;
|
||||
const controller =
|
||||
handlerNode.children?.find((c: SyntaxNode) => c.type === 'name')?.text ?? null;
|
||||
return { controller, method: '__invoke' };
|
||||
}
|
||||
|
||||
|
|
@ -788,22 +800,22 @@ function extractControllerTarget(argsNode: any): {
|
|||
interface ChainedRouteCall {
|
||||
isRouteFacade: boolean;
|
||||
terminalMethod: string;
|
||||
attributes: { method: string; argsNode: any }[];
|
||||
terminalArgs: any;
|
||||
node: any;
|
||||
attributes: { method: string; argsNode: SyntaxNode | null }[];
|
||||
terminalArgs: SyntaxNode | null;
|
||||
node: SyntaxNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a chained call like Route::middleware('auth')->prefix('api')->group(fn)
|
||||
*/
|
||||
function unwrapRouteChain(node: any): ChainedRouteCall | null {
|
||||
function unwrapRouteChain(node: SyntaxNode): ChainedRouteCall | null {
|
||||
if (node.type !== 'member_call_expression') return null;
|
||||
|
||||
const terminalMethod = getCallMethodName(node);
|
||||
if (!terminalMethod) return null;
|
||||
|
||||
const terminalArgs = getArguments(node);
|
||||
const attributes: { method: string; argsNode: any }[] = [];
|
||||
const attributes: { method: string; argsNode: SyntaxNode | null }[] = [];
|
||||
|
||||
let current = node.children?.[0];
|
||||
|
||||
|
|
@ -831,7 +843,7 @@ function unwrapRouteChain(node: any): ChainedRouteCall | null {
|
|||
}
|
||||
|
||||
/** Parse Route::group(['middleware' => ..., 'prefix' => ...], fn) array syntax */
|
||||
function parseArrayGroupArgs(argsNode: any): RouteGroupContext {
|
||||
function parseArrayGroupArgs(argsNode: SyntaxNode | null): RouteGroupContext {
|
||||
const ctx: RouteGroupContext = { middleware: [], prefix: null, controller: null };
|
||||
if (!argsNode) return ctx;
|
||||
|
||||
|
|
@ -841,7 +853,7 @@ function parseArrayGroupArgs(argsNode: any): RouteGroupContext {
|
|||
for (const el of target.children ?? []) {
|
||||
if (el.type !== 'array_element_initializer') continue;
|
||||
const children = el.children ?? [];
|
||||
const arrowIdx = children.findIndex((c: any) => c.type === '=>');
|
||||
const arrowIdx = children.findIndex((c: SyntaxNode) => c.type === '=>');
|
||||
if (arrowIdx === -1) continue;
|
||||
const key = extractStringContent(children[arrowIdx - 1]);
|
||||
const val = children[arrowIdx + 1];
|
||||
|
|
@ -852,7 +864,7 @@ function parseArrayGroupArgs(argsNode: any): RouteGroupContext {
|
|||
} else if (val?.type === 'array_creation_expression') {
|
||||
for (const item of val.children ?? []) {
|
||||
if (item.type === 'array_element_initializer') {
|
||||
const str = item.children?.find((c: any) => c.type === 'string');
|
||||
const str = item.children?.find((c: SyntaxNode) => c.type === 'string');
|
||||
const s = str ? extractStringContent(str) : null;
|
||||
if (s) ctx.middleware.push(s);
|
||||
}
|
||||
|
|
@ -862,7 +874,7 @@ function parseArrayGroupArgs(argsNode: any): RouteGroupContext {
|
|||
ctx.prefix = extractStringContent(val) ?? null;
|
||||
} else if (key === 'controller') {
|
||||
if (val?.type === 'class_constant_access_expression') {
|
||||
ctx.controller = val.children?.find((c: any) => c.type === 'name')?.text ?? null;
|
||||
ctx.controller = val.children?.find((c: SyntaxNode) => c.type === 'name')?.text ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -871,7 +883,7 @@ function parseArrayGroupArgs(argsNode: any): RouteGroupContext {
|
|||
return ctx;
|
||||
}
|
||||
|
||||
function extractLaravelRoutes(tree: any, filePath: string): ExtractedRoute[] {
|
||||
function extractLaravelRoutes(tree: Parser.Tree, filePath: string): ExtractedRoute[] {
|
||||
const routes: ExtractedRoute[] = [];
|
||||
|
||||
function resolveStack(stack: RouteGroupContext[]): {
|
||||
|
|
@ -892,10 +904,10 @@ function extractLaravelRoutes(tree: any, filePath: string): ExtractedRoute[] {
|
|||
|
||||
function emitRoute(
|
||||
httpMethod: string,
|
||||
argsNode: any,
|
||||
argsNode: SyntaxNode | null,
|
||||
lineNumber: number,
|
||||
groupStack: RouteGroupContext[],
|
||||
chainAttrs: { method: string; argsNode: any }[],
|
||||
chainAttrs: { method: string; argsNode: SyntaxNode | null }[],
|
||||
) {
|
||||
const effective = resolveStack(groupStack);
|
||||
|
||||
|
|
@ -944,7 +956,7 @@ function extractLaravelRoutes(tree: any, filePath: string): ExtractedRoute[] {
|
|||
}
|
||||
}
|
||||
|
||||
function walk(node: any, groupStack: RouteGroupContext[]) {
|
||||
function walk(node: SyntaxNode, groupStack: RouteGroupContext[]) {
|
||||
// Case 1: Simple Route::get(...), Route::post(...), etc.
|
||||
if (isRouteStaticCall(node)) {
|
||||
const method = getCallMethodName(node);
|
||||
|
|
@ -1003,7 +1015,7 @@ function extractLaravelRoutes(tree: any, filePath: string): ExtractedRoute[] {
|
|||
walkChildren(node, groupStack);
|
||||
}
|
||||
|
||||
function walkChildren(node: any, groupStack: RouteGroupContext[]) {
|
||||
function walkChildren(node: SyntaxNode, groupStack: RouteGroupContext[]) {
|
||||
for (const child of node.children ?? []) {
|
||||
walk(child, groupStack);
|
||||
}
|
||||
|
|
@ -1073,7 +1085,7 @@ const processFileGroup = (
|
|||
result: ParseWorkerResult,
|
||||
onFileProcessed?: () => void,
|
||||
): void => {
|
||||
let query: any;
|
||||
let query: Parser.Query;
|
||||
try {
|
||||
const lang = parser.getLanguage();
|
||||
query = new Parser.Query(lang, queryString);
|
||||
|
|
@ -1124,7 +1136,7 @@ const processFileGroup = (
|
|||
// runs. This pre-pass makes parent class information available for type resolution.
|
||||
const fileParentMap = new Map<string, string[]>();
|
||||
for (const match of matches) {
|
||||
const captureMap: Record<string, any> = {};
|
||||
const captureMap: Record<string, SyntaxNode> = {};
|
||||
for (const c of match.captures) {
|
||||
captureMap[c.name] = c.node;
|
||||
}
|
||||
|
|
@ -1176,7 +1188,7 @@ const processFileGroup = (
|
|||
const fileDecorators = new Map<number, { name: string; arg?: string; isTool?: boolean }>();
|
||||
|
||||
for (const match of matches) {
|
||||
const captureMap: Record<string, any> = {};
|
||||
const captureMap: Record<string, SyntaxNode> = {};
|
||||
for (const c of match.captures) {
|
||||
captureMap[c.name] = c.node;
|
||||
}
|
||||
|
|
@ -1830,10 +1842,19 @@ const mergeResult = (target: ParseWorkerResult, src: ParseWorkerResult) => {
|
|||
target.fileCount += src.fileCount;
|
||||
};
|
||||
|
||||
parentPort!.on('message', (msg: any) => {
|
||||
parentPort!.on('message', (msg: WorkerIncomingMessage) => {
|
||||
try {
|
||||
// Legacy single-message mode (backward compat): array of files
|
||||
if (Array.isArray(msg)) {
|
||||
const result = processBatch(msg, (filesProcessed) => {
|
||||
parentPort!.postMessage({ type: 'progress', filesProcessed });
|
||||
});
|
||||
parentPort!.postMessage({ type: 'result', data: result });
|
||||
return;
|
||||
}
|
||||
|
||||
// Sub-batch mode: { type: 'sub-batch', files: [...] }
|
||||
if (msg && msg.type === 'sub-batch') {
|
||||
if (msg.type === 'sub-batch') {
|
||||
const result = processBatch(msg.files, (filesProcessed) => {
|
||||
parentPort!.postMessage({
|
||||
type: 'progress',
|
||||
|
|
@ -1848,7 +1869,7 @@ parentPort!.on('message', (msg: any) => {
|
|||
}
|
||||
|
||||
// Flush: send accumulated results
|
||||
if (msg && msg.type === 'flush') {
|
||||
if (msg.type === 'flush') {
|
||||
parentPort!.postMessage({ type: 'result', data: accumulated });
|
||||
// Reset for potential reuse
|
||||
accumulated = {
|
||||
|
|
@ -1872,15 +1893,6 @@ parentPort!.on('message', (msg: any) => {
|
|||
cumulativeProcessed = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Legacy single-message mode (backward compat): array of files
|
||||
if (Array.isArray(msg)) {
|
||||
const result = processBatch(msg, (filesProcessed) => {
|
||||
parentPort!.postMessage({ type: 'progress', filesProcessed });
|
||||
});
|
||||
parentPort!.postMessage({ type: 'result', data: result });
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
parentPort!.postMessage({ type: 'error', error: message });
|
||||
|
|
|
|||
|
|
@ -21,6 +21,13 @@ export interface WorkerPool {
|
|||
readonly size: number;
|
||||
}
|
||||
|
||||
/** Message shapes sent back by worker threads. */
|
||||
type WorkerOutgoingMessage =
|
||||
| { type: 'progress'; filesProcessed: number }
|
||||
| { type: 'sub-batch-done' }
|
||||
| { type: 'error'; error: string }
|
||||
| { type: 'result'; data: unknown };
|
||||
|
||||
/**
|
||||
* Max files to send to a worker in a single postMessage.
|
||||
* Keeps structured-clone memory bounded per sub-batch.
|
||||
|
|
@ -105,32 +112,28 @@ export const createWorkerPool = (workerUrl: URL, poolSize?: number): WorkerPool
|
|||
worker.postMessage({ type: 'sub-batch', files: subBatch });
|
||||
};
|
||||
|
||||
const handler = (msg: any) => {
|
||||
const handler = (msg: WorkerOutgoingMessage) => {
|
||||
if (settled) return;
|
||||
if (msg && msg.type === 'progress') {
|
||||
if (msg.type === 'progress') {
|
||||
workerProgress[i] = msg.filesProcessed;
|
||||
if (onProgress) {
|
||||
const total = workerProgress.reduce((a, b) => a + b, 0);
|
||||
onProgress(total);
|
||||
}
|
||||
} else if (msg && msg.type === 'sub-batch-done') {
|
||||
} else if (msg.type === 'sub-batch-done') {
|
||||
sendNextSubBatch();
|
||||
} else if (msg && msg.type === 'error') {
|
||||
} else if (msg.type === 'error') {
|
||||
settled = true;
|
||||
cleanup();
|
||||
reject(new Error(`Worker ${i} error: ${msg.error}`));
|
||||
} else if (msg && msg.type === 'result') {
|
||||
} else if (msg.type === 'result') {
|
||||
settled = true;
|
||||
cleanup();
|
||||
resolve(msg.data);
|
||||
} else {
|
||||
settled = true;
|
||||
cleanup();
|
||||
resolve(msg);
|
||||
resolve(msg.data as TResult);
|
||||
}
|
||||
};
|
||||
|
||||
const errorHandler = (err: any) => {
|
||||
const errorHandler = (err: Error) => {
|
||||
if (!settled) {
|
||||
settled = true;
|
||||
cleanup();
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ export const streamAllCSVsToDisk = async (
|
|||
);
|
||||
break;
|
||||
case 'Community': {
|
||||
const keywords = (node.properties as any).keywords || [];
|
||||
const keywords = node.properties.keywords || [];
|
||||
const keywordsStr = `[${keywords.map((k: string) => `'${k.replace(/\\/g, '\\\\').replace(/'/g, "''").replace(/,/g, '\\,')}'`).join(',')}]`;
|
||||
await communityWriter.addRow(
|
||||
[
|
||||
|
|
@ -352,8 +352,8 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVField(node.properties.name || ''),
|
||||
escapeCSVField(node.properties.heuristicLabel || ''),
|
||||
keywordsStr,
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
escapeCSVField((node.properties as any).enrichedBy || 'heuristic'),
|
||||
escapeCSVField(node.properties.description || ''),
|
||||
escapeCSVField(node.properties.enrichedBy || 'heuristic'),
|
||||
escapeCSVNumber(node.properties.cohesion, 0),
|
||||
escapeCSVNumber(node.properties.symbolCount, 0),
|
||||
].join(','),
|
||||
|
|
@ -361,18 +361,18 @@ export const streamAllCSVsToDisk = async (
|
|||
break;
|
||||
}
|
||||
case 'Process': {
|
||||
const communities = (node.properties as any).communities || [];
|
||||
const communities = node.properties.communities || [];
|
||||
const communitiesStr = `[${communities.map((c: string) => `'${c.replace(/'/g, "''")}'`).join(',')}]`;
|
||||
await processWriter.addRow(
|
||||
[
|
||||
escapeCSVField(node.id),
|
||||
escapeCSVField(node.properties.name || ''),
|
||||
escapeCSVField((node.properties as any).heuristicLabel || ''),
|
||||
escapeCSVField((node.properties as any).processType || ''),
|
||||
escapeCSVNumber((node.properties as any).stepCount, 0),
|
||||
escapeCSVField(node.properties.heuristicLabel || ''),
|
||||
escapeCSVField(node.properties.processType || ''),
|
||||
escapeCSVNumber(node.properties.stepCount, 0),
|
||||
escapeCSVField(communitiesStr),
|
||||
escapeCSVField((node.properties as any).entryPointId || ''),
|
||||
escapeCSVField((node.properties as any).terminalId || ''),
|
||||
escapeCSVField(node.properties.entryPointId || ''),
|
||||
escapeCSVField(node.properties.terminalId || ''),
|
||||
].join(','),
|
||||
);
|
||||
break;
|
||||
|
|
@ -388,7 +388,7 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVNumber(node.properties.endLine, -1),
|
||||
node.properties.isExported ? 'true' : 'false',
|
||||
escapeCSVField(content),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
escapeCSVField(node.properties.description || ''),
|
||||
escapeCSVNumber(node.properties.parameterCount, 0),
|
||||
escapeCSVField(node.properties.returnType || ''),
|
||||
].join(','),
|
||||
|
|
@ -404,21 +404,21 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVField(node.properties.filePath || ''),
|
||||
escapeCSVNumber(node.properties.startLine, -1),
|
||||
escapeCSVNumber(node.properties.endLine, -1),
|
||||
escapeCSVNumber((node.properties as any).level, 1),
|
||||
escapeCSVNumber(node.properties.level, 1),
|
||||
escapeCSVField(content),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
escapeCSVField(node.properties.description || ''),
|
||||
].join(','),
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'Route': {
|
||||
const responseKeys = (node.properties as any).responseKeys || [];
|
||||
const responseKeys = node.properties.responseKeys || [];
|
||||
// LadybugDB array literal inside a quoted CSV field: escapeCSVField wraps in "..."
|
||||
// and the array uses single-quoted elements
|
||||
const keysStr = `[${responseKeys.map((k: string) => `'${k.replace(/'/g, "''")}'`).join(',')}]`;
|
||||
const errorKeys = (node.properties as any).errorKeys || [];
|
||||
const errorKeys = node.properties.errorKeys || [];
|
||||
const errorKeysStr = `[${errorKeys.map((k: string) => `'${k.replace(/'/g, "''")}'`).join(',')}]`;
|
||||
const middleware = (node.properties as any).middleware || [];
|
||||
const middleware = node.properties.middleware || [];
|
||||
const middlewareStr = `[${middleware.map((m: string) => `'${m.replace(/'/g, "''")}'`).join(',')}]`;
|
||||
await routeWriter.addRow(
|
||||
[
|
||||
|
|
@ -438,7 +438,7 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVField(node.id),
|
||||
escapeCSVField(node.properties.name || ''),
|
||||
escapeCSVField(node.properties.filePath || ''),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
escapeCSVField(node.properties.description || ''),
|
||||
].join(','),
|
||||
);
|
||||
break;
|
||||
|
|
@ -456,7 +456,7 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVNumber(node.properties.endLine, -1),
|
||||
node.properties.isExported ? 'true' : 'false',
|
||||
escapeCSVField(content),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
escapeCSVField(node.properties.description || ''),
|
||||
].join(','),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -472,7 +472,7 @@ export const streamAllCSVsToDisk = async (
|
|||
escapeCSVNumber(node.properties.startLine, -1),
|
||||
escapeCSVNumber(node.properties.endLine, -1),
|
||||
escapeCSVField(content),
|
||||
escapeCSVField((node.properties as any).description || ''),
|
||||
escapeCSVField(node.properties.description || ''),
|
||||
].join(','),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue