📜 JavaScript 格式化

JavaScript 代码格式化:基于词法分析规范化缩进、保留字符串与注释、支持箭头函数与解构。

等待输入...
已复制
const SAMPLE = 'function fibonacci(n){\nlet result=[];\nlet a=0,b=1;\nfor(let i=0;ix+y;\nconst greeting=`Hello, ${name}!`;'; const IND = () => { const v = document.getElementById('indent').value; return v === 'tab' ? '\t' : ' '.repeat(parseInt(v)); }; function tokenizeJs(src){ const tokens = []; let i = 0; const isName = c => /[A-Za-z_$]/.test(c); const isNameOrNum = c => /[A-Za-z0-9_$]/.test(c); const isWs = c => /\s/.test(c); while(i < src.length){ const c = src[i]; if(isWs(c)){ let s=i; while(i 0){ if(src[i] === '{') d++; else if(src[i] === '}') d--; i++; } continue; } i++; } i++; tokens.push({t:'str',v:src.slice(s,i)}); continue; } if(c === '/' && tokens.length > 0){ const last = tokens.filter(t => t.t !== 'ws').pop(); const isRegexContext = !last || /[(,=:;&|!?[{+\-*%<>?~^]/.test(last.v) || last.t === 'keyword'; if(isRegexContext){ let s = i; i++; let inClass = false; while(i < src.length && (src[i] !== '/' || inClass)){ if(src[i] === '\\'){ i += 2; continue; } if(src[i] === '[') inClass = true; else if(src[i] === ']') inClass = false; i++; } i++; while(i < src.length && /[gimsuy]/.test(src[i])) i++; tokens.push({t:'regex',v:src.slice(s,i)}); continue; } } if(c === '.' && /[0-9]/.test(src[i+1]) || /[0-9]/.test(c)){ let s = i; while(i < src.length && /[0-9.eExXa-fA-F_+\-]/.test(src[i])){ if((src[i] === '+' || src[i] === '-') && !/[eE]/.test(src[i-1])) break; i++; } tokens.push({t:'num',v:src.slice(s,i)}); continue; } if(isName(c)){ let s = i; while(i < src.length && isNameOrNum(src[i])) i++; const v = src.slice(s,i); tokens.push({t:/^(var|let|const|function|return|if|else|for|while|do|switch|case|break|continue|new|try|catch|finally|throw|class|extends|super|import|export|default|from|as|typeof|instanceof|in|of|delete|void|yield|async|await|this|null|undefined|true|false)$/.test(v) ? 'keyword' : 'name', v}); continue; } if('()[]{};,.'.includes(c)){ tokens.push({t:'punct',v:c}); i++; continue; } const ops = ['=>','...','===','!==','==','!=','<=','>=','&&','||','??','+=','-=','*=','/=','%=','**=','>>=','<<=','>>>','<<','>>','**','++','--','->','?.','?.','+=']; let matched = false; for(const op of ops){ if(src.substr(i, op.length) === op){ tokens.push({t:'op',v:op}); i += op.length; matched = true; break; } } if(matched) continue; tokens.push({t:'op',v:c}); i++; } return tokens; } function formatJs(){ setErr(''); try{ const tokens = tokenizeJs(document.getElementById('input').value); let out = ''; let depth = 0; let atLineStart = true; let parenDepth = 0; let bracketDepth = 0; let prevType = null; let prevVal = ''; const pad = () => IND().repeat(depth); const newline = () => { out = out.replace(/\s+$/, ''); out += '\n'; atLineStart = true; }; for(let k = 0; k < tokens.length; k++){ const tk = tokens[k]; if(tk.t === 'ws') continue; if(tk.t === 'comment'){ if(!atLineStart) newline(); out += pad() + tk.v; newline(); continue; } if(tk.v === '{'){ if(!atLineStart && prevVal && !/[\s(\[{,;:=+\-*%<>!|&^~?]/.test(prevVal.slice(-1))) out += ' '; out += '{'; depth++; newline(); continue; } if(tk.v === '}'){ depth = Math.max(0, depth - 1); if(!atLineStart) newline(); out += pad() + '}'; if(depth === 0 && parenDepth === 0 && bracketDepth === 0){ out += '\n\n'; atLineStart = true; } else { atLineStart = false; } prevVal = '}'; continue; } if(tk.v === '('){ parenDepth++; } if(tk.v === ')'){ parenDepth = Math.max(0, parenDepth - 1); } if(tk.v === '['){ bracketDepth++; } if(tk.v === ']'){ bracketDepth = Math.max(0, bracketDepth - 1); } if(tk.v === ';'){ out += ';'; if(parenDepth === 0 && bracketDepth === 0) newline(); else out += ' '; continue; } if(tk.v === ','){ out += ','; if(parenDepth > 0 || bracketDepth > 0) out += ' '; else newline(); continue; } let needSpace = false; if(prevType){ if(prevVal !== '(' && prevVal !== '[' && prevVal !== '.' && tk.v !== ')' && tk.v !== ']' && tk.v !== '.' && tk.v !== ',' && tk.v !== ';' && tk.v !== ':'){ if((prevType === 'name' || prevType === 'num' || prevType === 'keyword' || prevType === 'str' || prevType === 'regex') && (tk.t === 'name' || tk.t === 'num' || tk.t === 'keyword' || tk.t === 'str' || tk.t === 'regex')) needSpace = true; if((tk.t === 'op' || tk.t === 'punct') && (prevType === 'op' || prevType === 'punct')) needSpace = true; if(prevType === 'op' && tk.t === 'name' && !['++','--','!','~','+','-'].includes(prevVal)) needSpace = true; if(prevType === 'name' && tk.t === 'op' && !['++','--'].includes(tk.v) && !['.','?.'].includes(tk.v)) needSpace = true; if(prevVal === 'return' || prevVal === 'typeof' || prevVal === 'instanceof' || prevVal === 'new' || prevVal === 'throw' || prevVal === 'delete' || prevVal === 'void' || prevVal === 'in' || prevVal === 'of' || prevVal === 'await' || prevVal === 'yield') needSpace = true; if(tk.v === '=>') needSpace = true; if(prevVal === '=>') needSpace = true; } } if(atLineStart){ out += pad() + tk.v; atLineStart = false; } else { if(needSpace) out += ' '; out += tk.v; } prevType = tk.t; prevVal = tk.v; } setOut(out.replace(/\n{3,}/g, '\n\n').trim() + '\n'); setErr('✅ 格式化成功'); }catch(e){ setErr('❌ ' + e.message); } } function minifyJs(){ setErr(''); try{ const tokens = tokenizeJs(document.getElementById('input').value); let out = ''; let prevType = null; let prevVal = ''; for(const tk of tokens){ if(tk.t === 'ws' || tk.t === 'comment') continue; if(tk.v === 'regex'){ out += tk.v; prevType = 'regex'; prevVal = tk.v; continue; } let needSpace = false; if(prevType){ if((prevType === 'name' || prevType === 'num' || prevType === 'keyword') && (tk.t === 'name' || tk.t === 'num' || tk.t === 'keyword')) needSpace = true; if(prevVal === 'return' || prevVal === 'typeof' || prevVal === 'instanceof' || prevVal === 'new' || prevVal === 'throw' || prevVal === 'delete' || prevVal === 'void' || prevVal === 'in' || prevVal === 'of' || prevVal === 'await' || prevVal === 'yield') needSpace = true; if(prevVal === '++' || prevVal === '--'){ const pl = (out.match(/\b[a-zA-Z_$][a-zA-Z0-9_$]*$/) || [])[0]; if(pl){ needSpace = false; } } } if(needSpace) out += ' '; out += tk.v; prevType = tk.t; prevVal = tk.v; } setOut(out); setErr('✅ 压缩成功'); }catch(e){ setErr('❌ ' + e.message); } } function doBeautify(){ formatJs(); } function doMinify(){ minifyJs(); } function doValidate(){ setErr(''); try{ const src = document.getElementById('input').value; const tokens = tokenizeJs(src); let braceO = 0, braceC = 0, parenO = 0, parenC = 0, brackO = 0, brackC = 0; for(const tk of tokens){ if(tk.v === '{') braceO++; if(tk.v === '}') braceC++; if(tk.v === '(') parenO++; if(tk.v === ')') parenC++; if(tk.v === '[') brackO++; if(tk.v === ']') brackC++; } const stats = '✅ JavaScript 分析\n' + '总 Token: ' + tokens.length + '\n' + '行数: ' + src.split('\n').length + '\n' + '字符数: ' + src.length + '\n' + '{ }: ' + braceO + ' / ' + braceC + '\n' + '( ): ' + parenO + ' / ' + parenC + '\n' + '[ ]: ' + brackO + ' / ' + brackC + '\n' + 'function 数量: ' + (src.match(/\bfunction\b/g) || []).length + '\n' + 'const/let 数量: ' + (src.match(/\b(const|let)\b/g) || []).length; setOut(stats); if(braceO !== braceC) setErr('❌ 花括号不平衡:{ ' + braceO + ' / } ' + braceC); else if(parenO !== parenC) setErr('❌ 圆括号不平衡:( ' + parenO + ' / ) ' + parenC); else if(brackO !== brackC) setErr('❌ 方括号不平衡:[ ' + brackO + ' / ] ' + brackC); else setErr('✅ 校验通过'); }catch(e){ setErr('❌ ' + e.message); } } function getIndent(){const v=document.getElementById('indent').value;return v==='tab'?'\t':' '.repeat(parseInt(v));} function setOut(s){document.getElementById('output').textContent=s;} function setErr(s){const e=document.getElementById('err');e.textContent=s||'';e.className='err'+(s?'':' ok');} function clearAll(){document.getElementById('input').value='';setOut('等待输入...');setErr('');} function loadSample(){document.getElementById('input').value=SAMPLE;doBeautify();}