JSLint检测Javascript语法规范

3,038 阅读14分钟

前端javascript代码编写中,有一个不错的工具叫JSLint,可以检查代码规范化,压缩JS,CSS等,但是他的语法规范检查个人觉得太“苛刻”了,会提示各种各样的问题修改建议,有时候提示的信息我们看的莫名其妙,这里,先转载一下携程UED的一个技术文章(原文链接ued.ctrip.com/blog/?p=273…),看看JSLint的错误提示都是什么意思:

一直以为检查JS语法错误非jslint不可,不过使用起来总是觉得太重量级了一点点。

后来无意中发现了一个叫jshint的东东。

首先介绍一下,jshint和jslint的差别在哪里。

摘自官网的一段内容

JSHint is a fork of Douglas Crockford’s JSLint that is designed to be more flexible than the original. Our goal is to make a tool that helps you to find errors in your JavaScript code and to enforce your favorite coding style.

We realize that people use different styles and conventions, and we want our tool to adjust to them. JSHint will never enforce one particular convention.

大概的意思就是,JSHint比起JSLint而言,会更加轻量级一些。它能够找出代码中的语法错误,并且建议更好的一种编码风格。当然,它也不是强制性的非要你根据它规定的编码风格来做。因为它提供了一系列的配置,你可以随时关掉某些你觉得不必要的错误提示。这个我后面会介绍到。

那么如何使用jsHint检查错误呢?用起来非常简单哦~

var result = JSHINT(source, options);

先解释一下参数和返回值:

第一个参数source : 必选项。表示需要检查的代码,js或者json,可以传一个字符串或者一个数组。如果传字符串,需要用’\r’或者’\n’来分隔一行一行的代码;如果传数组,则每一个数组元素表示一行的代码。

第二个参数option : 可选项。表示代码检查的配置项。大部分的都是bool类型的,也有一部分,例如predef,可以是一个array,含有全局变量或者全局方法;或者是一个object,key是全局变量或者方法,value是一个bool值,表示是否被定义过。

返回值:如果代码没有问题,JSHINT会返回一个true;否则返回false。

详细的说明

1. 关于第一个参数

由于只能传入一个字符串或者数组。但是如果需要根据一个js的链接来检查此文件呢?

我尝试使用ajax方法获取js的内容(虽然是可以跨域的),但是如果页面是gb2312的取回来就会乱码,那么用jshint页面检查的话,读到中文部分就会报错”unsafe charater”。尝试使用二进制的responseBody进行转码,但是没有找到是适合的js转码方法。

后来,我写了一个php的中转页面,用file_get_contents的方法读取文件,使用mb_detect_encoding检测页面编码

<?php
    mb_detect_order("GB2312,GBK,UTF-8,ASCII");
    $url = $_REQUEST["url"];
    $str = file_get_contents($url);    if(!isset($url) || !$str){
        echo "";
    }else{
        $getcontent = iconv(mb_detect_encoding($str), "utf-8", $str);
        echo $getcontent;
    }
?>

当然,也有同学提到可以将js文件获取下来,存到本地。读取的时候判断编码来读取,也是一种方法。不过我没有尝试过。

2. 关于第二个参数

我觉得这个才是JSHINT的精髓,因为每一个配置项都可以定义你要check的深度和广度。

下面就把这一系列的配置项列出来(偶自己翻译的)。以下的这些是官网上面的option。

