/
sheonn
/
ObjectScriptFormatter
Обзор
Документация
Войти
/
sheonn
/
ObjectScriptFormatter
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/formatter.ts
217 строк
9 KB
Sheonn
preliminary version
19 май 2026, 11:29
19 май 2026, 11:29
ae87253
Код
Авторство
О чём код?
import { Parser, Query, Language, Tree } from 'web-tree-sitter'; import { TextEdit, Range, Position } from 'vscode'; import { ConditionType, Condition, Conditions } from "./conditions"; import { TSListener } from "./listener"; export class TSFormatter { private parser: Parser; private language: Language; private edits: TextEdit[] = []; private source: string = ""; constructor(parser: Parser, language: Language) { this.parser = parser; this.language = language; } public formatCode(source: string): TextEdit[] { const tree = this.parser.parse(source); if (!tree) { return []; // Возвращаем пустой массив, если не удалось разобрать код } this.edits = []; // Очищаем массив от предыдущих форматирований this.source = source; // Сохраняем исходный код для дальнейшего использования this.formatIndent(tree); // Сортируем TextEdit по убыванию позиции начала this.edits.sort((a, b) => { if (a.range.start.line !== b.range.start.line) { return b.range.start.line - a.range.start.line; } return b.range.start.character - a.range.start.character; }); return this.edits; } private formatIndent(tree: Tree) { const conditions = new Conditions(); const queryText = ` [ (method_definition "{" @append_indent "}" @prepend_indent ) (command_if "{" @append_indent "}" @prepend_indent) (command_for "{" @append_indent "}" @prepend_indent) (elseif_block "{" @append_indent "}" @prepend_indent) (else_block "{" @append_indent "}" @prepend_indent) (command_while "{" @append_indent "}" @prepend_indent) (command_dowhile "{" @append_indent "}" @prepend_indent) ] [ (binary_operator) ] @prepend_space @append_space (set_argument lhs: (glvn) "=" @prepend_space @append_space rhs: (expression)) ( "," @prepend_antispace @append_space ) (post_conditional (expression) @wrap_parens . ) `; const query: Query = new Query(this.language, queryText); for (const { captures } of query.matches(tree.rootNode)) { for (const capture of captures) { switch (capture.name) { case "append_indent": conditions.add(capture.node, new Condition(ConditionType.AppendIndent, capture.node)); break; case "prepend_indent": conditions.add(capture.node, new Condition(ConditionType.PrependIndent, capture.node)); break; case "append_space": conditions.add(capture.node, new Condition(ConditionType.AppendSpace, capture.node)); break; case "prepend_space": conditions.add(capture.node, new Condition(ConditionType.PrependSpace, capture.node)); break; case "prepend_antispace": conditions.add(capture.node, new Condition(ConditionType.PrependAntispace, capture.node)); break; case "wrap_parens": conditions.add(capture.node, new Condition(ConditionType.WrapParens, capture.node)); break; default: break; } } } // проходим по всем узлам и форматируем по заданным условиям // Размер отступа (1 табуляция) const indentSize = 1; let line = 0; let indentLevel = 0; const listener = new TSListener(tree); while (listener.nextNode()) { const currentNode = listener.getNode(); const rownum = currentNode.startPosition.row; const conditionList = conditions.get(currentNode); conditionList.forEach(condition => { switch (condition.type) { case ConditionType.PrependIndent: indentLevel -= 1; break; case ConditionType.AppendIndent: line = currentNode.startPosition.row; indentLevel += 1; break; case ConditionType.AppendSpace: const acountws = this.getCountSpace(currentNode.endIndex, false); if (acountws === 0) { this.edits.push( TextEdit.insert(new Position(rownum, currentNode.endPosition.column), " ") ); } else if (Math.abs(acountws) > 1) { this.edits.push( TextEdit.replace(new Range( new Position(rownum, currentNode.endPosition.column), new Position(rownum, currentNode.endPosition.column + acountws)), " ") ); } break; case ConditionType.PrependSpace: const pcountws = this.getCountSpace(currentNode.startIndex - 1, true); if (pcountws === 0) { this.edits.push( TextEdit.insert(new Position(rownum, currentNode.startPosition.column + pcountws), " ") ); } else if (Math.abs(pcountws) > 1) { this.edits.push( TextEdit.replace(new Range( new Position(rownum, currentNode.startPosition.column + pcountws), new Position(rownum, currentNode.startPosition.column)), " ") ); } break; case ConditionType.PrependAntispace: const pcountaws = this.getCountSpace(currentNode.startIndex - 1, true); if (Math.abs(pcountaws) > 0) { this.edits.push( TextEdit.delete(new Range( new Position(rownum, currentNode.startPosition.column + pcountaws), new Position(rownum, currentNode.startPosition.column))) ); } break; case ConditionType.WrapParens: if (this.source[currentNode.startIndex] !== "(") { this.edits.push( TextEdit.insert(new Position(rownum, currentNode.startPosition.column), "(") ); this.edits.push( TextEdit.insert(new Position(rownum, currentNode.endPosition.column), ")") ); } break; default: break; } }); if ((indentLevel > 0) && (currentNode.startPosition.row > line) &&(currentNode.startPosition.column !== 0)) { line = currentNode.startPosition.row; const desiredIndent = "\t".repeat(indentLevel * indentSize); const startRange = new Position(line, 0); const endRange = new Position(line, currentNode.startPosition.column); this.edits.push(TextEdit.replace(new Range(startRange, endRange), desiredIndent)); } else { line = currentNode.startPosition.row; } } } private getCountSpace(startIndex: number, reverse: boolean = false) { let count = 0; while (this.source[startIndex + count] === " ") { if (reverse) { count--; } else { count++; } } return count; } }