用JAVA写了一个简单的JS代码格式化工具

时间:2023-02-01 03:53:37
Section I: 为什么写这个小玩意
1. 手上有些JS代码,因为某种原因,起代码中没有任何回车、制表符。在编辑器中打开时,显示的就是一行。而我实现想研究和学习这些写得比较漂亮的JS代码,
2. 可是好想没有哪个编辑器自带了JS格式化的功能。它们都可以给JS着色,但就是不能控制代码格式,真是遗憾。
3. 我是我去网上搜看有没有前人做过这个东东,发现有个JS写的JS代码格式化工具,相当不错,可以给代码格式化,还可以着色。只不过结果都是显示在网页上,复制下来的时候那些制表符都没有了,所以贴到编辑器中时,代码还是比较混乱;而且这个每次只能格式话一个文件,还得自己手动去Copy & paste;而且因为是JS写的,速度也受到了不小的限制;
基于以上原因,我准备自己些一个小工具来格式化我需要的代码:

Section II: 功能
1. 因为很多编辑器都有JS的着色功能,所一我需要的仅仅是一个格式化功能而已,那就是主要将那种故意将代码中的空白符去掉的的JS加上换行、制表符等等。
大概有一下几种情况:
  1). add /n/n before "function";
  2). add /n after ";" (Except "for", and ";" between " " which is part of a string in javascript. ) and /t before the next line;
  3). add "/n" and "/t" after "{";
  4). add "/n" and "/t" before "}".
2. 不仅可以格式话一个给定的文件,还可以格式化给定的一个文件夹里的所有的JS文件。这个就是通过传给Main函数的参数来实现的。参数可以如下:
  1)格式化一个文件:
    -f path/file.js 
  或者:
    --file path/file.js
  2) 格式化给定的一个文件夹里的所有的JS文件
    -d path/directory
  或者:
    --directory path/directory

举例:要格式化/home/newweapon/js/ 这个文件夹中的所有JS文件,可一使用如下命令:
 JsFormatter -d /home/newweapon/js

Section III 缺陷
目前写的这个程序还是一个比较脆弱的程序。有很多情况没有考虑到,我在注释中把我想到的需要提高的基本都提了一下。但是因为我写这个小玩意主要是想把我自己下的一些JS代码格式化一下,而这个对我来说已经够用了,再加上自己的水平有限,所以没有将它写得很完美。等以后有时间了,再对这个程序做一些更新。

Section IV 代码
/**
 * cn.newweapon.JsFormatter
 
*/
package  cn.newweapon;

import  java.io.File;
import  java.io.FileReader;
import  java.io.FileWriter;
import  java.io.FilenameFilter;
import  java.io.IOException;
import  java.util.ArrayList;
import  java.util.Iterator;
import  java.util.List;
import  java.util.regex.Pattern;

