Node.js静态代码检查之JSHint

达芬奇密码2018-07-19 13:05

初识Node.js

Node.js是一个基于Chrome JavaScript运行时建立的平台,它是对Google V8引擎进行了封装的运行环境. 简单的说node.js就是把浏览器的解释器封装起来作为服务器运行平台,用类似javascript的结构语法进行编程,在node.js上运行

Node.js静态代码检查工具分析

JavaScript 史上出现了以下几个主要的 Linter:JSlint、JShint、ESlint、Closure Linter、JSCS(JavaScript Code Style)、JavaScript Standard Style、等。其中使用最普遍的是JSlint和JSHint这两个工具,也有很多人对两个工具做出过比较, 如:

First Header JSLint JSHint
功能 JS代码质量检查和分析 JS代码质量检查和分析
使用情况 大量的用户,并应用于多个知名JavaScript项目 大量的用户,并应用于多个知名JavaScript项目
代码要求等级 要求极为严格 可以严格可以宽松
可配置项 可配置项很少 可配置项很多
适用情况 项目整体研发水平高,项目整体质量要求高 开发水平低,项目启动成本低,要求迭代、适应快

相较之下,JSHint 更友好,可配置性更高,在项目中应用更灵活, 本文针对JSHint的安装和与Jenkins的集成进行了调研.

JSHint安装与使用

每个JSHint版本都发布到npm,Node.js平台的包管理器,所以请准备好npm工具

首先确定 Node and Node Packaged Modules (npm) installed.

sudoaddaptrepositoryppa:chrislea/node.js sudo apt-get update sudoaptgetinstallnodejs sudo apt-get install npm

然后通过全局方式安装jshint

$ sudo npm install jshint -g

官网链接


  1. 安装后可以查看到目录下生成了package.json package.json 中包含各种所需模块以及项目的配置信息(名称、版本、许可证等)meta 信息

    root@wuwuw1-1809-164754444-bu0wg:/# cat package.json                                                          
    {                                                                                                             
    "name": "yuz",                                                                                              
    "version": "1.0.0",                                                                                         
    "description": "",                                                                                          
    "main": "index.js",                                                                                         
    "dependencies": {                                                                                           
     "express": "^4.14.0",                                                                                     
     "jshint": "^2.9.4"                                                                                        
    },                                                                                                          
    "devDependencies": {                                                                                        
     "jshint": "^2.9.4"                                                                                        
    },                                                                                                          
    "scripts": {                                                                                                
     "test": "echo \"Error: no test specified\" && exit 1"                                                     
    },                                                                                                          
    "author": "",                                                                                               
    "license": "ISC"                                                                                            
    }
    
  2. 在项目根目录下添加.jshintrc文件即jshint的语法校验规则配置文件.不添加执行默认语法检查

  3. 这里我们使用jshint的一个option 通过shell来执行js的语法检查
       --checkstyle-reporter Use a CheckStyle compatible XML reporter 
                             (DEPRECATED, use --reporter=checkstyle 
                             instead)
    
    Before: 无语法错误
    root@xxx# jshint *.js --checkstyle-reporter > check_result.xml 
    root@xxx# cat check_result.xml 
    <?xml version="1.0" encoding="utf-8"?>
    <checkstyle version="4.3">
    </checkstyle>
    
    After: 故意注释掉app.js的一个变量,让语法出现error
    root@xxx# jshint *.js --checkstyle-reporter > check_result.xml 
    root@xxx# cat check_result.xml 
    <?xml version="1.0" encoding="utf-8"?>
    <checkstyle version="4.3">
     <file name="app.js">
         <error line="1" column="1" severity="error" message="Expected an identifier and instead saw &apos;#&apos;." source="jshint.E030" />
         <error line="1" column="1" severity="warning" message="Expected an assignment or function call and instead saw an expression." source="jshint.W030" />
         <error line="1" column="2" severity="warning" message="Missing semicolon." source="jshint.W033" />
     </file>
    </checkstyle>
    


可以发现, jshint将错误检查并以xml的方式记录了下来

JSHint与Jenkins

  1. 通过Shell执行jshint命令, 并将xml结果保存
  2. 增加构建后步骤Publish JSHint analysis results, 读取测试报告
  3. 可以看到刚才通过shell执行的语法代码的3个error通过jenkins的jshint插件读取到并显示

    JSHint语法

jshint的配置通过jshintrc文件来进行配置, 例:

{
    "camelcase": false,
    "curly": false,
    "eqeqeq": false,
    "eqnull": false,
    "esnext": true,
    "freeze": true,
    "immed": true,
    "indent": false,
    "latedef": false,
    "laxbreak": true,
    "laxcomma": true,
    "loopfunc": true,
    "newcap": true,
    "noarg": true,
    "nonew": true,
    "nonbsp": true,
    "node": true,
    "proto": true,
    "regexp":true,
    "smarttabs": true,
    "sub":true,
    "trailing": true,
    "undef": true,
    "unused": true,
    "white": false
}