propdescription
asi是否使用自动插入分号
bitwise如果是true,则禁止使用位运算符
boss如果是true,则允许在if/for/while的条件中使用=做赋值操作
curly如果是true,则要求在if/while的模块时使用TAB结构
debug如果是true,则允许使用debugger的语句
eqeqeq如果是true,则要求在所有的比较时使用===和!==
eqnull如果是true,则允许使用== null
evil如果是true,则允许使用eval方法
forin如果是true,则不允许for in在没有hasOwnProperty时使用
immed如果是true,则要求“立即调用”(immediate invocations)必须使用括号包起来
laxbreak如果是true,则不检查换行,那么自动插入分号的选项必须开启。
maxerr默认是50。 表示多少错误时,jshint停止分析代码
newcap如果是true,则构造函数必须大写
noarg如果是true,则不允许使用arguments.caller和arguments.callee
noempty如果是true,则不允许使用空函数
nonew如果是true,则不允许不做赋值的构造函数,例如new UIWindow();
nomen如果是true,则不允许在名称首部和尾部加下划线
onevar如果是true,则在一个函数中只能出现一次var
passfail如果是true,则在遇到第一个错误的时候就终止
plusplus如果是true,则不允许使用++或者- -的操作
regexp如果是true,则正则中不允许使用.或者[^…]
undef如果是ture,则所有的局部变量必须先声明之后才能使用
sub如果是true,则允许使用各种写法获取属性(一般使用.来获取一个对象的属性值)
strict如果是true,则需要使用strict的用法,
详见ejohn.org/blog/ecmasc…
white如果是true,则需要严格使用空格用法。

当然,千万别以为JSHINT就只有这些配置项,在我使用的过程中,发现很多配置项就需要去读它的源代码才能发现。
比如,当我发现他会报错我的某个自定义函数$animate没有定义的时候,我尝试用/*global $animate*/来声明,但是没有效果。
于是我跟踪代码,发现了如下的代码

function assume() {
    if (option.couch)
    combine(predefined, couch);
    if (option.rhino)
    combine(predefined, rhino);
    if (option.prototypejs)
    combine(predefined, prototypejs);
    if (option.node)
    combine(predefined, node);
    if (option.devel)
    combine(predefined, devel);
    if (option.dojo)
    combine(predefined, dojo);
    if (option.browser)
    combine(predefined, browser);
    if (option.jquery)
    combine(predefined, jquery);
    if (option.mootools)
    combine(predefined, mootools);
    if (option.wsh)
    combine(predefined, wsh);
    if (option.globalstrict && option.strict !== false)
    option.strict = true;
}

当时我在这个后面加了一段代码,准备扩展一个predef的项。
后来才发现,原来JSHINT本身就有一个predef的配置,就像上面说的一个数组或一个object即可。

if(option.predef){
    for(var i=0,l=option.predef.length; i<l; i++){
        predefined[option.predef[i]] = true;
    }
}

当然,细心的同学一定发现了,里面也可以声明代码运行的环境。例如jquery/dojo等。

  • Browser (browser)
  • Development: console, alert, etc. (devel)
  • jQuery (jquery)
  • CouchDB (couch)
  • ES5 (es5)
  • Node.js (node)
  • Rhino (rhino)
  • Prototype.js (prototypejs)
  • MooTools (mootools)

又比如JSHINT检查的时候,针对某些字符会报”unsafe character”的错误,但是如果有些字符恰巧就是我们需要的怎么办呢?
跟踪代码,发现检查unsafe的正则如下:

cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/

我们可以自定义一下cx

3. 返回值

当返回false的时候,我们如何知道代码哪里出问题了呢?

有以下几个方法:

1. JSHINT.errors

JSHINT.errors是一个object,有以下值:

{
    line: 错误的行数,
    charater: 错误的字符数,
    reason: 问题详细描述信息,
    evidence: 出错的代码,
    raw: 原本的描述信息,
    a: the first detail,
    b: the second detail,
    c: the third detail,
    d: the fourth detail
}

2. JSHINT.report(limited)

参数limited如果为true,则表示report仅仅输出错误(errors)

返回一个类似report的最终结果。可以被放置在html中。

3. JSHINT.data()

返回一个object格式的数据结果, 有以下值:

