From 51147b77f1766e0c35b01143c34a05c7591fe203 Mon Sep 17 00:00:00 2001 From: songle <951540966@qq.com> Date: Fri, 2 Aug 2024 14:16:42 +0800 Subject: [PATCH] refactor: getDocumentSymbolTree by babel traverse vistor --- src/extension.ts | 11 +-- src/parse.ts | 119 ++++++++++++++++++++++++ src/parser.ts | 233 ----------------------------------------------- 3 files changed, 124 insertions(+), 239 deletions(-) create mode 100644 src/parse.ts delete mode 100644 src/parser.ts diff --git a/src/extension.ts b/src/extension.ts index cefa1e8..7f3f363 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,6 +1,5 @@ import * as vscode from "vscode"; -import { getSymbolTree } from "./parser"; - +import { Parse } from './parse' export function activate(ctx: vscode.ExtensionContext): void { showNewVersionMessage(ctx); ctx.subscriptions.push( @@ -15,15 +14,15 @@ export function activate(ctx: vscode.ExtensionContext): void { ); } -export function deactivate() {} +export function deactivate() { } class ReactDocumentSymbolProvider implements vscode.DocumentSymbolProvider { public provideDocumentSymbols( document: vscode.TextDocument, - token: vscode.CancellationToken ): Thenable { - return new Promise((resolve, reject) => { - const symbols = getSymbolTree(document.getText()); + return new Promise((resolve) => { + const parse = new Parse(document.getText()); + const symbols = parse.getDocumentSymbolTree(); resolve(symbols); }); } diff --git a/src/parse.ts b/src/parse.ts new file mode 100644 index 0000000..825f81d --- /dev/null +++ b/src/parse.ts @@ -0,0 +1,119 @@ +import { ParseResult, parse } from "@babel/parser"; +import traverse, { NodePath, TraverseOptions, VisitNodeObject } from "@babel/traverse"; +import { File, JSXElement, JSXFragment } from '@babel/types' +import { DocumentSymbol, Position, Range, SymbolKind } from "vscode"; +import { SourceLocation } from "estree"; + + +type Node = JSXElement | JSXFragment + +type StackItem = { + node: Node, + documentSymbol?: DocumentSymbol + children: StackItem[] +} +export class Parse { + private ast: ParseResult + private inStack: StackItem[] = [] + private tree: StackItem[] = [] + private documentSymbolTree: DocumentSymbol[] = [] + constructor(code: string) { + this.ast = parse(code, { + sourceType: "module", + plugins: ["jsx", "typescript"] + }) + this.traverse() + this.vistor() + this.tree2DocumentSymbolTree(this.tree, this.documentSymbolTree) + + } + + private getRange(loc: SourceLocation) { + if (!loc) { + throw new Error("No location"); + } + const position = new Position(loc?.start.line - 1, loc?.start.column - 1); + const range = new Range(position, position); + + return range; + } + + private generateDocumentSymbol( + name: string, + detail: string, + symbol: SymbolKind, + range: Range + ): DocumentSymbol { + return new DocumentSymbol(name, detail, symbol, range, range); + } + + private traverse() { + try { + // @ts-ignore + traverse(this.ast, this.vistor()); + } catch (err) { + console.error('traverse error', err); + throw err + } + } + + private getInStackLast(): undefined | StackItem { + return this.inStack[this.inStack.length - 1] + } + + private vistor(): TraverseOptions { + const visitNodeObject: VisitNodeObject = { + enter: (path: NodePath) => { + this.inStack.push({ + node: path.node, + children: [] + }) + }, + exit: (path: NodePath) => { + if (this.getInStackLast()?.node === path.node) { + const popItem = this.inStack.pop() + if (popItem) { + let name = '' + // @ts-ignore + if (popItem.node.openingElement?.name && popItem.node.openingElement.name.type === 'JSXMemberExpression') { + // @ts-ignore + name = popItem.node.openingElement.name.object.name + '.' + popItem.node.openingElement.name.property.name + } else { + if (popItem.node.type === 'JSXFragment') { + name = '<>' + } else { + // @ts-ignore + name = popItem.node.openingElement.name.name + } + } + popItem.documentSymbol = this.generateDocumentSymbol(name, '', SymbolKind.Variable, this.getRange(popItem.node.loc!)) + popItem.documentSymbol.children = [] + } + if (this.getInStackLast()) { + popItem && this.getInStackLast()?.children.push(popItem) + } else { + popItem && this.tree.push(popItem) + } + } + } + } + const visitor: TraverseOptions = { + 'JSXElement': visitNodeObject, + 'JSXFragment': visitNodeObject + } + return visitor + } + + getDocumentSymbolTree() { + return this.documentSymbolTree + } + + private tree2DocumentSymbolTree(tree: StackItem[], resultTree: DocumentSymbol[]) { + for (const item of tree) { + if (item.children) { + this.tree2DocumentSymbolTree(item.children, item.documentSymbol!.children) + } + resultTree.push(item.documentSymbol!) + } + } +} diff --git a/src/parser.ts b/src/parser.ts deleted file mode 100644 index b43df87..0000000 --- a/src/parser.ts +++ /dev/null @@ -1,233 +0,0 @@ -import { parse } from "@babel/parser"; -import traverse from "@babel/traverse"; -import { - ConditionalExpression, - JSXElement, - JSXExpressionContainer, - JSXFragment, - JSXMemberExpression, - LogicalExpression, - SourceLocation -} from "@babel/types"; -import { DocumentSymbol, Position, Range, SymbolKind } from "vscode"; - -type Node = - | JSXElement - | JSXFragment - | JSXMemberExpression - | ConditionalExpression - | LogicalExpression - | JSXExpressionContainer; - -export function getSymbolTree(code: string): DocumentSymbol[] { - try { - const nodes: Node[] = []; - const ast = parse(code, { - sourceType: "module", - plugins: ["jsx", "typescript"] - }); - - traverse(ast, { - JSXElement(path) { - nodes.push(path.node); - path.stop(); - }, - JSXFragment(path) { - nodes.push(path.node); - path.stop(); - } - }); - - const symbols = parseDocumentSymbols(nodes); - return symbols; - } catch (err) { - console.error("React Outline Error:", err); - return []; - } -} - -function parseDocumentSymbols(nodes: Node[]): DocumentSymbol[] { - const symbols: DocumentSymbol[] = []; - for (const node of nodes) { - const symbol = parseNode(node); - symbol && symbols.push(symbol); - } - - return symbols; -} - -function parseNode(node: Node) { - switch (node.type) { - case "JSXElement": - return parseJSXElement(node); - case "JSXFragment": - return parseJSXFragment(node); - case "ConditionalExpression": - return parseConditionalExpression(node); - case "LogicalExpression": - return parseLogicalExpression(node); - case "JSXExpressionContainer": - if (node.expression.type === "ConditionalExpression") { - return parseConditionalExpression(node); - } else if (node.expression.type === "LogicalExpression") { - return parseLogicalExpression(node); - } - default: - return null; - } -} - -function parseJSXElement(node: JSXElement) { - const { children } = node; - - const name = () => { - if (node.openingElement.name.type === "JSXMemberExpression") { - // @ts-ignore - return `${node.openingElement.name.object.name}.${node.openingElement.name.property.name}`; - } else { - return node.openingElement.name.name as string; - } - }; - - const symbol = generateDocumentSymbol( - name(), - "", - SymbolKind.Variable, - // @ts-ignore - getRange(node.loc) - ); - const childs = parseChildren(children); - childs && symbol.children.push(...childs); - return symbol; -} - -function parseJSXFragment(node: JSXFragment): DocumentSymbol { - if (!node.loc) { - throw new Error("No LOC"); - } - - let symbol: DocumentSymbol = generateDocumentSymbol( - "Fragment", - "", - SymbolKind.Function, - getRange(node.loc) - ); - const childs = parseChildren(node?.children); - childs && symbol.children.push(...childs); - return symbol; -} - -function parseLogicalExpression(node: any) { - let left, right; - const exp = node?.expression; - - // TODO: WHY DID I ADD THIS CONDITION HERE? I AM NOT TOUCHING THIS - if (exp) { - left = exp.left; - right = exp.right; - } else { - left = node.left; - right = node.right; - } - - const symbol = generateDocumentSymbol( - "LogicalExpression", - "", - SymbolKind.Class, - getRange(node.loc) - ); - - const childs = parseChildren([left, right]); - childs && symbol.children.push(...childs); - - return symbol; -} - -function parseConditionalExpression(node: any) { - const exp = node?.expression; - let alternate, consequent; - - // TODO: WHY DID I ADD THIS CONDITION HERE? I AM NOT TOUCHING THIS - if (exp) { - alternate = exp.alternate; - consequent = exp.consequent; - } else { - alternate = node.alternate; - consequent = node.consequent; - } - - const { loc } = node; - if (!loc) { - throw new Error("No location"); - } - - const symbol: DocumentSymbol = generateDocumentSymbol( - "ConditionalExpression", - "", - SymbolKind.Class, - getRange(loc) - ); - - const childs = parseChildren([alternate, consequent]); - childs && symbol.children.push(...childs); - - return symbol; -} - -// TODO: ADD TYPES HERE -// @ts-ignore -function parseChildren(children) { - const childs = []; - for (const child of children) { - const res = parseNode(child); - res && childs.push(res); - } - return childs; -} - -function getRange(loc: SourceLocation) { - if (!loc) { - throw new Error("No location"); - } - const position = new Position(loc?.start.line - 1, loc?.start.column - 1); - const range = new Range(position, position); - - return range; -} - -function generateDocumentSymbol( - name: string, - detail: string, - symbol: SymbolKind, - range: Range -): DocumentSymbol { - return new DocumentSymbol(name, detail, symbol, range, range); -} - -// TODO: PENDING WORK -const parseCallExpression = (node: any) => { - const args = node.expression.arguments; - const exp = args[0]; - - if (exp.type === "ArrowFunctionExpression") { - if ( - exp.body.type === "BlockStatement" && - exp.body.body[0].type === "ReturnStatement" - ) { - if (exp.body.body[0].argument.type === "JSXElement") { - return parseJSXElement(exp.body.body[0].argument); - } else if (exp.body.body[0].argument.type === "ConditionalExpression") { - return parseConditionalExpression(exp.body.body[0].argument); - } else if (exp.body.body[0].argument.type === "LogicalExpression") { - return parseLogicalExpression(exp.body.body[0].argument); - } - } - if (exp.body.type === "JSXElement") { - return parseJSXElement(exp.body); - } else if (exp.body.type === "ConditionalExpression") { - return parseConditionalExpression(exp.body); - } else if (exp.body.type === "LogicalExpression") { - return parseLogicalExpression(exp.body); - } - } -};