那么具体的配置详见 官网链接 其他参考 这里列出一些仅供参考:

  • bitwise

    禁用位运算符(如^, ,&) 位运算符在JS中很少使用,性能也较差,出现&也很可能是想写&&。

  • camelcase

    使用驼峰命名(camelCase)或全大写下划线命名(UPPER_CASE)

    这是条最佳实践,关键不在于采用什么样的命名规则(比如纯小写配下划线),而在于要有规则,在代码中看到不同的命名规则会让人头痛不已。

  • curly

    if和while等语句中使用{}来明确代码块

    while (day)
    shuffle();
    sleep();
    

    虽然缩进表示两条语句都在循环中,但事实却是只有一句循环。

  • eqeqeq

    使用===和!==替代==和!=

    ==和!=比较时会对前后元素进行自动转义,作为读者,需要动脑筋想这里可能有什么样的转义规则,加重负担;作为作者,其实很可能是不确定这段代码运行时是怎么样的,想要偷懒。

  • es3

强制使用ECMAScript 3规范

  • forin

    在for in循环中使用Object.prototype.hasOwnProperty()来过滤原型链中的属性

    for (key in obj) {
      if (obj.hasOwnProperty(key)) {
      // We are sure that obj[key] belongs to the object and was not inherieted.
      }
    }
    

    for in遍历对象属性的时候,包括继承自原型链的属性,hasOwnProperty可以来判断一个属性是否是对象本身的属性而不是继承得来的。

  • freeze

    禁止复写原生对象(如Array, Date)的原型

  • immed 匿名函数调用必须

    (function() {
     // body 
    }());
    

    而不是

    (function() {
     // body
    })();
    

    这是为了表明,表达式的值是函数的结果,而不是函数本身。

  • indent

    代码缩进宽度(空格数)

    前面几个项目我比较喜欢4,新项目我又在尝试2。关键不在于是几,而在于大家都要设成一样的。

  • latedef

    变量定义前禁止使用

    JS的变量是“函数级作用域”,而不是通常所见的“块级作用域”,简单说

    function sum(numbers) {
      for (var i = 0, n = numbers.length; i < n; i++) {
          var sum = sum + numbers[i];
      }
    
      return sum;
    }
    

    相当于

    function sum(numbers) {
      var i, n, sum;
    
      for (i = 0, n = numbers.length; i < n; i++) {
         sum = sum + numbers[i];
      }
    
      return sum;
    }
    这个行为叫做“变量声明提升”,为了不产生混淆,这条规则建议函数都使用第二种写法。
    
  • newcap

    构造函数名首字母必须大写

    这条最佳实践是为了方便区分构造函数和普通函数,这样在直接调用大写字母开头的函数时,使用者就会想想是不是自己写错了。

    不通过new而直接调用构造函数,会使得构造函数中的this指向global对象,从而产生错误。

    PS. 有些高手可以通过在构造函数中判断this的指向来判断是否重新new自身,从而让构造函数也能直接调用产生新对象。但这有些高深,加重开发人员和使用人员的负担,也不利于统一编码风格。

  • noarg

    禁止使用arguments.caller和arguments.callee

    一方面这两个属性不是所有的浏览器都支持,另一方面这两个属性的使用会导致JS引擎很难优化代码,在未来的JS规范中会被去掉,所以不建议使用。

  • noempty

    禁止出现空的代码块

    空的代码块并不是有害的,但是出现的话我们需要考虑下为什么。

  • nonbsp

    禁止”non-breaking whitespace”

    这是Mac键盘在某种情况下可以键入的字符,据说会破坏非UTF8编码的页面。

  • nonew

    禁止使用构造器

    new MyConstructor(); 构造一个对象,却不给它赋值到某个变量,只是利用构造函数中的逻辑。这个行为完全可以用一个普通函数来完成,不应该借助构造器。

  • plusplus

    禁止使用++和–

    不是很赞成把这个选项打成true,不过乱用自增/自减确实也会带来阅读上的障碍。

  • quotemark

    统一使用单引号或双引号

    这个最佳实践要求代码风格统一,我比较喜欢统一成单引号。

    这是为什么规定最佳实践的一个好例子,在写到字符串的时候我们就不用考虑使用单引号好还是用双引号好,就都用单引号,这在一定程度上也减轻了我们的思考负担。

  • undef

    禁止使用不在全局变量列表中的未定义的变量

function test() {
    var myVar = 'Hello, World';
    console.log(myvar); // Oops, typoed here. JSHint with undef will complain
}
如果本地作用域里的变量没有使用var来声明,则会被放到全局作用域下面,众所周知,全局变量时罪恶的源泉。
  • unused

    禁止定义变量却不使用

