UNPKG

pss-langserver

Version:

A Language server for the Portable Stimulus Standard

275 lines (273 loc) 11.5 kB
"use strict"; /* * Copyright (C) 2025 Darshan(@thisisthedarshan) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatDocument = formatDocument; exports.formatFileHeader = formatFileHeader; const formattingHelper_1 = __importDefault(require("./formattingHelper")); function formatDocument(fileName, text, tabspace, author, patterns, formatHeader, maxColumns) { /* First format curly braces */ let doc = formatCurlyBraces(text); /* Then add spaces after commas */ doc = formatCommas(doc); /* Format multi-line comments: */ doc = formatMultilineComments(doc); /* Then format semicolons */ doc = addNewlinesAfterSemicolons(doc); /* Then start by formatting patterns - beautification */ doc = (0, formattingHelper_1.default)(doc, patterns); /* Add spaces when using keywords */ doc = doc.replace(/\b(if|for|while|repeat)(?=\()/g, '$1 '); /* Process line by line */ let lines = doc.split('\n'); const formattedLines = []; let indentLevel = 0; /* Start with indentLevel of 0 */ let isInBlockComment = false; for (let line of lines) { /* Keep empty newlines as it is */ if (line.trim() === '') { formattedLines.push(''); // Keep the empty line as-is continue; } line = line.trim(); /* Format specific syntax */ line = formatOperators(line); line = formatSingleLineComments(line); /* Handle closing braces */ if (line.startsWith('}') && !isInBlockComment && !(/\/\//.test(line))) { indentLevel = Math.max(indentLevel - tabspace, 0); } /* Check if comment block is encountered */ if (line.startsWith("/*")) { isInBlockComment = true; } if (line.endsWith("*/")) { isInBlockComment = false; if (!line.startsWith("/*")) { line = line.replace(/^(?!.*\/\*).*?\*\/$/, (match) => match.replace(/(\*\/)/, ' $1')); } } /* Check if still in comment */ if (isInBlockComment) { if (line.startsWith("*")) { line = ` ${line}`; // Add an extra space } } /* Add indentation */ const indentedLine = `${' '.repeat(indentLevel)}${line}`; /* Wrap the line if necessary */ const wrappedLines = wrapLine(indentedLine, indentLevel, tabspace, maxColumns); formattedLines.push(...wrappedLines); /* Handle opening braces */ if (line.endsWith('{') && !isInBlockComment && !(/\/\/|\/\*/.test(line))) { indentLevel += tabspace; } } let formattedFile = formattedLines.join('\n'); if (formatHeader) { formatFileHeader(formattedFile, fileName, "sad", "dsa", author); } return formattedFile; } function wrapLine(line, indentLevel, tabspace, maxColumns) { if (maxColumns === 0 || line.length <= maxColumns) { return [line]; } const words = line.split(' '); const wrappedLines = []; let currentLine = ''; const baseIndent = ' '.repeat(indentLevel); const doubleIndent = ' '.repeat(indentLevel * 2); for (const word of words) { const separator = currentLine ? ' ' : ''; if (currentLine.length + separator.length + word.length > maxColumns) { if (currentLine) { wrappedLines.push(currentLine.trimEnd()); } currentLine = doubleIndent + word; } else { currentLine += separator + word; } } if (currentLine) { wrappedLines.push(currentLine.trimEnd()); } /* Ensure the first line retains the original indent */ if (wrappedLines.length > 0) { wrappedLines[0] = baseIndent + wrappedLines[0].trimStart(); } return wrappedLines; } function formatCurlyBraces(input) { // Remove newline before `{` and move it back to the previous line input = input.replace(/\n\s*{/g, ' {'); // Ensure there is exactly 1 space before the opening `{` input = input.replace(/\s*{/g, ' {'); // Ensure there is always a newline after the opening `{` without removing existing newlines input = input.replace(/({)(?!\n)/g, '$1\n'); // Ensure `}` is on its own line and add a newline before it if needed input = input.replace(/([^\n])(\s*})(?!\n)/g, (match, p1, p2) => { // Only add newline if there isn't already a newline before the closing brace return /\n/.test(match) ? match : p1 + '\n' + p2; }); // Add a newline after `}` only if there is no newline already input = input.replace(/}(?!\n)(?!\s*\n)(?!;)/g, '}\n'); return input; } function addNewlinesAfterSemicolons(input) { // Step 1: Add newline after semicolon if not followed by newline or comment (single-line or multi-line) input = input.replace(/;(?!\s*(?:\n|\/\/|\/\*[^*]*\*\/))(?=\s*(?!\n))(?=\s*(?![^*]*\*\/)[^]*\n)/g, ';\n'); return input; // No need for .trim() to avoid stripping empty lines } function formatCommas(input) { // Add a space after every comma if there isn't already one return input.replace(/,\s*/g, ', '); } function formatMultilineComments(documentText) { return documentText.replace(/(\/\*\*?)([\s\S]*?)\*\//g, (match, openingBlock, commentBody) => { const opening = openingBlock; const closing = '*/'; // Handle single-line comments if (!commentBody.includes('\n')) { return `${opening} ${commentBody.trim()} ${closing}`.replace(/\s+/g, ' '); } // Handle multiline comments const newlineIndex = commentBody.indexOf('\n'); let firstPart = ''; let rest = commentBody; // Split into first part (before first newline) and the rest if (newlineIndex !== -1) { firstPart = commentBody.substring(0, newlineIndex).trim(); rest = commentBody.substring(newlineIndex + 1); } else { firstPart = commentBody.trim(); rest = ''; } // Check if first part is all asterisks if (/^\*+$/.test(firstPart)) { let formattedComment = `${opening} ${firstPart}`; // Process the remaining lines if any if (rest) { const lines = rest.split('\n'); const formattedLines = lines.map((line) => { const trimmedLine = line.trim(); if (trimmedLine === '' || trimmedLine === '*') { return null; } else if (trimmedLine.startsWith('*')) { return `* ${trimmedLine.slice(1).trim()}`; } else { return `* ${trimmedLine}`; } }).filter((line) => line !== null); formattedComment += '\n' + formattedLines.join('\n'); } formattedComment += '\n' + closing; return formattedComment; } else { // First part is not all asterisks, format with * on each line const lines = commentBody.split('\n'); const formattedLines = lines.map((line) => { const trimmedLine = line.trim(); if (trimmedLine === '' || trimmedLine === '*') { return null; } else if (trimmedLine.startsWith('*')) { return `* ${trimmedLine.slice(1).trim()}`; } else { return `* ${trimmedLine}`; } }).filter((line) => line !== null); return `${opening}\n${formattedLines.join('\n')}\n${closing}`; } }); } function formatOperators(input) { const operatorRegex = /([^\s])([\+\-\*\/\%\^=<>!&|]+)([^\s])/g; function formatExpression(expression) { return expression.replace(operatorRegex, (match, left, ops, right) => { if ((left === '/' && (ops === '*' || ops === '**')) || (ops === '*' && right === '/')) { return match; } if (ops.length === 1) { return `${left} ${ops} ${right}`; } else { return match; } }); } function formatNested(content) { let previousContent; do { previousContent = content; content = content.replace(/\(([^()]+)\)/g, (match, innerContent) => { return `(${formatExpression(innerContent)})`; }); } while (content !== previousContent); return formatExpression(content); } return input.replace(/\/\*[\s\S]*?\*\//g, match => match) // Ignore multiline comments .replace(/\/\/[^\n]*/g, match => match) // Ignore single-line comments .replace(/['"`][^'"`]*['"`]/g, match => match) // Ignore strings .replace(/\bhttps?:\/\/[^\s)]+/g, match => match) // Ignore URLs .replace(/[^\s()]+/g, formatNested); } function formatSingleLineComments(line) { // Ensure there is a space before `//`, but ignore URLs starting with `://` line = line.replace(/([^:])\/\/(?! )/g, '$1 // '); // Add a space before `//` if not preceded by a colon // Ensure there is a space after `//`, but ignore if it's part of a URL (contains colon before `//`) line = line.replace(/([^:])\/\/(?! )/g, '$1 // '); // Ensures space after `//` if not already present and not part of a URL return line; } function formatFileHeader(content, fileName, creationDate, lastModifiedDate, author) { const headerRegex = /^\/\*\*[\s\S]*?\*\/\n?/; // Match the header block only at the top of the file const lastModifiedRegex = /(Last Modified on: ).*/; // If a header already exists, update "Last Modified on:" if (headerRegex.test(content)) { return content.replace(headerRegex, (header) => { // Update or add the "Last Modified on:" field in the existing header if (lastModifiedRegex.test(header)) { return header.replace(lastModifiedRegex, `$1${lastModifiedDate}`); } else { // Add "Last Modified on:" if it doesn't exist const headerLines = header.split('\n'); headerLines.splice(headerLines.length - 1, 0, ` * Last Modified on: ${lastModifiedDate}`); return headerLines.join('\n'); } }); } // If no header exists, add a new one at the top const newHeader = `/** * @file ${fileName} * @author ${author} * @brief * @date ${creationDate} * Last Modified on: ${lastModifiedDate} */ `; return newHeader + content; }