echo-fecs
Version:
Front End Code Style Suite
194 lines (164 loc) • 5.62 kB
JavaScript
/**
* @file Rule to validate variables' name and jsdoc comment.
* @author chris<wfsr@foxmail.com>
*/
;
var doctrine = require('doctrine2');
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
schema: []
},
create: function (context) {
var sourceCode = context.getSourceCode();
/**
* 匹配常量命名
*
* @const
* @type {RegExp}
*/
var CONST_PATTERN = /^[A-Z]([A-Z\d$]+_?)*[A-Z\d$]$/;
/**
* 匹配 Pascal 命名
*
* @const
* @type {RegExp}
*/
var PASCAL_PATTERN = /^([A-Z][a-zA-Z\d$]+)+$/;
/**
* 匹配 Camel 命名
*
* @const
* @type {RegExp}
*/
var CAMEL_PATTERN = /^[a-z$][a-zA-Z\d$]+$/;
/**
* 匹配 is 或 has 开头的命名
*
* @const
* @type {RegExp}
*/
var IS_HAS_PATTERN = /^(?=is|has|IS_|HAS_)/;
function isConstName(name) {
return CONST_PATTERN.test(name);
}
function isPascalName(name) {
return PASCAL_PATTERN.test(name);
}
function isCamelName(name) {
return CAMEL_PATTERN.test(name);
}
function parseDoc(commentNode) {
try {
return doctrine.parse(
commentNode.value,
{
strict: true,
unwrap: true,
sloppy: true,
lineNumbers: true,
recoverable: true
}
);
}
catch (ex) {
return null;
}
}
function getJSDoc(node) {
var leadingComments = sourceCode.getComments(node).leading;
var comment = leadingComments[leadingComments.length - 1];
if (!comment
|| comment.type !== 'Block'
|| comment.value[0] !== '*'
|| node.loc.start.line - comment.loc.end.line > 1
) {
return null;
}
return parseDoc(comment);
}
function checkConst(id, stat, jsdoc, node) {
var constTag = stat.const || stat.constant;
if (isConstName(id.name)) {
if (stat && stat.desc && !(constTag && (constTag.type || stat.type))) {
context.report(
id,
'Constant variables should be tagged with description, @const and @type.baidu060'
);
}
}
else if (constTag) {
context.report(
id,
'Identifier `{{name}}` should be only uppercase and underscore.baidu026',
{name: id.name}
);
}
}
function checkBoolean(id, stat, jsdoc, node) {
if (!IS_HAS_PATTERN.test(id.name)) {
context.report(
id,
'Expected boolean variables with `is` or `has` prefix.baidu036'
);
}
}
function checkPascal(id, stat, jsdoc, node) {
var init = node.init;
// 忽略无赋值或可能为类定义的变量
if (!init || init.type === 'FunctionExpression') {
return;
}
if (isPascalName(id.name)) {
if (init.type === 'ObjectExpression') {
init.properties.forEach(function (property) {
if (property.key && !isConstName(property.key.name)) {
context.report(
property,
'Property key of enum object should be named as constant variable.baidu031'
);
}
});
}
}
else {
context.report(id, 'Enumerable variables should be named as `Pascal`.baidu031');
}
}
function validate(node) {
var id = node.id;
if (id.type !== 'Identifier') {
return;
}
var name = id.name;
var jsdoc = getJSDoc(node.parent);
var stat = jsdoc && jsdoc.tags.reduce(function (stat, tag) {
stat[tag.title] = tag;
return stat;
}, {desc: jsdoc && jsdoc.description}) || {};
if (node.parent.kind === 'const' && !(stat.enum || stat.namespace)) {
checkConst(id, stat, jsdoc, node);
}
else if (stat.enum || isPascalName(name) && !(stat.const || stat.namespace)) {
checkPascal(id, stat, jsdoc, node);
}
else if (stat.type
&& stat.type.type
&& String(stat.type.type.name).toLowerCase() === 'boolean'
) {
checkBoolean(id, stat, jsdoc, node);
}
else if (stat.namespace && !isCamelName(name)) {
context.report(
id,
'Namespace should be named as `Camel`.baidu032'
);
}
}
return {
VariableDeclarator: validate
};
}
};