/**
 * 
@author  Newweapon @ ustc [Email: newweapon111 (at) gmail.com]
 * 
@version  0.0.1
 * @Created 20071015
 * 
 * @TODO 1. user can specify the formatted file name;<br/>
 *         2. User can located the formatted files to a specified folder.<br/> 
 *         3. If a file is formatted partly already. Delete all blank characters first, and then format the file.<br/>
 
*/
public   class  JsFormatter {
    
    
/*  ============ constants begin ===================  */
    
/**  Usage  */
    
public   static  String USAGE  =   " Usage:  JsFormatter -d path/directory  JsFormatter -f path/filename.js " ;
    
    
/**  Type: Directory = "0"; File = "1"  */
    
public   interface  Type {
        
/**  Directory: 0  */
        
public   static  String DIRECTORY  =   " 0 " ;
        
/**  File: 1  */
        
public   static  String FILE  =   " 1 " ;
    }
    
/*  ============ constants end ===================  */
    
    
/**
     * Entry point of the project.
     * 
     * 
@param  args Like "-d /path/directory" or "-f /path/file.js"
     
*/
    
public   static   void  main(String[] args) {
        String startMsg 
=   " Processing... " ;
        String finishMsg 
=   " Finished. "
            
        
//  Parameters check. 
         if (args.length  !=   2 ) {
            System.err.println(USAGE);
            
return ;
        }
        
//  Get the two parameters.
        String type  =  args[ 0 ];
        String path 
=  args[ 1 ];
        
//  Parameters check.
         if ( " -d " .equals(type)  ||   " --directory " .equals(type)) {
            type 
=  Type.DIRECTORY;
        } 
else   if ( " -f " .equals(type)  ||   " --file " .equals(type)) {
            type 
=  Type.FILE;
        } 
else  {
            System.err.println(USAGE);
            
return ;
        }
        
//  Check file type 
         if (Type.FILE.equals(type)) {
            
if (path.length()  <=   3   ||   ! isJsFile(path)) {
                System.err.println(
" The file must be a JS file. " );
                
return ;
            }
        }
        
        
//  Start message
        System.out.println(startMsg);
        
        
//  Format file(s)
         try  {
            
if (Type.FILE.equals(type)) {
                formatFile(path);
            } 
else  {
                List
< String >  jsFileList  =  getJsFileList(path);
                Iterator
< String >  it  =  jsFileList.iterator();
                
while (it.hasNext()) {
                    formatFile(it.next());
                }
                finishMsg 
+=   "  ( "   +  jsFileList.size()  +   "  file(s) formatted) " ;
            }
        } 
catch  (Exception e) {
            e.printStackTrace();
            
return ;
        }
        
        
//  Finish message
        System.out.println(finishMsg);
    }

    
/**  
     * Format a JS file.
     * 
     * 
@param  fileName The file name of the file which is to be formatted.
     * 
@return  String The formatted string.
     * 
@throws  IOException Exception when open, read and write file.
     
*/
    
private   static   void  formatFile(String fileName)  throws  IOException {
        String formattedFileName 
=  fileName  +   " .formatted " ;
        
        FileReader fr 
=   new  FileReader(fileName);
        FileWriter fw 
=   new  FileWriter(formattedFileName);
        
        String lastWord 
=   "" ;
        
        
int  forCount  =   0 ;
        
int  quoteCount  =   0 ;
        
int  sigleQuoteCount  =   0 ;
        
int  bracketCount  =   0 ;
        
        
int  thisChar  =   0 ;
        
int  lastChar  =   0 ;
        
int  nextChar  =   0 ;
        
        thisChar 
=  fr.read();
        
if (thisChar  !=   - 1 ) {
            nextChar 
=  fr.read();
        }
        
        
while (thisChar  !=   - 1 ) {
            
//  find and replace
             switch (thisChar) {
            
//  2. add   after ";" (Except "for", and ";" between " " which is part of a string in javascript. ) and   before the next line
                 case   ' ; ' :
                    
//  If the ";" is in quote or in "for", then not print " "
                     if (quoteCount  >   0   ||  sigleQuoteCount  >   0   ||  forCount  >   0 ) {
                        fw.write(
' ; ' );
                        
if (forCount  >   0 ) {
                            forCount
-- ;
                        }
                    
//  Add " " after ";"
                    }  else  {
                        fw.write(
' ; ' );
                        
if ( ' '   !=  nextChar  &&   ' '   !=  nextChar) {
                            fw.write(
' ' );
                            fillTableChar(fw, bracketCount);
                        }
                    }
                    
break ;
                
case   ' { ' //  3. add " " and " " after "{"
                    bracketCount ++ ;
                    fw.write(
' { ' );
                    
if ( ' '   !=  nextChar  &&   ' '   !=  nextChar) {  //  If the file is already formatted, don't add   after {.
                        fw.write( ' ' );
                        fillTableChar(fw, bracketCount);
                    }
                    
break ;
                
case   ' } ' //  4. add " " and " " before "}"
                    bracketCount -- ;
                    fw.write(
' ' );
                    fillTableChar(fw, bracketCount);
                    fw.write(
' } ' );
                    
if ( ' ; '   !=  nextChar  &&   ' } '   !=  nextChar  &&   ' '   !=  nextChar  &&   ' '   !=  nextChar) {
                        fw.write(
' ' );
                        fillTableChar(fw, bracketCount);
                    }
                    
break ;
                
case   ' ' ' :
                    fw.write(
' ' ' );
                    
if (quoteCount  ==   0 ) {  // When ' is not between "", change its state. 
                        sigleQuoteCount  =  sigleQuoteCount  ==   0   ?   1  :  0 ;
                    }
                    
break ;
                
case   ' " ' :
                    fw.write(
' " ' );
                    
if (sigleQuoteCount  ==   0 ) {  // When ' is not between "", change its state.
                        quoteCount  =  quoteCount  ==   0   ?   1  : 0 ;
                    }
                    
break ;
                
case   ' f ' //  1. add   before "function"
                     if (nextChar  ==   ' u '   &&  lastChar  !=   ' = ' ) {  //  TODO This is a very weak way to determine whether this coming word is "function", so it is need to be fixed.
                        fw.write( ' ' );
                        fw.write(
' ' );
                    }
                    fw.write(
' f ' );
                    
break ;
                
default :
                    fw.write(thisChar);
                    
break ;
            }
            
            
if (isAlpha(thisChar)) {
                
if ( ! isAlpha(lastChar)) {
                    lastWord 
=   "" ;
                }
                lastWord 
+=  String.valueOf(thisChar);
            } 
else  {
                
if (isAlpha(lastChar)) {
                    
if ( " 102111114 " .equals(lastWord)) {  //  "for"
                        forCount  =   2 ;
                    }
                    
// TODO Whether is is suitable here to determine "function" and add " " before it?
                }  else  {
                    lastWord 
=  String.valueOf(thisChar);
                }
            }
            
            lastChar 
=  thisChar;
            thisChar 
=  nextChar;
            
if (thisChar  !=   - 1 ) {
                nextChar 
=  fr.read();
            }
        }
        
        
//  close the files
        fw.close();
        fr.close();
    }
    
    
/**
     * Find all JS files in the specified directory.
     * 
     * 
@param  directory The directory in which the files to be listed. 
     * 
@return  List<String> The JS file list.
     
*/
    
private   static  List < String >  getJsFileList(String directory) {
        List
< String >   jsFileList  =   new  ArrayList < String > ();
        list(directory, jsFileList);
        
return  jsFileList;
    }
    
    
/**
     * List all the JS files in the specified directory recursively.
     * 
     * 
@param  path The path to be recursively searched for JS files.
     * 
@param  result The path and file list
     
*/
    
private   static   void  list(String path, List < String >  result) {
        File f 
=   new  File(path);
        
if (f.isDirectory()) {
            File[] fileList 
=  f.listFiles();
            
for ( int  i  =   0 ; i  <  fileList.length; i ++ ) {
                list(fileList[i].getPath(), result);
            }
        } 
else  {
            
if (isJsFile(f.getName())) {
                result.add(f.getPath());
            }
        }
    }
    
    
/**
     * Determine whether the the specified file is a JS file.
     * 
     * 
@param  fileName
     * 
@return  True: is a JS file; False: not a JS file.
     
*/
    
private   static   boolean  isJsFile(String fileName) {
        
// TODO use pattern!!!
         return   " .js " .equals(fileName.substring(fileName.length()  -   3 ));
    }
    
    
/**
     * List all JS files in the specified directory(Not in their sub-directory). 
     * 
     * 
@param  dir The specified directory.
     * 
@return  String[] The JS file list.
     
*/
    
private   static  String[] getSingleDirJsFileList( final  String dir) {
        String[] jsFileList;
        File path 
=   new  File(dir);
        jsFileList 
=  path.list( new  FilenameFilter() {
            
private  Pattern pattern  =  Pattern.compile( " .js " );
            
public   boolean  accept(File dir, String name) {
                
return  pattern.matcher( new  File(name).getName()).matches();
            }
        });
        
return  jsFileList;
    }
    
    
/**  
     * Check whether the character is an alpha char.
     * <b><red>Actually, the words exist in a function name would not be limit among those we list below. 
     * This need to be fixed. </red></b>
     * 
     * 
@param  c The char to be checked.
     * 
@return  boolean True: is alpha char; False: is not alpha char.
     
*/
    
private   static   boolean  isAlpha( int  c) {
        
return  ((c  >   ' a '   &&  c  <   ' z ' ||  (c  >   ' A '   &&  c  <   ' Z ' ||  (c  >   ' 0 '   &&  c  <   ' 9 ' ));
    }
    
    
/**
     * Fill specified number of ' '
     * 
     * 
@param  fw        FileWriter
     * 
@param  charNum    Specified number of ' '
     * 
@throws  IOException Exception when writing file
     
*/
    
private   static   void  fillTableChar(FileWriter fw,  int  charNum)  throws  IOException {
        
for ( int  i  =   0 ; i  <  charNum; i ++ ) {
            fw.write(
' ' );
        }
    }
}