JS Comment Remover + Syntax Check

Remove comments from JS code, show syntax errors with line numbers (EN/TR/DE)

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

Advertisement:

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

Advertisement:

// ==UserScript==
// @name         JS Comment Remover + Syntax Check
// @name:tr      JS Yorum Temizleyici + Sözdizimi Denetimi
// @name:de      JS Kommentar-Entferner + Syntax-Prüfung
// @description  Remove comments from JS code, show syntax errors with line numbers (EN/TR/DE)
// @description:tr JS kodundaki yorumları siler, sözdizimi hatasını satır numarasıyla gösterir (EN/TR/DE)
// @description:de Entfernt Kommentare aus JS-Code, zeigt Syntaxfehler mit Zeilennummer (EN/TR/DE)
// @namespace    https://tampermonkey.net/
// @version      4.0
// @author       Cleaner
// @match        *://*/*
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // Dil sözlüğü (kısa)
    const lang = {
        en: {
            title: "JS Cleaner",
            inputLabel: "Your code (with comments)",
            outputLabel: "Clean code (no comments)",
            placeholder: "Paste JavaScript code here...",
            cleanBtn: "Remove comments",
            copyBtn: "Copy",
            clearBtn: "Clear",
            syntaxError: "Syntax error",
            line: "Line",
            copied: "Copied!",
            noCode: "No code to copy"
        },
        tr: {
            title: "JS Temizleyici",
            inputLabel: "Kodunuz (yorumlu)",
            outputLabel: "Temiz kod (yorumsuz)",
            placeholder: "JavaScript kodunu yapıştır...",
            cleanBtn: "Yorumları sil",
            copyBtn: "Kopyala",
            clearBtn: "Temizle",
            syntaxError: "Sözdizimi hatası",
            line: "Satır",
            copied: "Kopyalandı!",
            noCode: "Kopyalanacak kod yok"
        },
        de: {
            title: "JS Reiniger",
            inputLabel: "Ihr Code (mit Kommentaren)",
            outputLabel: "Sauberer Code (ohne Kommentare)",
            placeholder: "JavaScript-Code einfügen...",
            cleanBtn: "Kommentare entfernen",
            copyBtn: "Kopieren",
            clearBtn: "Löschen",
            syntaxError: "Syntaxfehler",
            line: "Zeile",
            copied: "Kopiert!",
            noCode: "Kein Code zum Kopieren"
        }
    };

    let currentLang = 'en'; // varsayılan

    // Still (pastel çocuk, ama sade)
    GM_addStyle(`
        .js-cleaner-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(252, 245, 235, 0.97);
            backdrop-filter: blur(4px);
            z-index: 999999;
            display: flex;
            justify-content: center;
            align-items: center;
            font-family: 'Segoe UI', 'Comic Neue', monospace;
        }
        .js-cleaner-container {
            width: 90vw;
            height: 85vh;
            background: #fffdf5;
            border-radius: 40px;
            box-shadow: 0 20px 40px rgba(0,0,0,0.1);
            display: flex;
            flex-direction: row;
            overflow: hidden;
            border: 3px solid #f5d7b0;
        }
        .js-cleaner-pane {
            flex: 1;
            display: flex;
            flex-direction: column;
            padding: 20px;
            gap: 12px;
        }
        .left { background: #fff8ef; border-right: 2px dotted #f5d7b0; }
        .right { background: #fafaf0; }
        .title-bar {
            display: flex;
            justify-content: space-between;
            align-items: center;
            font-weight: bold;
            color: #b47c48;
        }
        .lang-select {
            background: #fef0e0;
            border: 1px solid #f5d7b0;
            border-radius: 30px;
            padding: 4px 10px;
            font-family: monospace;
            cursor: pointer;
        }
        .code-area {
            flex: 1;
            display: flex;
            gap: 6px;
            background: #fffef7;
            border-radius: 30px;
            border: 1px solid #f0dbc0;
            overflow: auto;
        }
        .line-numbers {
            background: #f9efdf;
            padding: 12px 6px;
            text-align: right;
            font-family: monospace;
            font-size: 12px;
            color: #b68b54;
            user-select: none;
            border-right: 1px solid #eedbc8;
            min-width: 40px;
            white-space: pre;
            line-height: 1.5;
        }
        textarea, pre {
            flex: 1;
            padding: 12px;
            font-family: 'Fira Code', monospace;
            font-size: 13px;
            line-height: 1.5;
            background: transparent;
            border: none;
            resize: none;
            outline: none;
            color: #3a5e3f;
        }
        pre {
            margin: 0;
            white-space: pre-wrap;
        }
        .error-box {
            background: #ffe8e6;
            border-left: 8px solid #f3a683;
            padding: 6px 12px;
            border-radius: 30px;
            color: #bc6f4b;
            font-size: 12px;
            font-family: monospace;
        }
        button {
            background: #f9efdf;
            border: 1px solid #f5d7b0;
            padding: 6px 16px;
            border-radius: 40px;
            cursor: pointer;
            font-family: monospace;
            font-weight: bold;
            color: #8b6946;
        }
        button.primary {
            background: #ffe4c4;
            border-color: #e6bc8b;
        }
        button:hover {
            background: #ffe0c0;
        }
        .toast {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: #d9e3ce;
            color: #4a5b4c;
            padding: 8px 18px;
            border-radius: 40px;
            opacity: 0;
            transition: 0.2s;
            pointer-events: none;
            font-size: 13px;
        }
        @media (max-width: 700px) {
            .js-cleaner-container { flex-direction: column; }
            .left { border-right: none; border-bottom: 2px dotted #f5d7b0; }
        }
    `);

    // Yorum temizleme (gelişmiş - tüm satırı siler)
    function stripComments(code) {
        const strings = [];
        const stringRegex = /(['"`])(?:(?!\1|\\)[\s\S]|\\.)*\1/g;
        let processed = code.replace(stringRegex, (m) => {
            strings.push(m);
            return `__JS_STR_${strings.length-1}__`;
        });

        const regexes = [];
        const regexLiteralRegex = /\/(?![*\/])(?:[^\/\\\n\r]|\\.)*\/[gimyus]*/g;
        processed = processed.replace(regexLiteralRegex, (m) => {
            regexes.push(m);
            return `__JS_REGEX_${regexes.length-1}__`;
        });

        // Tek satır yorum: satır başındaki boşluk/tab + // + her şey -> tüm satır silinir
        let noComments = processed.replace(/^\s*\/\/.*$/gm, '');
        // Çok satırlı yorum
        noComments = noComments.replace(/\/\*[\s\S]*?\*\//g, '');

        noComments = noComments.replace(/__JS_REGEX_(\d+)__/g, (_, i) => regexes[parseInt(i)]);
        noComments = noComments.replace(/__JS_STR_(\d+)__/g, (_, i) => strings[parseInt(i)]);

        // Boş satırları temizle (isteğe bağlı)
        return noComments.replace(/^\s*[\r\n]+/gm, '');
    }

    // Syntax kontrolü
    function checkSyntax(code) {
        try {
            new Function(code);
            return { ok: true };
        } catch (err) {
            let line = null;
            let match = err.message.match(/line (\d+)/i);
            if (match) line = parseInt(match[1]);
            if (!line && err.stack) {
                let m = err.stack.match(/<anonymous>:(\d+):/);
                if (m) line = parseInt(m[1]);
            }
            return { ok: false, error: err.message, line };
        }
    }

    // Satır numarası üret
    function lineNumbers(text) {
        if (!text) return '';
        const lines = text.split('\n');
        let out = '';
        for (let i = 1; i <= lines.length; i++) out += i + '\n';
        return out;
    }

    let panel = null;
    let currentTrans = lang.en;

    function updateTexts() {
        currentTrans = lang[currentLang];
        const elements = {
            title: document.querySelector('#panelTitle'),
            leftLabel: document.querySelector('#leftLabel'),
            rightLabel: document.querySelector('#rightLabel'),
            input: document.querySelector('#inputCode'),
            cleanBtn: document.querySelector('#cleanBtn'),
            clearBtn: document.querySelector('#clearBtn'),
            copyBtn: document.querySelector('#copyBtn')
        };
        if (elements.title) elements.title.innerText = currentTrans.title;
        if (elements.leftLabel) elements.leftLabel.innerText = currentTrans.inputLabel;
        if (elements.rightLabel) elements.rightLabel.innerText = currentTrans.outputLabel;
        if (elements.input) elements.input.placeholder = currentTrans.placeholder;
        if (elements.cleanBtn) elements.cleanBtn.innerText = currentTrans.cleanBtn;
        if (elements.clearBtn) elements.clearBtn.innerText = currentTrans.clearBtn;
        if (elements.copyBtn) elements.copyBtn.innerText = currentTrans.copyBtn;
        
        // Hata mesajı varsa güncelle
        const errBox = document.querySelector('#errorBox');
        if (errBox && errBox.innerText) {
            // içerik değişebilir, ama dil değişince resetlemek daha iyi
            validate();
        }
    }

    function showToast(msg) {
        let t = document.querySelector('.toast');
        if (!t) {
            t = document.createElement('div');
            t.className = 'toast';
            document.body.appendChild(t);
        }
        t.textContent = msg;
        t.style.opacity = '1';
        setTimeout(() => t.style.opacity = '0', 1500);
    }

    function copyText(text) {
        GM_setClipboard(text, 'text');
        showToast(currentTrans.copied);
    }

    function validate() {
        const input = document.querySelector('#inputCode');
        const errBox = document.querySelector('#errorBox');
        if (!input) return;
        const code = input.value;
        if (code.trim() === '') {
            errBox.style.display = 'none';
            return;
        }
        const res = checkSyntax(code);
        if (!res.ok) {
            errBox.style.display = 'block';
            const lineInfo = res.line ? ` (${currentTrans.line} ${res.line})` : '';
            errBox.innerHTML = `${currentTrans.syntaxError}${lineInfo}: ${res.error}`;
        } else {
            errBox.style.display = 'none';
        }
    }

    function cleanAndShow() {
        const input = document.querySelector('#inputCode');
        const output = document.querySelector('#outputCode');
        const code = input.value;
        if (code.trim() === '') {
            output.innerText = '';
            document.querySelector('#rightLineNumbers').textContent = '';
            return;
        }
        const cleaned = stripComments(code);
        output.innerText = cleaned;
        document.querySelector('#rightLineNumbers').textContent = lineNumbers(cleaned);
    }

    function createPanel() {
        if (panel) return;
        const overlay = document.createElement('div');
        overlay.className = 'js-cleaner-overlay';
        overlay.innerHTML = `
            <div class="js-cleaner-container">
                <div class="js-cleaner-pane left">
                    <div class="title-bar">
                        <span id="leftLabel">${currentTrans.inputLabel}</span>
                        <select id="langSelect" class="lang-select">
                            <option value="en" ${currentLang==='en'?'selected':''}>🇬🇧 English</option>
                            <option value="tr" ${currentLang==='tr'?'selected':''}>🇹🇷 Türkçe</option>
                            <option value="de" ${currentLang==='de'?'selected':''}>🇩🇪 Deutsch</option>
                        </select>
                    </div>
                    <div class="code-area">
                        <div class="line-numbers" id="leftLineNumbers"></div>
                        <textarea id="inputCode" placeholder="${currentTrans.placeholder}"></textarea>
                    </div>
                    <div id="errorBox" class="error-box" style="display:none;"></div>
                    <div style="display:flex; gap:10px; justify-content:flex-end;">
                        <button id="clearBtn">${currentTrans.clearBtn}</button>
                        <button id="cleanBtn" class="primary">${currentTrans.cleanBtn}</button>
                    </div>
                </div>
                <div class="js-cleaner-pane right">
                    <div class="title-bar">
                        <span id="rightLabel">${currentTrans.outputLabel}</span>
                        <button id="copyBtn">${currentTrans.copyBtn}</button>
                    </div>
                    <div class="code-area">
                        <div class="line-numbers" id="rightLineNumbers"></div>
                        <pre id="outputCode"></pre>
                    </div>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);
        panel = overlay;

        const input = overlay.querySelector('#inputCode');
        const output = overlay.querySelector('#outputCode');
        const leftLines = overlay.querySelector('#leftLineNumbers');
        const rightLines = overlay.querySelector('#rightLineNumbers');
        const cleanBtn = overlay.querySelector('#cleanBtn');
        const clearBtn = overlay.querySelector('#clearBtn');
        const copyBtn = overlay.querySelector('#copyBtn');
        const langSelect = overlay.querySelector('#langSelect');
        const errBox = overlay.querySelector('#errorBox');

        function updateLeftLines() {
            leftLines.textContent = lineNumbers(input.value);
        }

        input.addEventListener('input', () => {
            updateLeftLines();
            clearTimeout(window.validateTimer);
            window.validateTimer = setTimeout(validate, 300);
        });

        cleanBtn.addEventListener('click', () => {
            cleanAndShow();
        });

        clearBtn.addEventListener('click', () => {
            input.value = '';
            output.innerText = '';
            leftLines.textContent = '';
            rightLines.textContent = '';
            errBox.style.display = 'none';
            updateLeftLines();
        });

        copyBtn.addEventListener('click', () => {
            const cleanCode = output.innerText;
            if (cleanCode.trim()) copyText(cleanCode);
            else showToast(currentTrans.noCode);
        });

        langSelect.addEventListener('change', (e) => {
            currentLang = e.target.value;
            updateTexts();
            validate(); // hatayı yeniden göster (dil değişince)
            cleanAndShow(); // çıktıyı yenile (ama içerik aynı)
        });

        updateLeftLines();
        input.focus();
    }

    document.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.shiftKey && e.key === 'Y') {
            e.preventDefault();
            if (!panel) createPanel();
            else {
                panel.remove();
                panel = null;
            }
        }
    });
})();