{
errors:[
{
line: 错误的行数[number],
charater: 错误的字符数[number],
reason: 问题详细描述信息[string],
evidence: 出错的代码[string]
}
],
functions: [
name: 函数名称[STRING],
line: 错误的行数[NUMBER],
last: NUMBER,
param: [
参数[STRING]
],
closure: [
闭包[STRING]
],
var: [
STRING
],
exception: [
STRING
],
outer: [
STRING
],
unused: [
STRING
],
global: [
STRING
],
label: [
STRING
]
],
globals: [
STRING
],
member: {
STRING: NUMBER
},
unuseds: [
{
name: STRING,
line: NUMBER
}
],
implieds: [
{
name: STRING,
line: NUMBER
}
],
urls: [
STRING
],
json: 是否是json的数据[BOOLEAN]

使用下来,jshint对代码的检查非常不错的。

但是觉得jshint有些地方可以改进,例如所有的报错信息都是分散在四面八方。这里一句warning(“xx”),那里一句warning(“yy”)。不像wikipedia有一个统一管理message的地方,而且有语言版本的选择。
无奈之下,我只能增加了一下message的翻译,并且小改了一下warning的函数

globalMsg = {
“Missing semicolon.” : “缺少分号.”,
“Use the function form of \”use strict\”.” : “使用标准化定义function.”,
“Unexpected space after ‘-’.” : “在’-'后面不应出现空格.”,
“Expected a JSON value.” : “请传入一个json的值.”,
“Mixed spaces and tabs.”: “空格和TAB重复.”,
“Unsafe character.” : “不安全的字符.”,
“Line too long.”: “本行中的字符超过设定的最大长度.”,
“Trailing whitespace.”: “本行末尾有过多无用空格.”,
“Script URL.” : “脚本URL.”,
“Unexpected {a} in ‘{b}’.” : “在 ‘{b}’ 中不该出现 {a}.”,
“Unexpected ‘{a}’.” : “不该在此出现’{a}’.”,
“Strings must use doublequote.” : “字符串需要用双引号”,
“Unnecessary escapement.” : “不需要转义”,
“Control character in string: {a}.” : “在字符串中出现了Control的字符”,
“Avoid \\’.” : “避免 \\”,
“Avoid \\v.” : “避免 \\v”,
“Avoid \\x-.” : “避免 \\x-”,
“Bad escapement.” : “错误的转义字符”,
“Bad number ‘{a}’.” : “错误的数字 ‘{a}’”,
“Missing space after ‘{a}’.” : “在’{a}’之后缺少空格”,
“Don’t use extra leading zeros ‘{a}’.” : “不要再’{a}’的前面用多余的0″,
“Avoid 0x-. ‘{a}’.” : “避免使用 0x-. ‘{a}’.”,
“A trailing decimal point can be confused with a dot ‘{a}’.” : “在’{a}’中使用点尾随小数点”,
“Unexpected comment.” : “不该在此处出现注释”,
“Unescaped ‘{a}’.” : “没有转义 ‘{a}’”,
“Unexpected control character in regular expression.” : “在正则表达式中出现了control字符”,
“Unexpected escaped character ‘{a}’ in regular expression.” : “在正则表达式中出现了没有转义的字符 ‘{a}’”,
“Expected ‘{a}’ and instead saw ‘{b}’.” : “应该用 ‘{a}’代替’{b}’”,
“Spaces are hard to count. Use {{a}}.” : “空格难以统计,请使用 {{a}}”,
“Insecure ‘{a}’.” : “不安全的 ‘{a}’”,
“Empty class.” : “空的class”,
“Expected a number and instead saw ‘{a}’.”:“应该用数字代替’{a}’”,
“‘{a}’ should not be greater than ‘{b}’.”:“‘{a}’不应该比’{b}’大”,
“‘hasOwnProperty’ is a really bad name.”: “‘hasOwnProperty’是关键字”,
“‘{a}’ was used before it was defined.”:“‘{a}’未定义就已经使用了.”,
“‘{a}’ is already defined.”:“‘{a}’被重复定义”,
“A dot following a number can be confused with a decimal point.”:“数字后面的一个点会被误认为是十进制的小数点”,
“Confusing minusses” : “容易混淆的负数表达-”,
“Confusing plusses.” : “容易混淆的正数表达+”,
“Unmatched ‘{a}’.” : “无法匹配的’{a}’”,
“Expected ‘{a}’ to match ‘{b}’ from line {c} and instead saw ‘{d}’.”:“在行{c}中需要用’{a}’和’{b}’匹配,用来代替’{d}’”,
“Unexpected early end of program.”:“程序不可预期的提前终止”,
“A leading decimal point can be confused with a dot: ‘.{a}’.”:“‘{a}’前的点容易混淆成小数点”,
“Use the array literal notation [].”:“使用数组的符号 []“,
“Expected an operator and instead saw ‘{a}’.”:“需要用一个符号来代替’{a}’”,
“Unexpected space after ‘{a}’.”:“在’{a}’之后不能出现空格”,
“Unexpected space before ‘{a}’.”:“在’{a}’之前不能出现空格”,
“Bad line breaking before ‘{a}’.”:“在’{a}’之前错误的换行”,
“Expected ‘{a}’ to have an indentation at {b} instead at {c}.”:“‘{a}’需要在{c}而不是{b}处缩进”,
“Line breaking error ‘{a}’.”:“换行错误 ‘{a}’”,
“Unexpected use of ‘{a}’.”:“此处不能用’{a}’”,
“Bad operand.”:“错误的操作数”,
“Use the isNaN function to compare with NaN.”:“使用isNaN来与NaN比较”,
“Confusing use of ‘{a}’.”:“容易混淆的’{a}’的使用”,
“Read only.”:“只读的属性”,
“‘{a}’ is a function.”:“‘{a}’是一个函数”,
‘Bad assignment.’:“错误的赋值”,
“Do not assign to the exception parameter.”:“不要给额外的参数赋值”,
“Expected an identifier in an assignment and instead saw a function invocation.”:“在赋值的语句中需要有一个标识符,而不是一个方法的调用”,
“Expected an identifier and instead saw ‘{a}’ (a reserved word).”:“需要有一个标识符,而不是’{a}’(保留字符)”,
“Missing name in function declaration.”:“在方法声明中缺少名称”,
“Expected an identifier and instead saw ‘{a}’.”:“需要有一个标识符,而不是’{a}’”,
“Inner functions should be listed at the top of the outer function.”:“内部函数的声明应该放在此函数的顶部。”,
“Unreachable ‘{a}’ after ‘{b}’.”:“在’{b}’之后无法获取’{a}’”,
“Unnecessary semicolon.”:“不必要的分号”,
“Label ‘{a}’ on {b} statement.”:“将’{a}’放在{b}的声明中”,
“Label ‘{a}’ looks like a javascript url.”:“‘{a}’看上去像一个js的链接”,
“Expected an assignment or function call and instead saw an expression”:“需要一个赋值或者一个函数调用,而不是一个表达式.”,
“Do not use ‘new’ for side effects.”:“不要用’new’语句.”,
“Unnecessary \”use strict\”.”:“不必要的\”use strict\”.”,
“Missing \”use strict\” statement.”:“缺少\”use strict\”的声明”,
“Empty block.”:“空的模块”,
“Unexpected /*member ‘{a}’.”:“不应出现 /*元素 ‘{a}’.”,
“‘{a}’ is a statement label.”:“‘{a}’是一个声明”,
“‘{a}’ used out of scope.”:“‘{a}’使用超出范围”,
“‘{a}’ is not allowed.”:“不允许使用’{a}’”,
“‘{a}’ is not defined.”:“‘{a}’没有被定义”,
“Use ‘{a}’ to compare with ‘{b}’.”:“使用’{a}’与’{b}’相比”,
“Variables should not be deleted.”:“变量需要被删除”,
“Use the object literal notation {}.”:“使用对象的文字符号 {}”,
“Do not use {a} as a constructor.”:“不要使用{a}作为一个构造对象”,
“The Function constructor is eval.”:“The Function constructor is eval.”,
“A constructor name should start with an uppercase letter.”:“一个构造对象的名称必须用大写字母开头.”,
“Bad constructor.”:“错误的构造对象”,
“Weird construction. Delete ‘new’.”:“构造对象有误,请删除’new’”,
“Missing ‘()’ invoking a constructor.”:“缺少括号()”,
“Avoid arguments.{a}.”:“避免参数.{a}.”,
“document.write can be a form of eval.”:“document.write是eval的一种形式”,
‘eval is evil.’:“尽量不要使用eval”,
“Math is not a function.”:“Math不是一个函数”,
“Missing ‘new’ prefix when invoking a constructor.”:“此处缺少了’new’”,
“Missing radix parameter.”:“缺少参数”,
“Implied eval is evil. Pass a function instead of a string.”:“传递一个函数,而不是一个字符串”,
“Bad invocation.”:“错误的调用”,
“['{a}'] is better written in dot notation.”:“['{a}']最好用点.的方式”,
“Extra comma.”:“多余的逗号”,
“Don’t make functions within a loop.”:“不要用循环的方式创建函数”,
“Unexpected parameter ‘{a}’ in get {b} function.”:“在{b}方法中不该用到参数’{a}’”,
“Duplicate member ‘{a}’.”:“重复的’{a}’”,
“Expected to see a statement and instead saw a block.”:“此处应该是语句声明.”,
“Too many var statements.”:“过多var的声明”,
“Redefinition of ‘{a}’.”:“‘{a}’被重复定义”,
“It is not necessary to initialize ‘{a}’ to ‘undefined’.”:“无需将’{a}’初始化为’undefined’”,
“Expected a conditional expression and instead saw an assignment.”:“此处需要一个表达式,而不是赋值语句”,
“Expected a ‘break’ statement before ‘case’.”:“在’case’之前需要有’break’.”,
“Expected a ‘break’ statement before ‘default’.”:“在’default’之前需要有’break’.”,
“This ‘switch’ should be an ‘if’.”:“此处’switch’应该是’if’.”,
“All ‘debugger’ statements should be removed.”:“请删除’debugger’的语句”,
“‘{a}’ is not a statement label.”:“‘{a}’不是一个声明标签.”,
“Expected an assignment or function call and instead saw an expression.”:“需要一个语句或者一个函数调用,而不是一个表达式”,
“Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.”:“函数的声明不能放在类似if的块中,需要放在外部函数的顶部.”
},

warning函数增加了一个m = globalMsg[m] || m;

function warning(m, t, a, b, c, d) {
var ch, l, w;
t = t || nexttoken;
if (t.id === ‘(end)’) { // `~
t = token;
}
l = t.line || 0;
ch = t.from || 0;
m = globalMsg[m] || m;
w = {
id: ‘(error)’,
raw: m,
evidence: lines[l - 1] || ”,
line: l,
character: ch,
a: a,
b: b,
c: c,
d: d
};
w.reason = m.supplant(w);
JSHINT.errors.push(w);
if (option.passfail) {
quit(‘Stopping. ‘, l, ch);
}
warnings += 1;
if (warnings >= option.maxerr) {
quit(“Too many errors.”, l, ch);
}
return w;
}

如果你需要一个轻量级的语法检查工具,那么jshint还是一个蛮得心应手的工具。如果能够更深入的读一下JSHINT的代码,收获应该不小。

针对自己项目中遇到的一些提示,做一些举例说明:

1 [W099]:Mixed spaces and tabs

这个错误是最常见的,意思是在同一行中,空格和Tab缩进混合使用了,修改很简单,一般是删除Tab缩进,全部改为空格。为了方便,我们可以把编辑器的Tab缩进设置成2个或4个空格,来代替原有的缩进。

2 [W030]:Expected an assignment or function call and instead saw an expression

这个错误提示的很诡异,我是用如下代码提示的这个错误 index-1 <0 ? index = 0:index = index - 1; 这是一个逗号表达式,但是JSLInt认为这里不应该用表达式,而必须是一个函数,所以,如果非常在乎这个错误,就改为if else 语句吧

3 [W041]:Use '===' to compare with ...

这个错误是说,我们要是用全等来代替等于,如果表达式两边的数据类型是一致的话,建议使用全等来判断

4 [W033]:Missing semicolon

缺少分号;这个一般都是自己忘记写了吧,但是有一个需要注意的是,对于只有一句的结构,后面也需要写分号。例如:if(index<0) {index=tcount-1} 这句代码,正确写法是if(index<0) {index=tcount-1;},我是经常忘记这里写分号,汗...

其他还有一些错误提示就对照一下改吧,要培养自己良好的代码风格和书写习惯。