function test(a, b) { var c, d = 2; return a + d; } test(1, 2); // Line 1: 'b' was defined but never used. // Line 2: 'c' was defined but never used. 这种变量通常是写作过程中遗留下来的垃圾,需要及时清理掉。

  • strict

    强制使用ES5的严格模式

    Strict Mode是对JS用法的一些限制,过滤掉了容易出错的特性和不容易优化的特性。

    通过在函数开头处加入’use strict’;来触发严格模式,不要在文件头部加入,因为在JS链接的时候很可能就失效了。

  • trailing

    禁止行尾空格

  • maxparams

    函数可以接受的最大参数数量

    函数参数数量应该控制在3个以内,超出则可能造成使用困难,比如需要记忆参数顺序,难以设定默认值等。另外,在JS中可以很方便的使用参数对象来封装多个参数。

  • maxdepth

    代码块中可以嵌入{}的最大深度

  • maxstatement

    函数中最大语句数

  • maxcomplexity

    函数的最大圈复杂度

  • maxlen

    一行中最大字符数

    这个是为了减轻代码阅读的困难,简单说就是不要折行。

    上面四个参数最终都是为了减小代码的复杂程度,简单轻巧的代码片段更容易阅读和维护。

    松弛参数(Relaxing Options)

    本类参数设为true,JSHint会产生更少告警。

  • asi

    允许省略分号

    JavaScript的语法允许自动补全分号,但是这一特性也会造成难以定位的错误,所以建议写代码时不要省略分号。

  • boss

    允许在if,for,while语句中使用赋值

    在条件语句中使用赋值经常是笔误if (a = 10) {},但是牛人(boss)可以把这个特性用的很好,我们作为普通人就算了。

  • debug

    允许debugger语句

    debugger语句在产品代码中应该去掉。

  • eqnull

    允许==null

    ==null通常用来比较=== null === undefined

  • esnext

    允许ECMAScript 6规约

    目前ES6的特性不是所有的浏览器都支持。

  • evil

    允许使用eval

    eval有“注入攻击”的危险,另一方面也不利于JS引擎优化代码,所以尽量不要使用。

  • expr

    允许应该出现赋值或函数调用的地方使用表达式

  • funcscope

    允许在控制体内定义变量而在外部使用

function test() {
    if (true) {
        var x = 0;
    }

    x += 1; // Default: 'x' used out of scope.
            // No warning when funcscope:true
}

虽然“变量声明提升”使得上面的代码可以运行通过,但是读者还是会感到头晕。

  • globalstrict

    允许全局严格模式

    在strict中解释了,’use strict’;放在全局域可能造成JS文件链接错误。

  • iterator

允许iterator

不是所有的浏览器都支持iterator

  • lastsemic

    允许单行控制块省略分号

    var name = (function() { return 'Anton' }()); 高手用得到的特性,我们还是坚持加上分号吧。

  • laxbreak

    允许不安全的行中断(与laxcomma配合使用)

  • laxcomma

    允许逗号开头的编码样式

    var obj = {
      name: 'Anton'
    , handle: 'valueof'
    , role: 'SW Engineer'
    };
    
  • loopfunc

    允许循环中定义函数

    在循环中定义函数经常会导致错误:

var nums = [];

for (var i = 0; i < 10; i++) {
    nums[i] = function (j) {
        return i + j;
    };
}
nums[0](2); // Prints 12 instead of 2

错误的根源在于function(j)中的i是对循环中的i的引用,而不是赋值。所以在最终函数执行时,i的值是10。 修改的方法是使用闭包:

var nums = [];

for (var i = 0; i < 10; i++) {
    (function (i) {
        nums[i] = function (j) {
            return i + j;
        };
    }(i));
}
  • maxerr

    JSHint中断扫描前允许的最大错误数

    因为最终我们需要清零JSHint报错的,所以这个值用在对已有项目的扫描中。

  • multistr

    允许多行字符串

  • notypeof

    允许非法的typeof操作

  • proto

    允许 proto

    不是所有的浏览器都支持proto.

  • smarttabs

    允许混合tab和space排版

    SmartTabs方法使用tab进行缩进,使用空格进行代码对齐。比较高级的用法,有兴趣的话可以尝试下。

  • shadow

    允许变量shadow

    function test() {
      var x = 10;
    
      if (true) {
          var x = 20;
      }
    
      return x;
    }
    

    基于“函数作用域”,多次定义变量和单次定义是没有区别的,但是会造成阅读障碍。

  • sub

    允许person[‘name’]

    JSHint推荐使用person.name代替person[‘name’]

  • supernew

    允许new function() {…}和new Object;

  • validthis

    允许严格模式下在非构造函数中使用this

  • noyield

    允许发生器中没有yield语句


本文来自网易实践者社区,经作者张雨授权发布。