feat: add TypeScript language parser
This commit is contained in:
227
packages/parser/src/languages/typescript.ts
Normal file
227
packages/parser/src/languages/typescript.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { parse as babelParse } from "@babel/parser";
|
||||
import _traverse from "@babel/traverse";
|
||||
import type {
|
||||
FileNode,
|
||||
FunctionNode,
|
||||
ClassNode,
|
||||
ImportNode,
|
||||
ExportNode,
|
||||
} from "@codeboard/shared";
|
||||
import type { LanguageParser } from "./base.js";
|
||||
|
||||
const traverse =
|
||||
typeof _traverse === "function"
|
||||
? _traverse
|
||||
: (_traverse as unknown as { default: typeof _traverse }).default;
|
||||
|
||||
function extractFunctionParams(
|
||||
params: Array<{ name?: string; left?: { name?: string }; type?: string }>
|
||||
): string[] {
|
||||
return params.map((p) => {
|
||||
if (p.type === "AssignmentPattern" && p.left?.name) return p.left.name;
|
||||
return p.name ?? "unknown";
|
||||
});
|
||||
}
|
||||
|
||||
export const typescriptParser: LanguageParser = {
|
||||
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"],
|
||||
|
||||
parse(content: string, filePath: string): FileNode {
|
||||
const functions: FunctionNode[] = [];
|
||||
const classes: ClassNode[] = [];
|
||||
const imports: ImportNode[] = [];
|
||||
const exports: ExportNode[] = [];
|
||||
const calls: Set<string> = new Set();
|
||||
|
||||
let ast;
|
||||
try {
|
||||
ast = babelParse(content, {
|
||||
sourceType: "module",
|
||||
plugins: [
|
||||
"typescript",
|
||||
"jsx",
|
||||
"decorators-legacy",
|
||||
"classProperties",
|
||||
"classPrivateProperties",
|
||||
"classPrivateMethods",
|
||||
"optionalChaining",
|
||||
"nullishCoalescingOperator",
|
||||
"dynamicImport",
|
||||
],
|
||||
errorRecovery: true,
|
||||
});
|
||||
} catch {
|
||||
return {
|
||||
path: filePath,
|
||||
language: filePath.endsWith(".py") ? "python" : "typescript",
|
||||
size: content.length,
|
||||
functions: [],
|
||||
classes: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
complexity: 0,
|
||||
};
|
||||
}
|
||||
|
||||
traverse(ast, {
|
||||
FunctionDeclaration(path) {
|
||||
const node = path.node;
|
||||
if (!node.id) return;
|
||||
functions.push({
|
||||
name: node.id.name,
|
||||
params: extractFunctionParams(node.params as never[]),
|
||||
returnType: node.returnType
|
||||
? content.slice(node.returnType.start!, node.returnType.end!)
|
||||
: undefined,
|
||||
lineStart: node.loc?.start.line ?? 0,
|
||||
lineEnd: node.loc?.end.line ?? 0,
|
||||
calls: [],
|
||||
});
|
||||
},
|
||||
|
||||
ArrowFunctionExpression(path) {
|
||||
const parent = path.parent;
|
||||
if (
|
||||
parent.type === "VariableDeclarator" &&
|
||||
parent.id.type === "Identifier"
|
||||
) {
|
||||
const node = path.node;
|
||||
functions.push({
|
||||
name: parent.id.name,
|
||||
params: extractFunctionParams(node.params as never[]),
|
||||
returnType: node.returnType
|
||||
? content.slice(node.returnType.start!, node.returnType.end!)
|
||||
: undefined,
|
||||
lineStart: node.loc?.start.line ?? 0,
|
||||
lineEnd: node.loc?.end.line ?? 0,
|
||||
calls: [],
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
ClassDeclaration(path) {
|
||||
const node = path.node;
|
||||
if (!node.id) return;
|
||||
const methods: FunctionNode[] = [];
|
||||
const properties: Array<{ name: string; type?: string }> = [];
|
||||
|
||||
for (const member of node.body.body) {
|
||||
if (
|
||||
member.type === "ClassMethod" &&
|
||||
member.key.type === "Identifier"
|
||||
) {
|
||||
methods.push({
|
||||
name: member.key.name,
|
||||
params: extractFunctionParams(member.params as never[]),
|
||||
lineStart: member.loc?.start.line ?? 0,
|
||||
lineEnd: member.loc?.end.line ?? 0,
|
||||
calls: [],
|
||||
});
|
||||
} else if (
|
||||
member.type === "ClassProperty" &&
|
||||
member.key.type === "Identifier"
|
||||
) {
|
||||
properties.push({
|
||||
name: member.key.name,
|
||||
type: member.typeAnnotation
|
||||
? content.slice(
|
||||
member.typeAnnotation.start!,
|
||||
member.typeAnnotation.end!
|
||||
)
|
||||
: undefined,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
classes.push({ name: node.id.name, methods, properties });
|
||||
},
|
||||
|
||||
ImportDeclaration(path) {
|
||||
const node = path.node;
|
||||
const specifiers = node.specifiers.map((s) => s.local.name);
|
||||
imports.push({ source: node.source.value, specifiers });
|
||||
},
|
||||
|
||||
ExportDefaultDeclaration() {
|
||||
exports.push({ name: "default", isDefault: true });
|
||||
},
|
||||
|
||||
ExportNamedDeclaration(path) {
|
||||
const node = path.node;
|
||||
if (node.declaration) {
|
||||
if (
|
||||
node.declaration.type === "FunctionDeclaration" &&
|
||||
node.declaration.id
|
||||
) {
|
||||
exports.push({
|
||||
name: node.declaration.id.name,
|
||||
isDefault: false,
|
||||
});
|
||||
} else if (
|
||||
node.declaration.type === "ClassDeclaration" &&
|
||||
node.declaration.id
|
||||
) {
|
||||
exports.push({
|
||||
name: node.declaration.id.name,
|
||||
isDefault: false,
|
||||
});
|
||||
} else if (node.declaration.type === "VariableDeclaration") {
|
||||
for (const decl of node.declaration.declarations) {
|
||||
if (decl.id.type === "Identifier") {
|
||||
exports.push({ name: decl.id.name, isDefault: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (node.specifiers) {
|
||||
for (const spec of node.specifiers) {
|
||||
if (spec.exported.type === "Identifier") {
|
||||
exports.push({ name: spec.exported.name, isDefault: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
CallExpression(path) {
|
||||
const callee = path.node.callee;
|
||||
if (callee.type === "Identifier") {
|
||||
calls.add(callee.name);
|
||||
} else if (
|
||||
callee.type === "MemberExpression" &&
|
||||
callee.property.type === "Identifier"
|
||||
) {
|
||||
calls.add(callee.property.name);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
for (const fn of functions) {
|
||||
fn.calls = Array.from(calls);
|
||||
}
|
||||
|
||||
let complexity = 0;
|
||||
traverse(ast, {
|
||||
IfStatement() { complexity++; },
|
||||
ForStatement() { complexity++; },
|
||||
ForInStatement() { complexity++; },
|
||||
ForOfStatement() { complexity++; },
|
||||
WhileStatement() { complexity++; },
|
||||
DoWhileStatement() { complexity++; },
|
||||
SwitchCase() { complexity++; },
|
||||
ConditionalExpression() { complexity++; },
|
||||
LogicalExpression() { complexity++; },
|
||||
CatchClause() { complexity++; },
|
||||
});
|
||||
|
||||
return {
|
||||
path: filePath,
|
||||
language: filePath.match(/\.tsx?$/) ? "typescript" : "javascript",
|
||||
size: content.length,
|
||||
functions,
|
||||
classes,
|
||||
imports,
|
||||
exports,
|
||||
complexity,
|
||||
};
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user