├── .editorconfig ├── .fecsrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── README.md ├── appveyor.yml ├── bin └── fecs ├── cli ├── check.js └── format.js ├── doc ├── check.md ├── fecs.md └── format.md ├── index.js ├── lib ├── checker.js ├── cli.js ├── css │ ├── checker.js │ ├── config.js │ ├── csscomb.yml │ ├── csshint.yml │ ├── formatter.js │ └── rules │ │ └── space-after-value.js ├── formatter.js ├── formatter │ ├── checkstyle.js │ ├── html.js │ ├── index.js │ ├── json.js │ └── xml.js ├── html │ ├── checker.js │ ├── config.js │ ├── formatter.js │ └── htmlcs.yml ├── ignored.js ├── js │ ├── checker.js │ ├── config.js │ ├── esformatter.yml │ ├── eslint.yml │ ├── esnext.js │ ├── formatter.js │ └── rules │ │ ├── arrow-body-style.js │ │ ├── camelcase.js │ │ ├── eol-last.js │ │ ├── esnext-ext.js │ │ ├── export-on-declare.js │ │ ├── imports-on-top.js │ │ ├── indent.js │ │ ├── index.js │ │ ├── jsx-var.js │ │ ├── max-calls-in-template.js │ │ ├── max-destructure-depth.js │ │ ├── max-statements.js │ │ ├── min-vars-per-destructure.js │ │ ├── no-anonymous-before-rest.js │ │ ├── no-eval.js │ │ ├── no-extra-destructure.js │ │ ├── no-extra-semi.js │ │ ├── no-forin-array.js │ │ ├── no-global-require.js │ │ ├── no-require.js │ │ ├── no-this-arrow.js │ │ ├── one-var-per-line.js │ │ ├── prefer-assign-pattern.js │ │ ├── prefer-async-await.js │ │ ├── prefer-class.js │ │ ├── prefer-destructure.js │ │ ├── prefer-spread-element.js │ │ ├── prefer-super.js │ │ ├── properties-quote.js │ │ ├── shim-promise.js │ │ ├── use-async-require.js │ │ ├── use-computed-property.js │ │ ├── use-for-of.js │ │ ├── use-method-definition.js │ │ ├── use-property-shorthand.js │ │ ├── use-standard-promise.js │ │ ├── valid-amd-id.js │ │ ├── valid-class-jsdoc.js │ │ ├── valid-constructor.js │ │ ├── valid-dom-style.js │ │ ├── valid-jsdoc.js │ │ ├── valid-map-set.js │ │ ├── valid-super.js │ │ └── valid-var-jsdoc.js ├── less │ ├── checker.js │ ├── config.js │ └── lesslint.yml ├── log.js ├── reporter │ ├── baidu │ │ ├── css.yml │ │ ├── csshint-map.yml │ │ ├── es-next.yml │ │ ├── eslint-map.yml │ │ ├── html.yml │ │ ├── htmlcs-map.yml │ │ ├── index.js │ │ ├── javascript.yml │ │ └── lesslint-map.yml │ ├── filter.js │ └── index.js ├── util.js └── version.js ├── package.json ├── scripts └── install.js └── test ├── .fecsrc ├── fixture ├── checker │ ├── a.spec │ ├── b.spec │ ├── bar.spec │ ├── baz.x │ ├── c.spec │ └── foo.spec ├── ignored │ ├── .fecsignore │ ├── .unfecsignore │ ├── a.min.js │ ├── a.spec │ ├── a.spec.js │ ├── b.min.js │ ├── b.spec │ ├── b.spec.js │ ├── c.min.js │ ├── c.spec │ └── c.spec.js └── util │ ├── .eslintrc │ ├── bar │ ├── foo │ ├── package.json │ └── parseExtends │ └── .eslintrc ├── helper.js ├── index.spec.js └── lib ├── checker.spec.js ├── cli.spec.js ├── css ├── checker.spec.js └── formatter.spec.js ├── formatter.spec.js ├── html ├── checker.spec.js └── formatter.spec.js ├── ignored.spec.js ├── js ├── checker.spec.js ├── esnext.spec.js ├── formatter.spec.js └── rules │ ├── arrow-body-style.spec.js │ ├── camelcase.spec.js │ ├── eol-last.spec.js │ ├── esnext-ext.spec.js │ ├── export-on-declare.spec.js │ ├── imports-on-top.spec.js │ ├── indent.spec.js │ ├── index.spec.js │ ├── jsx-var.spec.js │ ├── max-calls-in-template.spec.js │ ├── max-destructure-depth.spec.js │ ├── max-statements.spec.js │ ├── no-anonymous-before-rest.spec.js │ ├── no-eval.spec.js │ ├── no-extra-destructure.spec.js │ ├── no-extra-semi.spec.js │ ├── no-forin-array.spec.js │ ├── no-global-require.spec.js │ ├── no-require.spec.js │ ├── no-this-arrow.spec.js │ ├── one-var-per-line.spec.js │ ├── prefer-assign-pattern.spec.js │ ├── prefer-async-await.spec.js │ ├── prefer-class.spec.js │ ├── prefer-destructure.spec.js │ ├── prefer-spread-element.spec.js │ ├── prefer-super.spec.js │ ├── properties-quote.spec.js │ ├── shim-promise.spec.js │ ├── use-async-require.spec.js │ ├── use-computed-property.spec.js │ ├── use-for-of.spec.js │ ├── use-method-definition.spec.js │ ├── use-property-shorthand.spec.js │ ├── use-standard-promise.spec.js │ ├── valid-amd-id.spec.js │ ├── valid-class-jsdoc.spec.js │ ├── valid-constructor.spec.js │ ├── valid-dom-style.spec.js │ ├── valid-jsdoc.spec.js │ ├── valid-map-set.spec.js │ ├── valid-super.spec.js │ └── valid-var-jsdoc.spec.js ├── less └── checker.spec.js ├── log.spec.js ├── util.spec.js └── version.spec.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.fecsrc: -------------------------------------------------------------------------------- 1 | files: 2 | - "lib" 3 | - "cli" 4 | - "test/**/*.spec.js" 5 | - "!test/fixture/**/*" 6 | eslint: 7 | env: 8 | node: true 9 | rules: 10 | no-console: 0 11 | fecs-camelcase: 12 | - 2 13 | - 14 | ignore: 15 | - "/-/" 16 | csshint: {} 17 | htmlcs: {} 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node.js 2 | node_modules 3 | npm-debug.log 4 | tmp 5 | 6 | # Windows 7 | Thumbs.db 8 | 9 | # Mac 10 | .DS_Store 11 | 12 | # WebStorm 13 | .idea/ 14 | *.ipr 15 | *.iws 16 | *.iml 17 | 18 | # NPM 19 | *.tgz 20 | 21 | *.orig 22 | *.log 23 | 24 | coverage 25 | .vscode/ 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | *.log 4 | *.node 5 | *.orig 6 | .* 7 | test 8 | format 9 | coverage 10 | out 11 | output 12 | lib/reporter/baidu/cover.js 13 | lib/reporter/baidu/uncover.js 14 | public 15 | appveyor.yml 16 | 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "8" 5 | - "10" 6 | - "11" 7 | after_script: 8 | - npm run coveralls 9 | sudo: false 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FECS 2 | ========== 3 | 4 | FECS 是基于 Node.js 的前端代码风格工具。 5 | 6 | [![Build Status](https://img.shields.io/travis/ecomfe/fecs.svg?style=flat)](http://travis-ci.org/ecomfe/fecs) 7 | [![Build Status](https://img.shields.io/appveyor/ci/chriswong/fecs.svg?style=flat)](https://ci.appveyor.com/project/chriswong/fecs) 8 | [![NPM version](https://img.shields.io/npm/v/fecs.svg?style=flat)](https://www.npmjs.com/package/fecs) 9 | [![Coverage Status](https://img.shields.io/coveralls/ecomfe/fecs.svg?style=flat)](https://coveralls.io/r/ecomfe/fecs) 10 | [![Dependencies](https://img.shields.io/david/ecomfe/fecs.svg?style=flat)](https://david-dm.org/ecomfe/fecs) 11 | [![DevDependencies](https://img.shields.io/david/dev/ecomfe/fecs.svg?style=flat)](https://david-dm.org/ecomfe/fecs) [![Greenkeeper badge](https://badges.greenkeeper.io/ecomfe/fecs.svg)](https://greenkeeper.io/) 12 | 13 | 14 | ### 安装 15 | 16 | ``` 17 | $ [sudo] npm install fecs -g 18 | ``` 19 | 20 | ### 使用 21 | 22 | ``` 23 | fecs 24 | fecs -v 25 | fecs check --help 26 | fecs format --help 27 | ``` 28 | 29 | 更多参数见 wiki: [CLI](https://github.com/ecomfe/fecs/wiki/CLI) 30 | 31 | ### API 32 | 33 | #### fecs.leadName 34 | 35 | 设置或获取控制台输出信息前的名称,默认值为 `fecs`。 36 | 37 | ```javascript 38 | var fecs = require('fecs'); 39 | fecs.leadName = 'edp'; 40 | ... 41 | ``` 42 | 43 | #### fecs.getOptions(Array argv) 44 | 45 | 获取经 `minimist` 解释后的命令行参数对象,可用于 `fecs.check` 和 `fecs.format` 方法。 46 | 47 | ```javascript 48 | var options = fecs.getOptions(process.argv.slice(2)); 49 | 50 | console.log(options.command); // 'check' 51 | ... 52 | ``` 53 | 54 | #### fecs.check(Object options[, Function done]) 55 | 56 | 检查文件或输入流的代码规范。 57 | 58 | ```javascript 59 | // 设置检查的文件路径 60 | options._ = ['/path/to/check']; 61 | 62 | // 或者设置为 stream 63 | // options.stream = yourReadableStream; 64 | 65 | // 设置文件类型 66 | // options.type = 'js,css'; 67 | 68 | 69 | /** 70 | * callback after check finish 71 | * 72 | * @param {boolean} success true as all files ok, or false. 73 | * @param {Object[]} errors data for check result. 74 | */ 75 | function done(success, errors) { 76 | // blablabla 77 | } 78 | 79 | fecs.check(options, done); 80 | ``` 81 | 82 | #### fecs.format(Object options) 83 | 84 | 格式化、修复文件或输入流的代码。 85 | 86 | ```javascript 87 | fecs.check(options); 88 | ``` 89 | 90 | 91 | ### 工具支持 92 | 93 | - [x] [VIM](https://github.com/hushicai/fecs.vim) 94 | - [x] [WebStorm](https://github.com/leeight/Baidu-FE-Code-Style#webstorm) 95 | - [x] [Eclipse](https://github.com/ecomfe/fecs-eclipse) 96 | - [x] Sublime Text 2/3 [Baidu FE Code Style](https://github.com/leeight/Baidu-FE-Code-Style) [Sublime Helper](https://github.com/baidu-lbs-opn-fe/Sublime-fecsHelper) [SublimeLinter-contrib-fecs](https://github.com/robbenmu/SublimeLinter-contrib-fecs) 97 | - [x] Visual Studio Code [fecs-visual-studio-code](https://github.com/21paradox/fecs-visual-studio-code) [vscode-fecs(中文)](https://github.com/MarxJiao/VScode-fecs) 98 | - [x] [Atom](https://github.com/8427003/atom-fecs) 99 | - [x] [Emacs](https://github.com/Niandalu/flycheck-fecs) 100 | 101 | - [x] [Grunt](https://github.com/ecomfe/fecs-grunt) 102 | - [x] [Gulp](https://github.com/ecomfe/fecs-gulp) 103 | 104 | - [x] [Git Hook](https://github.com/cxtom/fecs-git-hooks) 105 | 106 | 107 | ### 常见问题 108 | 109 | - 110 | - 111 | 112 | 更多信息请访问 113 | 114 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor file 2 | # http://www.appveyor.com/docs/appveyor-yml 3 | 4 | # Build version format 5 | version: "{build}" 6 | 7 | # What combinations to test 8 | environment: 9 | matrix: 10 | - nodejs_version: 6 11 | - nodejs_version: 8 12 | - nodejs_version: 10 13 | - nodejs_version: 11 14 | 15 | install: 16 | # Get the latest stable version of Node.js or io.js 17 | - ps: Install-Product node $env:nodejs_version 18 | # install modules 19 | - npm install 20 | 21 | build: off 22 | 23 | test_script: 24 | - npm test 25 | 26 | matrix: 27 | fast_finish: true # set this flag to immediately finish build once one of the jobs fails. 28 | -------------------------------------------------------------------------------- /bin/fecs: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var cli = require('../lib/cli'); 4 | cli.parse(); 5 | 6 | -------------------------------------------------------------------------------- /doc/check.md: -------------------------------------------------------------------------------- 1 | check 2 | --------- 3 | ### Usage 4 | 5 | fecs [target...] 6 | fecs check [target...] 7 | fecs check [target...] --type=js,css,html 8 | 9 | 10 | ### Description 11 | 12 | 使用 `eslint` 对`当前目录`下所有 JavaScript 代码进行检测。 13 | 使用 `csshint` 对`当前目录`下所有 CSS 代码进行检测。 14 | 使用 `htmlcs` 对`当前目录`下所有 HTML 代码进行检测。 15 | 16 | 访问 https://github.com/ecomfe/fecs/wiki 获取更多信息 17 | -------------------------------------------------------------------------------- /doc/fecs.md: -------------------------------------------------------------------------------- 1 | fecs 2 | --------- 3 | ### Usage 4 | 5 | fecs [check] [target...] 6 | fecs format [target...] 7 | 8 | 9 | ### Description 10 | 11 | 对`当前目录`下所有 JavaScript 代码进行风格检查或格式化。 12 | 13 | 访问 https://github.com/ecomfe/fecs/wiki 获取更多信息 14 | -------------------------------------------------------------------------------- /doc/format.md: -------------------------------------------------------------------------------- 1 | format 2 | --------- 3 | ### Usage 4 | 5 | fecs format [target...] 6 | fecs format [target...] --type=js,css 7 | 8 | 9 | ### Description 10 | 11 | 使用 `fixmyjs` 与 `jformatter` 对`当前目录`下所有 JavaScript 代码进行格式化。 12 | 使用 `cssbeautify` 与 `csscomb` 对`当前目录`下所有 CSS 代码进行格式化。 13 | 使用 `htmlcs` 对`当前目录`下所有 HTML 代码进行格式化,对于 HTML 中的 JavaScript 和 CSS 代码,使用以上模块格式化。 14 | 15 | 访问 https://github.com/ecomfe/fecs/wiki 获取更多信息 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file command line interface 3 | * @author chris 4 | */ 5 | 6 | var leadName = require('./package').name; 7 | 8 | Object.defineProperties(exports, { 9 | getOptions: { 10 | get: function () { 11 | return require('./lib/cli').getOptions; 12 | } 13 | }, 14 | 15 | leadName: { 16 | set: function (value) { 17 | leadName = value; 18 | }, 19 | 20 | get: function () { 21 | return leadName; 22 | } 23 | }, 24 | 25 | check: { 26 | get: function () { 27 | return require('./cli/check').run; 28 | } 29 | }, 30 | 31 | format: { 32 | get: function () { 33 | return require('./cli/format').run; 34 | } 35 | } 36 | }); 37 | -------------------------------------------------------------------------------- /lib/checker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file jscs checker 3 | * @author chris 4 | */ 5 | var util = require('util'); 6 | 7 | var fecsUtil = require('./util'); 8 | 9 | /** 10 | * Checker 基类 11 | * 12 | * @constructor 13 | * @param {Object} options 配置项 14 | * @param {string} options.type 文件类型,可选值有 js | css | less | html 15 | * @param {string} options.name Checker 的名称,用于错误信息的标识 16 | * @param {(string|RegExp)} options.suffix 逗号分隔的文件扩展名或能匹配文件的正则 17 | * @param {(string|RegExp)} ignore 忽略的文件名列表或匹配的正则 18 | */ 19 | var Checker = function (options) { 20 | this.options = fecsUtil.mix({ 21 | type: '', 22 | name: '', 23 | suffix: '', 24 | ignore: '' 25 | }, options); 26 | }; 27 | 28 | /** 29 | * 检查当前文件是否可被检查 30 | * 31 | * @param {module:vinyl} file vinly 文件对象 32 | * @return {boolean} 文件符合 suffix 指定的格式,不被 ignore 忽略并且不为空时返回 true,否则为 false 33 | */ 34 | Checker.prototype.isValid = function (file) { 35 | var options = this.options; 36 | var path = file.path; 37 | 38 | if (!this._suffixReg) { 39 | this._suffixReg = fecsUtil.buildRegExp(options.suffix); 40 | } 41 | 42 | var result = this._suffixReg.test(path); 43 | 44 | if (options.ignore) { 45 | this._ignoreReg = this._ignoreReg || fecsUtil.buildRegExp(options.ignore); 46 | result = result && !this._ignoreReg.test(path); 47 | } 48 | 49 | return result && !file.isNull(); 50 | }; 51 | 52 | /** 53 | * 执行对文件内容的检查 54 | * 55 | * @abstract 56 | * @param {string} contents 文件内容 57 | * @param {string} path 文件路径 58 | * @param {Object} cliOptions 命令行中传过来的配置项 59 | * @param {Function=} callback 出现此参数时,在检查完成需要调用 callback 60 | * @return {(void|Object[]|Promise.)} 返回错误信息的数组或 Promise 对象 61 | */ 62 | Checker.prototype.check = function (contents, path, cliOptions) { 63 | throw new Error('check method not implement yet.'); 64 | }; 65 | 66 | /** 67 | * 对文件流执行检查 68 | * 69 | * @param {Object} cliOptions 命令行传来的配置项 70 | * @return {module:through2} through2 的转换流 71 | */ 72 | Checker.prototype.exec = function (cliOptions) { 73 | 74 | var checker = this; 75 | 76 | return fecsUtil.mapStream( 77 | function (file, cb) { 78 | if (!checker.isValid(file) || file.stat.size > cliOptions.maxsize) { 79 | cb(null, file); 80 | return; 81 | } 82 | 83 | var contents = file.contents.toString(); 84 | 85 | var done = function (errors) { 86 | file.errors = errors; 87 | cb(null, file); 88 | }; 89 | 90 | // callback style 91 | if (checker.check.length > 3) { 92 | return checker.check(contents, file.path, cliOptions, done); 93 | } 94 | 95 | var promise = checker.check(contents, file.path, cliOptions); 96 | 97 | // sync style 98 | if (util.isArray(promise)) { 99 | return done(promise); 100 | } 101 | 102 | // async style with promise 103 | promise.then(done, done); 104 | } 105 | ); 106 | }; 107 | 108 | module.exports = Checker; 109 | -------------------------------------------------------------------------------- /lib/cli.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file command line interface 3 | * @author chris 4 | */ 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var util = require('util'); 9 | var msee = require('msee'); 10 | var minimist = require('minimist'); 11 | 12 | /** 13 | * 显示指定命令的帮助 14 | * 15 | * @param {string} cmd 命令名 16 | */ 17 | function displayHelp(cmd) { 18 | var file = path.join(__dirname, '../doc', cmd + '.md'); 19 | var doc; 20 | 21 | if (fs.existsSync(file)) { 22 | doc = msee.parseFile(file); 23 | } 24 | else { 25 | doc = msee.parse( 26 | util.format('Have no help for command named `%s`, check your input please.', cmd) 27 | ); 28 | } 29 | 30 | console.log(doc); 31 | } 32 | 33 | /** 34 | * 显示 package.json 中的版本号 35 | * 36 | * @param {string[]=} modules 指定的模块名称集 37 | */ 38 | function displayVersion(modules) { 39 | var pkg = require('../package'); 40 | console.log('%s %s', pkg.name, pkg.version); 41 | 42 | var versions = require('./version')(modules); 43 | Object.keys(versions).forEach(function (name) { 44 | console.log(' %s@%s', name, versions[name]); 45 | }); 46 | } 47 | 48 | /** 49 | * 获取命令行选项对象 50 | * 51 | * @param {Array=} argv 传入的命令行参数 52 | * @return {Object} minimist 对命令行参数解释后的对象 53 | */ 54 | exports.getOptions = function (argv) { 55 | var options = minimist( 56 | argv || [], 57 | { 58 | 'boolean': [ 59 | 'color', 'debug', 'fix', 'help', 'lookup', 60 | 'rule', 'replace', 'silent', 'sort', 'stream', 'version' 61 | ], 62 | 'string': ['_', 'format', 'output', 'reporter'], 63 | 'default': { 64 | color: true, 65 | es: 6, 66 | fix: false, 67 | format: '', 68 | lookup: true, 69 | maxerr: 0, 70 | maxsize: 1024 * 900, 71 | output: './output', 72 | reporter: '' 73 | }, 74 | 'alias': { 75 | c: 'color', 76 | h: 'help', 77 | g: 'god', 78 | o: 'output', 79 | r: 'stream', 80 | s: 'silent', 81 | t: 'type', 82 | v: 'version' 83 | } 84 | } 85 | ); 86 | 87 | 88 | var cmd = options._[0]; 89 | 90 | if (cmd && fs.existsSync(path.join(__dirname, '../cli', cmd + '.js'))) { 91 | cmd = options._.shift(); 92 | } 93 | else if (!options.help) { 94 | cmd = 'check'; 95 | } 96 | 97 | if (options.silent && (options.format || !options.stream)) { 98 | console.log = function () {}; 99 | } 100 | 101 | options.command = cmd; 102 | process.env.DEBUG = options.debug; 103 | 104 | return options; 105 | }; 106 | 107 | /** 108 | * 命令行参数处理 109 | * 110 | * @return {void} 无返回 111 | */ 112 | exports.parse = function () { 113 | var options = exports.getOptions(process.argv.slice(2)); 114 | var cmd = options.command; 115 | 116 | if (options.version) { 117 | return displayVersion(options._); 118 | } 119 | 120 | if (options.help) { 121 | return displayHelp(cmd || 'fecs'); 122 | } 123 | 124 | require('../cli/' + cmd).run(options); 125 | }; 126 | -------------------------------------------------------------------------------- /lib/css/checker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file css checker 3 | * @author chris 4 | */ 5 | 6 | var csshint = require('csshint'); 7 | 8 | var util = require('../util'); 9 | var Checker = require('../checker'); 10 | 11 | var checker = new Checker({ 12 | name: 'csshint', 13 | type: 'css', 14 | suffix: 'css', 15 | ignore: 'm.css,min.css' 16 | }); 17 | 18 | 19 | /** 20 | * 执行对 CSS 文件内容的检查 21 | * 22 | * @param {string} contents 文件内容 23 | * @param {string} path 文件路径 24 | * @param {Object} cliOptions 命令行中传过来的配置项 25 | * @return {Array.} 返回错误信息的数组 26 | */ 27 | checker.check = function (contents, path, cliOptions) { 28 | 29 | var options = this.options; 30 | var name = options.name; 31 | var type = options.type; 32 | 33 | var config = util.getConfig(name, cliOptions.lookup && path); 34 | 35 | var errors = []; 36 | var success = function (result) { 37 | result[0] && result[0].messages.forEach(function (error) { 38 | errors.push( 39 | util.parseError( 40 | { 41 | type: type, 42 | checker: name, 43 | rule: error.ruleName 44 | }, 45 | error 46 | ) 47 | ); 48 | }); 49 | 50 | return errors; 51 | }; 52 | 53 | var failure = function (reasons) { 54 | errors.push( 55 | util.parseError( 56 | { 57 | type: type, 58 | checker: name, 59 | code: '999' 60 | }, 61 | reasons[0] && reasons[0].messages[0] 62 | ) 63 | ); 64 | 65 | return errors; 66 | }; 67 | 68 | config['max-error'] = cliOptions.maxerr; 69 | return csshint 70 | .checkString(contents, path, config) 71 | .then(success, failure); 72 | }; 73 | 74 | module.exports = checker; 75 | -------------------------------------------------------------------------------- /lib/css/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CSS 检查规则配置模块 3 | * @author chris 4 | */ 5 | 6 | var util = require('../util'); 7 | 8 | // 读取当前目录下的 json 文件,以文件名为 key 对外 export 9 | // json 配置文件中可以使用 javascript 注释 10 | util.readConfigs(__dirname, exports); 11 | -------------------------------------------------------------------------------- /lib/css/csshint.yml: -------------------------------------------------------------------------------- 1 | # ids: false 2 | # box-model: false 3 | # outline-none: false 4 | # text-indent: false 5 | # qualified-headings: false 6 | # unique-headings: false 7 | # underscore-property-hack: false 8 | # star-property-hack: false 9 | # overqualified-elements: false 10 | 11 | no-bom: true 12 | block-indent: 13 | - " " 14 | - 0 15 | require-before-space: 16 | - "{" 17 | require-after-space: 18 | - ":" 19 | - "," 20 | max-length: 120 21 | #require-after-linebreak: 22 | # - " " 23 | # - "," 24 | require-newline: 25 | - "selector" 26 | - "property" 27 | - "media-query-condition" 28 | require-around-space: 29 | - ">" 30 | - "+" 31 | - "~" 32 | require-doublequotes: 33 | - "attr-selector" 34 | - "text-content" 35 | always-semicolon: true 36 | disallow-overqualified-elements: true 37 | max-selector-nesting-level: 3 38 | shorthand: 39 | - "property" 40 | - "color" 41 | #group-properties: true 42 | disallow-important: true 43 | leading-zero: true 44 | disallow-quotes-in-url: true 45 | omit-protocol-in-url: true 46 | zero-unit: true 47 | hex-color: true 48 | disallow-named-color: true 49 | unifying-color-case-sensitive: true 50 | horizontal-vertical-position: true 51 | #font-family-space-in-quotes: true 52 | #font-family-sort: true 53 | unifying-font-family-case-sensitive: true 54 | min-font-size: 12 55 | require-number: 56 | - "font-weight" 57 | - "line-height" 58 | require-transition-property: true 59 | vendor-prefixes-sort: true 60 | disallow-expression: true 61 | property-not-existed: true 62 | 63 | -------------------------------------------------------------------------------- /lib/css/formatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file css formatter 3 | * @author chris 4 | */ 5 | 6 | var Comb = require('csscomb'); 7 | 8 | var util = require('../util'); 9 | var Formatter = require('../formatter'); 10 | 11 | 12 | var formatter = new Formatter({ 13 | name: 'csscomb', 14 | type: 'css', 15 | suffix: /\.(?:c|le|sa|sc)ss$/ 16 | }); 17 | 18 | var csscomb = new Comb('csscomb', 'css', 'less', 'scss', 'sass'); 19 | csscomb.use(require('./rules/space-after-value')); 20 | 21 | /** 22 | * 执行对 CSS 文件内容的格式化 23 | * 24 | * @param {string} contents 文件内容 25 | * @param {string} path 文件路径 26 | * @param {Object} cliOptions 命令行中传过来的配置项 27 | * @return {string} 返回格式化后的内容 28 | */ 29 | formatter.format = function (contents, path, cliOptions) { 30 | 31 | var name = this.options.name; 32 | var config = util.getConfig(name, cliOptions.lookup && path); 33 | 34 | csscomb.configure(config); 35 | 36 | var syntax = /\.html?$/.test(path) ? 'css' : path.split('.').pop(); 37 | 38 | return csscomb 39 | .processString(contents, {syntax: syntax}) 40 | .catch(function (error) { 41 | return cliOptions.debug ? Promise.reject(error) : contents; 42 | }); 43 | }; 44 | 45 | module.exports = formatter; 46 | -------------------------------------------------------------------------------- /lib/css/rules/space-after-value.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file csscomb rule 3 | * @author chris 4 | */ 5 | 6 | var path = require('path'); 7 | var Module = require('module'); 8 | 9 | function createModule(filename) { 10 | var mod = new Module(filename); 11 | mod.filename = filename; 12 | mod.paths = Module._nodeModulePaths(path.dirname(filename)); 13 | return mod; 14 | } 15 | 16 | 17 | var csscomb = createModule(Module._resolveFilename('csscomb', module.parent)); 18 | var gonzales = createModule(Module._resolveFilename('gonzales-pe', csscomb)); 19 | 20 | module.exports = { 21 | name: 'space-after-value', 22 | 23 | runBefore: 'block-indent', 24 | 25 | syntax: ['css', 'less', 'sass', 'scss'], 26 | 27 | accepts: { 28 | number: true, 29 | string: /^[ \t\n]*$/ 30 | }, 31 | 32 | /** 33 | * Processes tree node. 34 | * 35 | * @param {node} node AST node 36 | */ 37 | process: function (node) { 38 | if (!node.is || !node.is('block')) { 39 | return; 40 | } 41 | 42 | var value = this.getValue(module.exports.name); 43 | 44 | for (var i = node.length; i--;) { 45 | if (!node.get(i).is('declarationDelimiter')) { 46 | continue; 47 | } 48 | 49 | var hasSpace = node.get(i - 1).is('space'); 50 | 51 | if (!value && hasSpace) { 52 | node.remove(i - 1); 53 | continue; 54 | } 55 | 56 | if (value && !hasSpace) { 57 | var space = gonzales.createNode({type: 'space', content: value}); 58 | node.insert(i, space); 59 | continue; 60 | } 61 | } 62 | }, 63 | 64 | /** 65 | * Detects the value of an option at the tree node. 66 | * 67 | * @param {node} node AST node 68 | * @return {string} the config value 69 | */ 70 | detect: function (node) { 71 | if (!node.is('block')) { 72 | return; 73 | } 74 | 75 | for (var i = node.length; i--;) { 76 | if (!node.get(i).is('declarationDelimiter')) { 77 | continue; 78 | } 79 | 80 | return node.get(i - 1).is('space') ? node.get(i - 1).content : ''; 81 | } 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /lib/formatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file js formatter 3 | * @author chris 4 | */ 5 | 6 | var util = require('util'); 7 | var events = require('events'); 8 | 9 | var fecsUtil = require('./util'); 10 | 11 | /** 12 | * Formatter 基类 13 | * 14 | * @constructor 15 | * @extends module:events.EventEmitter 16 | * @param {Object} options 配置项 17 | * @param {string} options.type 文件类型,可选值有 js | css | less | html 18 | * @param {string} options.name Formatter 的名称,用于错误信息的标识 19 | * @param {(string|RegExp)} options.suffix 逗号分隔的文件扩展名或能匹配文件的正则 20 | * @param {(string|RegExp)} ignore 忽略的文件名列表或匹配的正则 21 | */ 22 | var Formatter = function (options) { 23 | this.options = fecsUtil.mix({ 24 | type: '', 25 | name: '', 26 | suffix: '', 27 | ignore: '' 28 | }, options); 29 | 30 | events.EventEmitter.call(this); 31 | }; 32 | 33 | util.inherits(Formatter, events.EventEmitter); 34 | 35 | 36 | /** 37 | * 检查当前文件是否可被格式化 38 | * 39 | * @param {module:vinyl} file vinly 文件对象 40 | * @return {boolean} 文件符合 suffix 指定的格式,不被 ignore 忽略并且不为空时返回 true,否则为 false 41 | */ 42 | Formatter.prototype.isValid = function (file) { 43 | var options = this.options; 44 | var path = file.path; 45 | 46 | if (!this._suffixReg) { 47 | this._suffixReg = fecsUtil.buildRegExp(options.suffix); 48 | } 49 | 50 | var result = this._suffixReg.test(path); 51 | 52 | if (options.ignore) { 53 | this._ignoreReg = this._ignoreReg || fecsUtil.buildRegExp(options.ignore); 54 | result = result && !this._ignoreReg.test(path); 55 | } 56 | 57 | return result && !file.isNull(); 58 | }; 59 | 60 | /** 61 | * 注册 Formatter 额外的规则 62 | * 63 | */ 64 | Formatter.prototype.register = function () {}; 65 | 66 | 67 | 68 | /** 69 | * 执行对文件内容的格式化 70 | * 71 | * @abstract 72 | * @param {string} contents 文件内容 73 | * @param {string} path 文件路径 74 | * @param {Object} cliOptions 命令行中传过来的配置项 75 | * @param {Function=} callback 出现此参数时,在检查完成需要调用 callback 76 | * @return {(void|string|Promise.)} 返回格式化后的内容或 Promise 对象 77 | */ 78 | Formatter.prototype.format = function (contents, path, cliOptions) { 79 | throw new Error('format method not implement yet.'); 80 | }; 81 | 82 | 83 | /** 84 | * 对文件流执行格式化 85 | * 86 | * @param {Object} cliOptions 命令行传来的配置项 87 | * @return {module:through2} through2 的转换流 88 | */ 89 | Formatter.prototype.exec = function (cliOptions) { 90 | this.register(); 91 | 92 | var formatter = this; 93 | 94 | return fecsUtil.mapStream( 95 | function (file, cb) { 96 | if (!formatter.isValid(file) || file.stat.size > cliOptions.maxsize) { 97 | cb(null, file); 98 | return; 99 | } 100 | 101 | var contents = file.contents.toString(); 102 | var done = function (contents) { 103 | file.contents = Buffer.from(contents); 104 | cb(null, file); 105 | }; 106 | 107 | // callback style 108 | if (formatter.format.length > 3) { 109 | return formatter.format(contents, file.path, cliOptions, done); 110 | } 111 | 112 | var promise = formatter.format(contents, file.path, cliOptions); 113 | 114 | // sync style 115 | if (typeof promise === 'string') { 116 | return done(promise); 117 | } 118 | 119 | // async style with promise 120 | promise.then(done, done); 121 | } 122 | ); 123 | }; 124 | 125 | module.exports = Formatter; 126 | -------------------------------------------------------------------------------- /lib/formatter/checkstyle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file XML formatter for check results 3 | * @author leeight 4 | */ 5 | 6 | function escape(text) { 7 | return text 8 | .replace(/&/g, '&') 9 | .replace(/"/g, '"') 10 | .replace(//g, '>') 12 | .replace(/'/g, '''); 13 | } 14 | 15 | module.exports = function (json) { 16 | 17 | var header = '' 18 | + '' 19 | + ''; 20 | 21 | 22 | var footer = '' 23 | + ''; 24 | 25 | var body = []; 26 | json.forEach(function (file) { 27 | var div = ['']; 28 | 29 | file.errors.forEach(function (error) { 30 | div.push(''); 38 | }); 39 | 40 | div.push(''); 41 | body.push(div.join('')); 42 | }); 43 | 44 | var html = header + body.join('') + footer; 45 | 46 | process.stdout.write(html); 47 | }; 48 | -------------------------------------------------------------------------------- /lib/formatter/html.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file HTML formatter for check results 3 | * @author chris 4 | */ 5 | 6 | module.exports = function (json) { 7 | 8 | var header = '' 9 | + '' 10 | + '' 11 | + '' 12 | + '' 13 | + 'fecs check results' 14 | + '' 15 | + '' 16 | + '

FECS Check Results

'; 17 | 18 | var footer = '' 19 | + '' 20 | + ''; 21 | 22 | var body = []; 23 | json.forEach(function (file) { 24 | var div = ['
']; 25 | 26 | div.push('

' + file.path + '

'); 27 | div.push('
    '); 28 | file.errors.forEach(function (error) { 29 | div.push('
  1. '); 30 | div.push('line ' + error.line + ' '); 31 | div.push('column ' + error.column + ': '); 32 | div.push(error.message); 33 | div.push(' (' + error.rule + ')'); 34 | div.push('
  2. '); 35 | }); 36 | 37 | div.push('
'); 38 | div.push(''); 39 | body.push(div.join('')); 40 | }); 41 | 42 | var html = header + body.join('') + footer + '\n'; 43 | 44 | process.stdout.write(html); 45 | }; 46 | -------------------------------------------------------------------------------- /lib/formatter/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file additional rules for eslint 3 | * @author chris 4 | */ 5 | 6 | 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | 11 | var dir = __dirname; 12 | var cur = path.relative(dir, __filename); 13 | var reg = /([^\\\/]+)\.js$/i; 14 | 15 | fs.readdirSync(dir).forEach(function (file) { 16 | if (file === cur) { 17 | return; 18 | } 19 | 20 | var match = file.match(reg); 21 | if (match) { 22 | var key = match[1].replace(/[A-Z]/g, function (a) { 23 | return '-' + a.toLowerCase(); 24 | }); 25 | 26 | exports[key] = require(path.join(dir, file)); 27 | } 28 | }); 29 | -------------------------------------------------------------------------------- /lib/formatter/json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file JSON formatter for check results 3 | * @author chris 4 | */ 5 | 6 | module.exports = function (json) { 7 | process.stdout.write(JSON.stringify(json) + '\n'); 8 | }; 9 | -------------------------------------------------------------------------------- /lib/formatter/xml.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file XML formatter for check results 3 | * @author chris 4 | */ 5 | 6 | module.exports = function (json) { 7 | 8 | var header = '' 9 | + '' 10 | + ''; 11 | 12 | 13 | var footer = '' 14 | + ''; 15 | 16 | var body = []; 17 | json.forEach(function (file) { 18 | var div = ['']; 19 | 20 | file.errors.forEach(function (error) { 21 | div.push(''); 26 | div.push(error.message); 27 | div.push(''); 28 | }); 29 | 30 | div.push(''); 31 | body.push(div.join('')); 32 | }); 33 | 34 | var html = header + body.join('') + footer; 35 | 36 | process.stdout.write(html); 37 | }; 38 | -------------------------------------------------------------------------------- /lib/html/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file HTML 检查规则配置模块 3 | * @author chris 4 | */ 5 | 6 | var util = require('../util'); 7 | 8 | // 读取当前目录下的 json 文件,以文件名为 key 对外 export 9 | // json 配置文件中可以使用 javascript 注释 10 | util.readConfigs(__dirname, exports); 11 | -------------------------------------------------------------------------------- /lib/html/formatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file css formatter 3 | * @author chris 4 | */ 5 | 6 | var htmlcs = require('htmlcs'); 7 | 8 | var util = require('../util'); 9 | var Formatter = require('../formatter'); 10 | 11 | var jsformatter = require('../js/formatter'); 12 | var cssformatter = require('../css/formatter'); 13 | 14 | var formatter = new Formatter({ 15 | name: 'htmlcs', 16 | type: 'html', 17 | suffix: 'html,htm' 18 | }); 19 | 20 | /** 21 | * 为 htmlcs 生成用于 HTML 内 script 与 style 标签内容的 formatter 22 | * 23 | * @param {string} path HTML 的文件路径,主要用于查找相关配置 24 | * @param {Object} options CLI 传来的配置 25 | * @return {Object} 26 | */ 27 | var buildFormatter = function (path, options) { 28 | return { 29 | formatter: { 30 | 31 | /** 32 | * 对 script 标签内容的 formatter 33 | * 34 | * @param {string} content 标签内容 35 | * @param {Node} node script 标签对象 36 | * @param {Object} opt 配置对象 37 | * @param {Object} helper 包含 indent 方法的对象 38 | * @return {string} 格式化后的内容 39 | */ 40 | script: function (content, node, opt, helper) { 41 | var type = node.getAttribute('type'); 42 | 43 | // javascript content 44 | if (!type || type === 'text/javascript') { 45 | var formatted = jsformatter.format(content, path, options); 46 | 47 | opt.level--; 48 | content = helper.indent(formatted); 49 | } 50 | 51 | return Promise.resolve(content.replace(/(^\s*\n)|(\n\s*$)/g, '')); 52 | }, 53 | 54 | /** 55 | * 对 style 标签内容的 formatter 56 | * 57 | * @param {string} content 标签内容 58 | * @param {Node} node style 标签对象 59 | * @param {Object} opt 配置对象 60 | * @param {Object} helper 包含 indent 方法的对象 61 | * @return {Promise.} 格式化后的内容 62 | */ 63 | style: function (content, node, opt, helper) { 64 | var type = node.getAttribute('type'); 65 | var pattern = /(^\s*\n)|(\n\s*$)/g; 66 | 67 | // style content 68 | if (!type || type === 'text/css') { 69 | return cssformatter 70 | .format(content, path, options) 71 | .then(function (formatted) { 72 | opt.level--; 73 | return helper.indent(formatted).replace(pattern, ''); 74 | }); 75 | 76 | } 77 | 78 | 79 | return Promise.resolve(content.replace(pattern, '')); 80 | } 81 | } 82 | }; 83 | }; 84 | 85 | /** 86 | * 执行对 HTML 文件内容的格式化 87 | * 88 | * @param {string} contents 文件内容 89 | * @param {string} path 文件路径 90 | * @param {Object} cliOptions 命令行中传过来的配置项 91 | * @return {Promise.} 返回格式化后的内容 92 | */ 93 | formatter.format = function (contents, path, cliOptions) { 94 | 95 | var name = this.options.name; 96 | var config = util.getConfig(name, cliOptions.lookup && path); 97 | 98 | config.format = util.extend(buildFormatter(path, cliOptions), config.format); 99 | 100 | return htmlcs.formatAsync(contents, config); 101 | }; 102 | 103 | module.exports = formatter; 104 | -------------------------------------------------------------------------------- /lib/html/htmlcs.yml: -------------------------------------------------------------------------------- 1 | asset-type: true 2 | bool-attribute-value: true 3 | button-name: true 4 | button-type: true 5 | charset: true 6 | css-in-head: true 7 | doctype: true 8 | html-lang: true 9 | ie-edge: true 10 | img-alt: true 11 | img-src: true 12 | img-title: true 13 | img-width-height: true 14 | indent-char: "space-4" 15 | lowercase-class-with-hyphen: true 16 | lowercase-id-with-hyphen: true 17 | nest: true 18 | rel-stylesheet: true 19 | script-in-tail: true 20 | title-required: true 21 | unique-id: true 22 | no-duplication-id-and-name: true 23 | no-meta-css: 24 | threshold: 4 25 | minlen: 4 26 | no-hook-class: 27 | keys: 28 | - "/[-_#](?:js|hook)[-_#]/" 29 | max-len: 80 30 | no-bom: true 31 | viewport: true 32 | label-for-input: true 33 | attr-lowercase: true 34 | attr-no-duplication: true 35 | attr-unsafe-chars: true 36 | attr-value-double-quotes: true 37 | id-class-ad-disabled: true 38 | spec-char-escape: true 39 | self-close: "no-close" 40 | style-disabled: true 41 | tag-pair: true 42 | tagname-lowercase: true 43 | script-content: true 44 | style-content: true 45 | 46 | -------------------------------------------------------------------------------- /lib/ignored.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file baidu reporter 3 | * @author chris 4 | */ 5 | 6 | var fs = require('fs'); 7 | 8 | var minimatch = require('minimatch'); 9 | 10 | var util = require('./util'); 11 | 12 | /** 13 | * 配置 fecs 忽略文件的文件名 14 | * 15 | * @const 16 | * @type {string} 17 | */ 18 | var IGNORE_FILENAME = '.fecsignore'; 19 | 20 | /** 21 | * 加载忽略规则文件并解释 cli 参数中的 ignore 22 | * 23 | * @param {string} filename ignore 文件名 24 | * @param {Object} options minimist 处理后的 cli 参数 25 | * @return {Array.} 包含 ignore 规则的字符串数组 26 | */ 27 | function load(filename, options) { 28 | var ignore = options.ignore || []; 29 | var patterns = typeof ignore === 'string' ? [ignore] : ignore; 30 | 31 | function valid(line) { 32 | line = line.trim(); 33 | return line !== '' && line[0] !== '#'; 34 | } 35 | 36 | if (fs.existsSync(filename)) { 37 | return fs.readFileSync(filename, 'utf8').split(/\r?\n/).filter(valid).concat(patterns); 38 | } 39 | 40 | return patterns; 41 | } 42 | 43 | /** 44 | * 根据 .fecsignore 与 --ignore 的规则过滤文件 45 | * 46 | * @param {Object} options 配置项 47 | * @param {Array.} specials 直接指定的目录或文件列表 48 | * @param {string} ignoreFilename 直接指定的忽略文件路径 49 | * @return {Transform} 转换流 50 | */ 51 | module.exports = function (options, specials, ignoreFilename) { 52 | 53 | var patterns = load(ignoreFilename || IGNORE_FILENAME, options); 54 | 55 | return util.mapStream( 56 | function (file, cb) { 57 | var filepath = file.relative.replace('\\', '/'); 58 | 59 | var isSpecial = specials.some(function (dirOrPath) { 60 | return filepath.indexOf(dirOrPath.replace(/^\.\//, '')) === 0; 61 | }); 62 | 63 | var matchOptions = { 64 | dot: true, 65 | nocase: true 66 | }; 67 | 68 | var isIgnore = !isSpecial && patterns.reduce(function (ignored, pattern) { 69 | var negated = pattern[0] === '!'; 70 | var matches; 71 | 72 | if (negated) { 73 | pattern = pattern.slice(1); 74 | } 75 | 76 | matches = minimatch(filepath, pattern, matchOptions) 77 | || minimatch(filepath, pattern + '/**', matchOptions); 78 | var result = matches ? !negated : ignored; 79 | 80 | if (options.debug && result) { 81 | console.log('%s is ignored by %s.', filepath, pattern); 82 | } 83 | 84 | return result; 85 | }, false); 86 | 87 | if (isIgnore) { 88 | cb(); 89 | } 90 | else { 91 | cb(null, file); 92 | } 93 | 94 | } 95 | ); 96 | }; 97 | -------------------------------------------------------------------------------- /lib/js/checker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file eslint checker 3 | * @author chris 4 | */ 5 | 6 | var rules = require('./rules'); 7 | var util = require('../util'); 8 | var esnext = require('./esnext'); 9 | var Checker = require('../checker'); 10 | 11 | var checker = new Checker({ 12 | name: 'eslint', 13 | type: 'js', 14 | suffix: 'js,jsx,es,es6,vue,san,atom', 15 | ignore: /\.(m|min|mock|mockup)\.(js|es|es6)$/ 16 | }); 17 | 18 | rules.register(); 19 | 20 | /** 21 | * 执行对 JS 文件内容的检查 22 | * 23 | * @param {string} contents 文件内容 24 | * @param {string} path 文件路径 25 | * @param {Object} cliOptions 命令行中传过来的配置项 26 | * @return {Array.} 返回错误信息的数组 27 | */ 28 | checker.check = function (contents, path, cliOptions) { 29 | 30 | var options = this.options; 31 | var name = options.name; 32 | var type = options.type; 33 | 34 | var config = util.parseExtends(util.getConfig(name, cliOptions.lookup && path), name); 35 | 36 | var maxerr = 0; 37 | var hasMax = cliOptions.maxerr > 0; 38 | var errors = []; 39 | 40 | esnext.removeRedundantRules(config); 41 | 42 | rules.registerPlugins(config.plugins); 43 | 44 | try { 45 | // 优先使用配置文件里的 ES 版本设置 46 | var ecmaVersion = config.parserOptions && config.parserOptions.ecmaVersion || cliOptions.es; 47 | 48 | if (ecmaVersion < 6) { 49 | config = util.mix( 50 | config, 51 | { 52 | env: {es6: false}, 53 | parser: 'espree', 54 | parserOptions: { 55 | ecmaVersion: ecmaVersion 56 | } 57 | } 58 | ); 59 | } 60 | else if (ecmaVersion > 6 || ecmaVersion === 'next') { 61 | config = util.mix( 62 | config, 63 | { 64 | env: {es6: true} 65 | } 66 | ); 67 | } 68 | 69 | esnext.verify(contents, config, path).forEach(function (error) { 70 | if (hasMax) { 71 | maxerr++; 72 | if (maxerr > cliOptions.maxerr) { 73 | return true; 74 | } 75 | } 76 | 77 | errors.push( 78 | util.parseError( 79 | { 80 | type: type, 81 | checker: name, 82 | rule: error.ruleId 83 | }, 84 | error 85 | ) 86 | ); 87 | }); 88 | } 89 | catch (error) { 90 | errors.push( 91 | util.parseError( 92 | { 93 | type: type, 94 | checker: name, 95 | code: '998' 96 | }, 97 | error 98 | ) 99 | ); 100 | } 101 | 102 | return errors; 103 | }; 104 | 105 | module.exports = checker; 106 | -------------------------------------------------------------------------------- /lib/js/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file JS 检查规则配置模块 3 | * @author chris 4 | */ 5 | 6 | var util = require('../util'); 7 | 8 | // 读取当前目录下的 json 文件,以文件名为 key 对外 export 9 | // json 配置文件中可以使用 javascript 注释 10 | util.readConfigs(__dirname, exports); 11 | -------------------------------------------------------------------------------- /lib/js/formatter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file js formatter 3 | * @author chris 4 | */ 5 | 6 | var CLIEngine = require('eslint').CLIEngine; 7 | var esformatter = require('esformatter'); 8 | 9 | var esnext = require('./esnext'); 10 | var util = require('../util'); 11 | var Formatter = require('../formatter'); 12 | 13 | var formatter = new Formatter({ 14 | name: 'esformatter', 15 | type: 'js', 16 | suffix: 'js,jsx,es,es6' 17 | }); 18 | 19 | /** 20 | * 执行对 JS 文件内容的格式化 21 | * 22 | * @param {string} contents 文件内容 23 | * @param {string} path 文件路径 24 | * @param {Object} cliOptions 命令行中传过来的配置项 25 | * @return {string} 返回格式化后的内容 26 | */ 27 | formatter.format = function (contents, path, cliOptions) { 28 | 29 | var name = this.options.name; 30 | var config = util.getConfig(name, cliOptions.lookup && path); 31 | 32 | if (cliOptions.fix) { 33 | var eslintConfig = util.getConfig('eslint', cliOptions.lookup && path); 34 | esnext.removeRedundantRules(eslintConfig); 35 | 36 | var rules = Object.assign(eslintConfig.rules); 37 | 38 | if (!('indent' in rules)) { 39 | var fecsIndent = rules['fecs-indent']; 40 | rules.indent = [ 41 | fecsIndent[0], 42 | {tab: fecsIndent[1], space: fecsIndent[2]}[fecsIndent[1]] 43 | ]; 44 | } 45 | var cli = new CLIEngine({ 46 | parser: eslintConfig.parser, 47 | envs: Object.keys(eslintConfig.env), 48 | globals: Object.keys(eslintConfig.globals), 49 | plugins: eslintConfig.plugins, 50 | fix: true, 51 | rules: rules 52 | }); 53 | var report = cli.executeOnText(contents, path); 54 | 55 | contents = report.results[0].output || contents; 56 | } 57 | 58 | try { 59 | contents = esformatter.format(contents, config); 60 | } 61 | catch (error) { 62 | if (cliOptions.debug) { 63 | throw error; 64 | } 65 | } 66 | 67 | return contents; 68 | }; 69 | 70 | module.exports = formatter; 71 | -------------------------------------------------------------------------------- /lib/js/rules/arrow-body-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to require braces in arrow function body. 3 | * @author Alberto Rodríguez 4 | * @copyright 2015 Alberto Rodríguez. All rights reserved. 5 | * See LICENSE file in root directory for full license. 6 | */ 7 | 'use strict'; 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | module.exports = { 14 | meta: { 15 | docs: { 16 | description: 'require braces around arrow function bodies', 17 | category: 'ECMAScript 6', 18 | recommended: false 19 | }, 20 | 21 | schema: [ 22 | { 23 | 'enum': ['always', 'as-needed'] 24 | }, 25 | { 26 | type: 'object', 27 | additionalProperties: { 28 | type: 'boolean' 29 | } 30 | } 31 | ] 32 | }, 33 | 34 | create: function (context) { 35 | var always = context.options[0] === 'always'; 36 | var asNeeded = !context.options[0] || context.options[0] === 'as-needed'; 37 | var needMaps = context.options[1] || {}; 38 | 39 | 40 | var EXPECT_MESSAGE = 'Expected block statement surrounding arrow body.'; 41 | var UNEXPECT_MESSAGE = 'Unexpected block statement surrounding arrow body.'; 42 | function report(node, expect) { 43 | context.report(node, node.body.loc.start, expect ? EXPECT_MESSAGE : UNEXPECT_MESSAGE); 44 | } 45 | 46 | /** 47 | * Determines whether a arrow function body needs braces 48 | * 49 | * @param {ASTNode} node The arrow function node. 50 | * @return {void} 51 | */ 52 | function validate(node) { 53 | var arrowBody = node.body; 54 | 55 | var isSurroundedByParentheses = context.getTokenBefore(arrowBody).value === '(' 56 | && context.getTokenAfter(arrowBody).value === ')'; 57 | 58 | if (arrowBody.type === 'BlockStatement') { 59 | var blockBody = arrowBody.body; 60 | var bodyLength = blockBody.length; 61 | var firstNode = blockBody[0]; 62 | 63 | if ( 64 | // allow empty function 65 | bodyLength === 0 && node.params.length > 0 66 | || bodyLength > 0 && firstNode.type === 'LabeledStatement' 67 | ) { 68 | report(node, true); 69 | return; 70 | } 71 | 72 | if (bodyLength > 1) { 73 | return; 74 | } 75 | 76 | if (asNeeded && firstNode && firstNode.type === 'ReturnStatement') { 77 | if (!needMaps[firstNode.argument.type]) { 78 | report(node); 79 | } 80 | } 81 | } 82 | else if (always && !isSurroundedByParentheses) { 83 | report(node, true); 84 | } 85 | } 86 | 87 | return { 88 | ArrowFunctionExpression: validate 89 | }; 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /lib/js/rules/eol-last.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Require file to end with single newline. 3 | * @author Nodeca Team 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = function (context) { 12 | 13 | var disallowMultiBlankLine = context.options[0] !== true; 14 | //-------------------------------------------------------------------------- 15 | // Public 16 | //-------------------------------------------------------------------------- 17 | 18 | return { 19 | 20 | Program: function checkBadEOF(node) { 21 | // Get the whole source code, not for node only. 22 | var src = context.getSource(); 23 | var location = {column: 1}; 24 | 25 | if (src[src.length - 1] !== '\n') { 26 | // file is not newline-terminated 27 | location.line = src.split(/\n/g).length; 28 | context.report(node, location, 'Newline required at end of file but not found.'); 29 | } 30 | else if (disallowMultiBlankLine && /\n\s*\n$/.test(src)) { 31 | // last line is empty 32 | location.line = src.split(/\n/g).length - 1; 33 | context.report(node, location, 'Unexpected blank line at end of file.'); 34 | } 35 | } 36 | 37 | }; 38 | 39 | }; 40 | 41 | 42 | module.exports.schema = [ 43 | { 44 | type: 'boolean' 45 | } 46 | ]; 47 | 48 | 49 | -------------------------------------------------------------------------------- /lib/js/rules/esnext-ext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check esnext file extension 3 | * @author chris 4 | */ 5 | 6 | module.exports = function (context) { 7 | var MESSAGE = 'Expected file extension `{{valid}}` but found `{{ext}}`.baidu{{code}}'; 8 | var RECOMMAND_EXT = 'js'; 9 | var RECOMMAND_EXT_PATTERN = /\.jsx?$/; 10 | 11 | var valids = context.options[0]; 12 | if (!Array.isArray(valids)) { 13 | valids = [valids]; 14 | } 15 | 16 | var validExtensions = valids.join('` or `'); 17 | 18 | function report(node, json) { 19 | context.report( 20 | node, 21 | {line: 1, column: 0}, 22 | MESSAGE, 23 | json 24 | ); 25 | } 26 | 27 | return { 28 | 29 | Program: function (node) { 30 | var filepath = context.getFilename(); 31 | var ext = filepath.split('.').pop().toLowerCase(); 32 | if (ext === '' || ext === RECOMMAND_EXT) { 33 | return; 34 | } 35 | 36 | var isIgnored = valids.some(function (valid) { 37 | return valid === ext; 38 | }); 39 | 40 | if (isIgnored && !RECOMMAND_EXT_PATTERN.test(filepath)) { 41 | report(node, {ext: ext, valid: RECOMMAND_EXT, code: 601}); 42 | } 43 | 44 | if (!isIgnored) { 45 | report(node, {ext: ext, valid: validExtensions, code: 602}); 46 | } 47 | } 48 | }; 49 | 50 | }; 51 | 52 | module.exports.schema = [ 53 | { 54 | oneOf: [ 55 | { 56 | type: 'string' 57 | }, 58 | { 59 | type: 'array', 60 | items: { 61 | type: 'string' 62 | } 63 | } 64 | ] 65 | } 66 | ]; 67 | 68 | -------------------------------------------------------------------------------- /lib/js/rules/export-on-declare.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to flag use of arguments 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | module.exports = function (context) { 13 | 14 | var MESSAGE = 'Use `export` on declare.'; 15 | 16 | function isImportBindingVariable(variable) { 17 | return variable.references.length 18 | && variable.defs.length === 1 19 | && variable.defs[0].type === 'ImportBinding'; 20 | } 21 | function isUnusedVariable(name) { 22 | var variables = context.getScope().variables; 23 | var unused = false; 24 | 25 | for (var i = 0, variable; variable = variables[i++];) { 26 | 27 | if (variable.name !== name) { 28 | continue; 29 | } 30 | 31 | if (variable.eslintUsed || isImportBindingVariable(variable)) { 32 | break; 33 | } 34 | 35 | return variable.references.length < 3; 36 | } 37 | 38 | return unused; 39 | } 40 | 41 | function check(node) { 42 | if (node.type === 'Identifier' && isUnusedVariable(node.name)) { 43 | context.report(node, MESSAGE); 44 | } 45 | } 46 | 47 | return { 48 | 49 | ExportNamedDeclaration: function (node) { 50 | if (!node.declaration && node.specifiers) { 51 | node.specifiers.forEach(function (specifier) { 52 | check(specifier.local || specifier.exported); 53 | }); 54 | } 55 | }, 56 | 57 | ExportDefaultDeclaration: function (node) { 58 | 59 | switch (node.declaration.type) { 60 | case 'Identifier': 61 | check(node.declaration); 62 | break; 63 | case 'ArrayExpression': 64 | node.declaration.elements.forEach(check); 65 | break; 66 | case 'ObjectExpression': 67 | node.declaration.properties.forEach(function (property) { 68 | if (property.type === 'ExperimentalSpreadProperty') { 69 | return; 70 | } 71 | 72 | check(property.value); 73 | }); 74 | break; 75 | } 76 | } 77 | }; 78 | 79 | }; 80 | 81 | module.exports.schema = []; 82 | -------------------------------------------------------------------------------- /lib/js/rules/imports-on-top.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check imports 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | 13 | module.exports = function (context) { 14 | 15 | function report(node) { 16 | context.report(node, 'All `import` statements must be at the top of the module.'); 17 | } 18 | 19 | return { 20 | 21 | 'Program:exit': function (node) { 22 | node.body.reduce(function (invalid, node) { 23 | if (node.type === 'ImportDeclaration' 24 | || node.type === 'ExpressionStatement' 25 | && node.expression.type === 'Literal' 26 | && node.expression.value === 'use strict' 27 | ) { 28 | if (invalid) { 29 | report(node); 30 | } 31 | } 32 | else { 33 | invalid = true; 34 | } 35 | 36 | return invalid; 37 | }, false); 38 | } 39 | }; 40 | 41 | }; 42 | 43 | module.exports.schema = []; 44 | -------------------------------------------------------------------------------- /lib/js/rules/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file additional rules for eslint 3 | * @author chris 4 | */ 5 | 6 | 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | var eslint = require('eslint').linter; 11 | var Config = require('eslint/lib/config'); 12 | var eslintPlugins; 13 | 14 | /** 15 | * 注册扩展的 eslint 校验规则 16 | * 17 | * @param {string=} [dir = __dirname] the directory of rules 18 | */ 19 | exports.register = function (dir) { 20 | dir = dir || __dirname; 21 | 22 | var cur = path.relative(dir, __filename); 23 | var reg = /([^\\\/]+)\.js$/i; 24 | 25 | fs.readdirSync(dir).forEach(function (file) { 26 | if (file === cur) { 27 | return; 28 | } 29 | 30 | var match = file.match(reg); 31 | if (match) { 32 | var key = 'fecs-' + match[1].replace(/[A-Z]/g, function (a) { 33 | return '-' + a.toLowerCase(); 34 | }); 35 | 36 | eslint.defineRule(key, path.join(dir, file)); 37 | } 38 | }); 39 | }; 40 | 41 | /** 42 | * 注册外部插件 43 | * 见 eslint/lib/config/plugins.js load 44 | * 45 | * @param {string[]} plugins 插件名数组 46 | */ 47 | exports.registerPlugins = function (plugins) { 48 | if (!Array.isArray(plugins)) { 49 | plugins = Array.prototype.slice.call(arguments); 50 | } 51 | 52 | if (!eslintPlugins) { 53 | eslintPlugins = new Config({}, eslint).plugins; 54 | } 55 | 56 | plugins.forEach(eslintPlugins.load, eslintPlugins); 57 | }; 58 | 59 | exports.eslintPlugins = function () { 60 | return eslintPlugins; 61 | }; 62 | -------------------------------------------------------------------------------- /lib/js/rules/max-calls-in-template.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check function call times in templates. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = { 12 | meta: { 13 | docs: { 14 | description: 'enforce a maximum function call times in template', 15 | category: 'ECMAScript 6', 16 | recommended: true 17 | }, 18 | 19 | schema: [ 20 | { 21 | type: 'integer', 22 | minimum: 1 23 | } 24 | ] 25 | }, 26 | 27 | create: function (context) { 28 | var max = (context.options[0] | 0) || 1; 29 | 30 | /** 31 | * Check destructure depth 32 | * 33 | * @param {ASTNode} node The ObjectPattern or ArrayPattern node. 34 | */ 35 | function validate(node) { 36 | if (!node.expressions.length) { 37 | return; 38 | } 39 | 40 | var times = node.expressions.map(function count(expression) { 41 | if (expression.type === 'CallExpression') { 42 | var args = expression.arguments; 43 | return 1 + (args.length ? Math.max.apply(Math, args.map(count)) : 0); 44 | } 45 | 46 | return 0; 47 | }); 48 | 49 | times = Math.max.apply(Math, times); 50 | 51 | if (times > max) { 52 | context.report( 53 | node, 54 | 'Too many nest function calls ({{times}}). Maximum allowed is {{max}}.', 55 | {times: times, max: max} 56 | ); 57 | } 58 | } 59 | 60 | return { 61 | TemplateLiteral: validate 62 | }; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /lib/js/rules/max-destructure-depth.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check destructure depth. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = { 12 | meta: { 13 | docs: { 14 | description: 'enforce a maximum depth that destructure can be nested', 15 | category: 'ECMAScript 6', 16 | recommended: true 17 | }, 18 | 19 | schema: [ 20 | { 21 | type: 'integer', 22 | minimum: 0 23 | } 24 | ] 25 | }, 26 | 27 | create: function (context) { 28 | var max = (context.options[0] | 0) || 2; 29 | 30 | function isDestructure(type) { 31 | return type === 'ObjectPattern' || type === 'ArrayPattern'; 32 | } 33 | 34 | /** 35 | * Check destructure depth 36 | * 37 | * @param {ASTNode} node The ObjectPattern or ArrayPattern node. 38 | */ 39 | function validate(node) { 40 | var depth = 0; 41 | 42 | for (var start = node, type; type = start.type; start = start.parent) { 43 | if ( 44 | type === 'VariableDeclarator' 45 | || type === 'FunctionDeclaration' 46 | || type === 'FunctionExpression' 47 | || type === 'ArrowFunctionExpression' 48 | || type === 'Program' 49 | ) { 50 | break; 51 | } 52 | 53 | if (isDestructure(type)) { 54 | depth++; 55 | } 56 | } 57 | 58 | if (depth > max) { 59 | context.report( 60 | node, 61 | 'Too many nested destructure ({{depth}}). Maximum allowed is {{max}}.', 62 | {depth: depth, max: max} 63 | ); 64 | } 65 | } 66 | 67 | return { 68 | ObjectPattern: validate, 69 | ArrayPattern: validate 70 | }; 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /lib/js/rules/max-statements.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file A rule to set the maximum number of statements in a function. 3 | * @author Ian Christian Myers 4 | * @author chris 5 | */ 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = function (context) { 12 | 13 | 'use strict'; 14 | 15 | //-------------------------------------------------------------------------- 16 | // Helpers 17 | //-------------------------------------------------------------------------- 18 | 19 | var functionStack = []; 20 | var maxStatements = context.options[0] || 10; 21 | var ignore = context.options[1] || {}; 22 | 23 | function isAMD(node) { 24 | var parent = node.parent; 25 | var params = node.params; 26 | var args = parent.arguments && parent.arguments.slice(-2)[0]; 27 | 28 | return ignore.AMD 29 | && node.type === 'FunctionExpression' 30 | && (params[0] && params[0].name === 'require' 31 | || args && args.elements && args.elements.length === params.length) 32 | && parent.type === 'CallExpression' 33 | && parent.callee.name === 'define'; 34 | } 35 | 36 | function isIIFE(node) { 37 | var parent = node.parent; 38 | 39 | return ignore.IIFE 40 | && parent.type === 'CallExpression' 41 | && parent.parent.parent.parent == null; 42 | } 43 | 44 | function startFunction() { 45 | functionStack.push(0); 46 | } 47 | 48 | function endFunction(node) { 49 | var count = functionStack.pop(); 50 | if (count > maxStatements && !(isIIFE(node) || isAMD(node))) { 51 | context.report( 52 | node, 53 | 'This function has too many statements ({{count}}). Maximum allowed is {{max}}.', 54 | {count: count, max: maxStatements} 55 | ); 56 | } 57 | } 58 | 59 | function countStatements(node) { 60 | functionStack[functionStack.length - 1] += node.body.length; 61 | } 62 | 63 | //-------------------------------------------------------------------------- 64 | // Public API 65 | //-------------------------------------------------------------------------- 66 | 67 | return { 68 | 'FunctionDeclaration': startFunction, 69 | 'FunctionExpression': startFunction, 70 | 'ArrowFunctionExpression': startFunction, 71 | 72 | 'BlockStatement': countStatements, 73 | 74 | 'FunctionDeclaration:exit': endFunction, 75 | 'FunctionExpression:exit': endFunction, 76 | 'ArrowFunctionExpression:exit': endFunction 77 | }; 78 | 79 | }; 80 | 81 | module.exports.schema = [ 82 | { 83 | type: 'integer', 84 | minimum: 0 85 | }, 86 | { 87 | type: 'object', 88 | properties: { 89 | AMD: { 90 | type: 'boolean' 91 | }, 92 | IIFE: { 93 | type: 'boolean' 94 | } 95 | }, 96 | additionalProperties: true 97 | } 98 | ]; 99 | 100 | -------------------------------------------------------------------------------- /lib/js/rules/min-vars-per-destructure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check variables declared during destructuring. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = { 12 | meta: { 13 | docs: { 14 | description: 'enforce a minimum variables declared during destructuring', 15 | category: 'ECMAScript 6', 16 | recommended: true 17 | }, 18 | 19 | schema: [ 20 | { 21 | type: 'integer', 22 | minimum: 0 23 | } 24 | ] 25 | }, 26 | 27 | create: function (context) { 28 | var min = (context.options[0] | 0) || 2; 29 | 30 | function isDestructure(node) { 31 | return node.type === 'ObjectPattern' || node.type === 'ArrayPattern'; 32 | } 33 | 34 | /** 35 | * Check destructure variables count 36 | * 37 | * @param {ASTNode} node The VariableDeclarator node. 38 | */ 39 | function validate(node) { 40 | var id = node.id; 41 | if (!id || !isDestructure(id)) { 42 | return; 43 | } 44 | 45 | var count = (id.properties || id.elements).reduce(function count(total, property) { 46 | if (!property) { 47 | return total; 48 | } 49 | 50 | var node = (property.value || property); 51 | 52 | switch (node.type) { 53 | case 'AssignmentPattern': 54 | total++; 55 | 56 | case 'RestElement': 57 | case 'Identifier': 58 | case 'RestProperty': 59 | case 'ExperimentalRestProperty': 60 | total++; 61 | break; 62 | 63 | case 'ArrayPattern': 64 | total += node.elements.reduce(count, 0); 65 | break; 66 | 67 | case 'ObjectPattern': 68 | total += node.properties.reduce(count, 0); 69 | break; 70 | } 71 | 72 | return total; 73 | }, 0); 74 | 75 | 76 | if (count < min) { 77 | context.report( 78 | node, 79 | 'Not enough variables declared during destructuring ({{count}}). Minimum allowed is {{min}}.', 80 | {count: count, min: min} 81 | ); 82 | } 83 | } 84 | 85 | return { 86 | VariableDeclarator: validate 87 | }; 88 | } 89 | }; 90 | -------------------------------------------------------------------------------- /lib/js/rules/no-anonymous-before-rest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check if exist anonymous elements before rest element. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = { 12 | meta: { 13 | docs: { 14 | description: 'enforce to named elements before rest element in declarations', 15 | category: 'ECMAScript 6', 16 | recommended: true 17 | }, 18 | 19 | schema: [] 20 | }, 21 | 22 | create: function (context) { 23 | 24 | return { 25 | RestElement: function (node) { 26 | var anonymous = 0; 27 | 28 | node.parent.elements && node.parent.elements.reduceRight(function (isBefore, element) { 29 | if (!isBefore && node === element) { 30 | isBefore = true; 31 | } 32 | 33 | if (!element && isBefore) { 34 | anonymous++; 35 | } 36 | 37 | return isBefore; 38 | }, false); 39 | 40 | if (anonymous) { 41 | context.report( 42 | node.parent, 43 | 'No anonymous elements (found {{num}}) before rest element.', 44 | {num: anonymous} 45 | ); 46 | } 47 | } 48 | }; 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /lib/js/rules/no-eval.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 检查是否有使用 eval,直接或间接 3 | * @author chris 4 | */ 5 | 6 | 7 | module.exports = function (context) { 8 | 'use strict'; 9 | 10 | function check(node) { 11 | var callee = node.callee; 12 | if (callee.name === 'eval' 13 | || callee.type === 'MemberExpression' 14 | && callee.object.name === 'window' 15 | && callee.property.name === 'eval' 16 | ) { 17 | context.report(node, 'Avoid to use eval or window.eval.'); 18 | } 19 | } 20 | 21 | return { 22 | CallExpression: check 23 | }; 24 | 25 | }; 26 | 27 | module.exports.schema = []; 28 | -------------------------------------------------------------------------------- /lib/js/rules/no-extra-destructure.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check if exists unnecessary destructure. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = { 12 | meta: { 13 | docs: { 14 | description: 'Disallow to use unnecessary destructure', 15 | category: 'ECMAScript 6', 16 | recommended: true 17 | }, 18 | 19 | schema: [] 20 | }, 21 | 22 | create: function (context) { 23 | 24 | function report(node) { 25 | context.report(node, 'Unnecessary destructure.'); 26 | } 27 | 28 | function isLiteral(node) { 29 | return node && node.type === 'Literal'; 30 | } 31 | 32 | function hasSameStructure(left, right) { 33 | return left === 'ObjectPattern' && right === 'ObjectExpression' 34 | || left === 'ArrayPattern' && right === 'ArrayExpression'; 35 | } 36 | 37 | function isSameIdentifier(left, right) { 38 | return left.type === 'Identifier' 39 | && right.type === 'Identifier' 40 | && left.name === right.name; 41 | } 42 | 43 | function validate(left, right) { 44 | if (!left || !right || !hasSameStructure(left.type, right.type)) { 45 | return; 46 | } 47 | 48 | if (left.elements) { 49 | left.elements.forEach(function (el, i) { 50 | var rightElement = right.elements[i]; 51 | if (!el || !rightElement) { 52 | return; 53 | } 54 | 55 | if (isLiteral(rightElement) || isSameIdentifier(el, rightElement)) { 56 | report(el); 57 | } 58 | else { 59 | validate(el, rightElement); 60 | } 61 | }); 62 | } 63 | else { 64 | var map = right.properties.reduce(function (map, property) { 65 | if (!property.key) { 66 | return map; 67 | } 68 | 69 | if (isLiteral(property.key) || isLiteral(property.value)) { 70 | report(property); 71 | } 72 | else { 73 | map[property.key.name] = true; 74 | } 75 | 76 | return map; 77 | }, {}); 78 | 79 | left.properties.forEach(function (property, i) { 80 | if (!property.key) { 81 | return; 82 | } 83 | 84 | if (property.key.type === 'Identifier' && map[property.key.name]) { 85 | report(property); 86 | } 87 | else { 88 | validate(property, right.properties[i]); 89 | } 90 | }); 91 | } 92 | } 93 | 94 | return { 95 | VariableDeclarator: function (node) { 96 | validate(node.id, node.init); 97 | }, 98 | 99 | AssignmentExpression: function (node) { 100 | validate(node.left, node.right); 101 | } 102 | }; 103 | } 104 | }; 105 | -------------------------------------------------------------------------------- /lib/js/rules/no-forin-array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 禁止使用 for-in 遍历数组的规则. 3 | * @author chris 4 | */ 5 | 6 | var util = require('../../util'); 7 | 8 | module.exports = function (context) { 9 | 10 | 'use strict'; 11 | 12 | var isArray = util.isArrayNode(context); 13 | 14 | 15 | return { 16 | 17 | ForInStatement: function (node) { 18 | 19 | var right = node.right; 20 | if (right.type === 'Identifier') { 21 | var filter = function (item) { 22 | return item.name === right.name; 23 | }; 24 | 25 | var variable = context.getScope().variables.filter(filter)[0]; 26 | 27 | if (!variable) { 28 | return; 29 | } 30 | 31 | 32 | var parent = variable.identifiers[0].parent; 33 | switch (parent.type) { 34 | case 'VariableDeclarator': 35 | if (parent.init) { 36 | right = parent.init; 37 | } 38 | break; 39 | 40 | case 'RestElement': 41 | right = {type: 'ArrayExpression'}; 42 | break; 43 | 44 | case 'FunctionExpression': 45 | case 'FunctionDeclaration': 46 | case 'ArrowFunctionExpression': 47 | var doc = context.getSourceCode().getJSDocComment(parent); 48 | if (doc && ~doc.value.indexOf('@param {Array} ' + right.name)) { 49 | right = {type: 'ArrayExpression'}; 50 | } 51 | break; 52 | 53 | default: 54 | break; 55 | } 56 | 57 | } 58 | 59 | if (isArray(right)) { 60 | context.report(node, 'Don\'t traverse Array with for-in.'); 61 | } 62 | } 63 | }; 64 | 65 | }; 66 | 67 | module.exports.schema = []; 68 | 69 | -------------------------------------------------------------------------------- /lib/js/rules/no-require.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 检查 ES6 模块中是否使用 require 3 | * @author chris 4 | * ielgnaw(wuji0223@gmail.com) 5 | */ 6 | 7 | 'use strict'; 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | module.exports = { 14 | meta: { 15 | docs: { 16 | description: 'Disallow to use `require` in ES6 file.', 17 | category: 'ECMAScript 6', 18 | recommended: true 19 | }, 20 | 21 | schema: [] 22 | }, 23 | 24 | create: function (context) { 25 | 26 | var taggedScopes = []; 27 | 28 | function clear() { 29 | taggedScopes.length = 0; 30 | } 31 | 32 | function hasRequireInScopes(node) { 33 | var scope = context.getScope(); 34 | var variables = scope.variables; 35 | var variableScope = scope.variableScope; 36 | 37 | var found; 38 | 39 | for (var i = 0, len = taggedScopes.length; i < len; i++) { 40 | var tag = taggedScopes[i]; 41 | if (tag.scope === variableScope) { 42 | found = tag; 43 | break; 44 | } 45 | } 46 | 47 | if (found) { 48 | return found.require; 49 | } 50 | 51 | while (scope.type !== 'global' && scope.type !== 'module') { 52 | scope = scope.upper; 53 | variables = variables.concat(scope.variables); 54 | } 55 | 56 | found = variables.some(function (variable) { 57 | if (variable.name === 'require') { 58 | taggedScopes.push({ 59 | scope: variable.scope, 60 | require: true 61 | }); 62 | 63 | return true; 64 | } 65 | }); 66 | 67 | return found; 68 | } 69 | 70 | function validate(node) { 71 | if ( 72 | node.callee.name !== 'require' 73 | || node.callee.name === 'require' && hasRequireInScopes(node) 74 | ) { 75 | return; 76 | } 77 | 78 | context.report(node, 'Disallow to use `require` in ES6 file.'); 79 | } 80 | 81 | return { 82 | 'Program:exit': clear, 83 | 'CallExpression': validate 84 | }; 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /lib/js/rules/one-var-per-line.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to enforce one variable per line when destructuring. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = { 12 | meta: { 13 | docs: { 14 | description: 'enforce one variable per line when destructuring', 15 | category: 'ECMAScript 6', 16 | recommended: true 17 | }, 18 | 19 | schema: [] 20 | }, 21 | 22 | create: function (context) { 23 | 24 | /** 25 | * Check ecah variable in destructure node 26 | * 27 | * @param {ASTNode} node ObjectPattern/ArrayPattern/ImportDeclaration node. 28 | */ 29 | function validate(node) { 30 | if (node.loc.start.line === node.loc.end.line) { 31 | return; 32 | } 33 | 34 | (node.properties || node.elements || node.specifiers).reduce(function (lastLine, item) { 35 | if (!item) { 36 | return lastLine + 1; 37 | } 38 | 39 | if (lastLine + 1 > item.loc.start.line) { 40 | context.report(item, 'One Variable per line when destructuring.'); 41 | } 42 | 43 | return item.loc.end.line; 44 | }, node.loc.start.line); 45 | } 46 | 47 | return { 48 | ObjectPattern: validate, 49 | ArrayPattern: validate, 50 | 51 | ImportDeclaration: function (node) { 52 | if (!node.specifiers[0]) { 53 | return; 54 | } 55 | 56 | var target = node.specifiers[0].type === 'ImportDefaultSpecifier' 57 | ? {loc: node.loc, specifiers: node.specifiers.slice(1)} 58 | : node; 59 | 60 | validate(target); 61 | } 62 | }; 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /lib/js/rules/prefer-spread-element.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to validate using of spread element. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | var util = require('../../util'); 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | module.exports = { 14 | meta: { 15 | docs: { 16 | description: 'validate using of spead element', 17 | category: 'ECMAScript 6', 18 | recommended: false 19 | }, 20 | 21 | schema: [{ 22 | type: 'object', 23 | properties: { 24 | copy: { 25 | type: 'boolean' 26 | } 27 | }, 28 | additionalProperties: false 29 | }] 30 | }, 31 | 32 | create: function (context) { 33 | var options = context.options[0] || {}; 34 | var useCopy = options.copy === true; 35 | 36 | 37 | function validateCopy(node) { 38 | if (node.parent.type === 'ArrayExpression' && node.parent.elements.length === 1) { 39 | context.report(node, 'Expected to use `Array.from` to copy Array.'); 40 | } 41 | } 42 | 43 | var isArray = util.isArrayNode(context); 44 | 45 | function validateConcat(node) { 46 | if (node.callee.type !== 'MemberExpression' 47 | || node.callee.property.name !== 'concat' 48 | || node.callee.object.name === 'Buffer' 49 | ) { 50 | return; 51 | } 52 | 53 | if (node.arguments.some(isArray)) { 54 | context.report(node, 'Expected to use SpreadElement to concat Array.'); 55 | } 56 | } 57 | 58 | return useCopy 59 | ? {CallExpression: validateConcat} 60 | : {CallExpression: validateConcat, SpreadElement: validateCopy}; 61 | } 62 | }; 63 | -------------------------------------------------------------------------------- /lib/js/rules/properties-quote.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to validate object properties 3 | * @author chris 4 | */ 5 | 6 | //------------------------------------------------------------------------------ 7 | // Rule Definition 8 | //------------------------------------------------------------------------------ 9 | 10 | var reserved = require('reserved-words'); 11 | 12 | module.exports = function (context) { 13 | 'use strict'; 14 | 15 | var NO_QUOTES = 'Expected key `{{name}}` but `\'{{name}}\'` found.baidu094'; 16 | var NEED_QUOTES = 'Expected key `\'{{name}}\'` but `{{name}}` found.baidu095'; 17 | 18 | var options = context.options[0] || {}; 19 | var ignore = options.ignore || []; 20 | var esVerioin = options.esVersion || 5; 21 | 22 | function reducer(map, key) { 23 | map[key] = true; 24 | return map; 25 | } 26 | 27 | var ignores = (Array.isArray(ignore) ? ignore : [ignore]).reduce(reducer, Object.create(null)); 28 | 29 | /** 30 | * Check SpreadProperty Node 31 | * 32 | * @param {Property} property property node 33 | * @return {boolean} [return description] 34 | */ 35 | function isSpreadProperty(property) { 36 | return property.type === 'SpreadProperty' 37 | || property.type === 'SpreadElement' 38 | || property.type === 'ExperimentalSpreadProperty' 39 | || property._babelType === 'SpreadProperty'; 40 | } 41 | 42 | function getKey(property) { 43 | var key = property.key; 44 | 45 | return isSpreadProperty(property) 46 | ? property.argument.name 47 | : (key.name || (typeof key.value === 'number' ? key.raw : key.value)); 48 | } 49 | 50 | function requireQuotes(needs, key) { 51 | var reg = /(^\d+\D+|[^\$\w]|^0[0-7]+|^0x[a-zA-Z\d]+)/; 52 | 53 | if (reg.test(key) || reserved.check(key, esVerioin)) { 54 | needs.push(key); 55 | } 56 | } 57 | 58 | return { 59 | 60 | ObjectExpression: function (node) { 61 | 62 | var needs = []; 63 | var type = {literal: [], identifier: []}; 64 | 65 | node.properties.forEach(function (property) { 66 | var key = getKey(property); 67 | 68 | if (isSpreadProperty(property) 69 | || property.method 70 | || property.computed 71 | || property.shorthand 72 | || property.kind === 'set' 73 | || property.kind === 'get' 74 | || ignores[key] 75 | ) { 76 | return; 77 | } 78 | 79 | var keyType = property.key.type.toLowerCase(); 80 | if (keyType === 'literal' && typeof property.key.value === 'number') { 81 | keyType = 'identifier'; 82 | } 83 | 84 | type[keyType].push(property); 85 | requireQuotes(needs, key); 86 | }); 87 | 88 | var hadToQuote = !!needs.length; 89 | var message = hadToQuote ? NEED_QUOTES : NO_QUOTES; 90 | 91 | type[hadToQuote ? 'identifier' : 'literal'].forEach(function (property) { 92 | context.report( 93 | property, 94 | message, 95 | {name: getKey(property)} 96 | ); 97 | }); 98 | } 99 | }; 100 | 101 | }; 102 | 103 | module.exports.schema = [ 104 | { 105 | type: 'object', 106 | properties: { 107 | ignore: { 108 | oneOf: [ 109 | { 110 | type: 'string' 111 | }, 112 | { 113 | type: 'array', 114 | items: { 115 | type: 'string' 116 | } 117 | } 118 | ] 119 | }, 120 | esVersion: { 121 | 'enum': ['es3', 3, 'es5', 5, 'es2015', 'es6', 6, 'es7', 7, 'next', 'default'] 122 | } 123 | } 124 | } 125 | ]; 126 | -------------------------------------------------------------------------------- /lib/js/rules/shim-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to enforce shim Promise. 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var util = require('../../util'); 9 | 10 | //------------------------------------------------------------------------------ 11 | // Rule Definition 12 | //------------------------------------------------------------------------------ 13 | 14 | module.exports = { 15 | meta: { 16 | docs: { 17 | description: 'enforce shim Promise', 18 | category: 'ECMAScript 6', 19 | recommended: false 20 | }, 21 | 22 | schema: [{type: 'string'}] 23 | }, 24 | 25 | create: function (context) { 26 | 27 | var globalName = context.options[0] || ''; 28 | var PROMISE = 'Promise'; 29 | 30 | var nodeToBeChecked; 31 | var forceShim; 32 | 33 | function checkDefinition(node) { 34 | if (!forceShim && node.id && node.id.name === PROMISE) { 35 | var parent = node.parent; 36 | if (node.type === 'VariableDeclarator') { 37 | parent = parent.parent; 38 | } 39 | 40 | if (parent.type !== 'Program') { 41 | nodeToBeChecked = {node: node, scope: context.getScope().variableScope}; 42 | } 43 | } 44 | } 45 | 46 | function checkShim(node) { 47 | var left = node.left; 48 | 49 | if (left.type === 'Identifier' 50 | && left.name === PROMISE 51 | && !util.variablesInScope(context).some( 52 | function (variable) { 53 | return variable.name === PROMISE; 54 | } 55 | ) 56 | ) { 57 | forceShim = true; 58 | nodeToBeChecked = null; 59 | } 60 | 61 | if (left.type !== 'MemberExpression') { 62 | return; 63 | } 64 | 65 | var objectName = left.object.name; 66 | 67 | if ( 68 | !('value' in node.right && node.right.value == null) 69 | && left.property.name === PROMISE 70 | && ((globalName && globalName === objectName) 71 | || /^(?:window|global)$/.test(objectName)) 72 | ) { 73 | forceShim = true; 74 | nodeToBeChecked = null; 75 | } 76 | } 77 | 78 | function validate() { 79 | if (nodeToBeChecked) { 80 | context.report(nodeToBeChecked.node.id, 'Promise should be shimmed to global scope.'); 81 | } 82 | } 83 | 84 | return { 85 | 'ClassDeclaration': checkDefinition, 86 | 'VariableDeclarator': checkDefinition, 87 | 'FunctionDeclaration': checkDefinition, 88 | 'AssignmentExpression': checkShim, 89 | 'Program:exit': validate 90 | }; 91 | } 92 | }; 93 | -------------------------------------------------------------------------------- /lib/js/rules/use-async-require.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check async require. 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var util = require('../../util'); 9 | 10 | module.exports = { 11 | meta: { 12 | schema: [] 13 | }, 14 | 15 | create: function (context) { 16 | 17 | var isArray = util.isArrayNode(context); 18 | 19 | function validate(node) { 20 | if (node.callee.type !== 'Identifier' || node.callee.name !== 'require') { 21 | return; 22 | } 23 | 24 | var noRequire = context.getScope().through.some(function (variable) { 25 | return variable.identifier.name === 'require'; 26 | }); 27 | 28 | if (!noRequire) { 29 | return; 30 | } 31 | 32 | if (node.arguments.length !== 2 || !isArray(node.arguments[0])) { 33 | context.report(node.arguments[0], 'Global require should be called as async.'); 34 | } 35 | } 36 | 37 | 38 | return { 39 | CallExpression: validate 40 | }; 41 | } 42 | }; 43 | 44 | -------------------------------------------------------------------------------- /lib/js/rules/use-computed-property.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to check computed property of object expression definition. 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | var util = require('../../util'); 9 | 10 | //------------------------------------------------------------------------------ 11 | // Rule Definition 12 | //------------------------------------------------------------------------------ 13 | 14 | module.exports = { 15 | meta: { 16 | docs: { 17 | description: 'Check computed property of object expression definition.', 18 | category: 'ECMAScript 6', 19 | recommended: false 20 | }, 21 | 22 | schema: [] 23 | }, 24 | 25 | create: function (context) { 26 | 27 | var sourceCode = context.getSourceCode(); 28 | 29 | /** 30 | * Find variable and it's write reference 31 | * 32 | * @param {string} name variable name 33 | * @param {function(ASTNode):boolean} checkType type of the write expression node 34 | * @return {?Reference} 35 | */ 36 | function findVariableReference(name, checkType) { 37 | var variable; 38 | 39 | util.variablesInScope(context).some(function (item) { 40 | if (item.name === name) { 41 | variable = item; 42 | return true; 43 | } 44 | }); 45 | 46 | var result; 47 | 48 | variable && variable.references.some(function (item) { 49 | if (item.isWrite() && checkType(item.writeExpr)) { 50 | result = item; 51 | return true; 52 | } 53 | }); 54 | 55 | return result; 56 | } 57 | 58 | function isPureObject(name) { 59 | return name && findVariableReference(name, function (node) { 60 | switch (node.type) { 61 | case 'NewExpression': 62 | return node.callee.name === 'Object' 63 | && (!node.arguments.length || node.arguments[0].type === 'ObjectExpression'); 64 | 65 | case 'ObjectExpression': 66 | return true; 67 | 68 | case 'CallExpression': 69 | var callee = node.callee; 70 | return callee.type === 'MemberExpression' 71 | && callee.object.name === 'Object' 72 | && callee.property.name === 'create' 73 | && node.arguments[0] 74 | && node.arguments[0].value === null 75 | || callee.name === 'Object' 76 | && (!node.arguments.length || node.arguments[0].type === 'ObjectExpression'); 77 | } 78 | }); 79 | } 80 | 81 | 82 | function validate(node) { 83 | var left = node.left; 84 | if (left.type !== 'MemberExpression' || left.object.type === 'MemberExpression') { 85 | return; 86 | } 87 | 88 | var id = left.object.name; 89 | var ref = isPureObject(id); 90 | if (!ref || sourceCode.getText(node.right).match('\\b' + id + '\\b')) { 91 | return; 92 | } 93 | 94 | if (ref.from === context.getScope() 95 | && ref.identifier.parent.parent.parent.parent === node.parent.parent.parent 96 | ) { 97 | context.report( 98 | node, 99 | 'Expected to init `{{name}}` with {{adj}} property.', 100 | {name: id, adj: left.computed ? 'computed' : 'this'} 101 | ); 102 | } 103 | } 104 | 105 | return { 106 | AssignmentExpression: validate 107 | }; 108 | } 109 | }; 110 | -------------------------------------------------------------------------------- /lib/js/rules/use-for-of.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to enforce use for-of statement. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | var util = require('../../util'); 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | module.exports = { 14 | meta: { 15 | docs: { 16 | description: 'enforce use for-of statement', 17 | category: 'ECMAScript 6', 18 | recommended: false 19 | }, 20 | 21 | schema: [] 22 | }, 23 | 24 | create: function (context) { 25 | 26 | var isObject = util.isObjectNode(context); 27 | 28 | function validate(node) { 29 | context.report(node, 'Unexpected for-in, use for-of instead.'); 30 | 31 | if (isObject(node.right)) { 32 | context.report(node.right, 'Expected to use Object.keys.'); 33 | } 34 | } 35 | 36 | return { 37 | ForInStatement: validate 38 | }; 39 | } 40 | }; 41 | -------------------------------------------------------------------------------- /lib/js/rules/use-method-definition.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to flag use of arguments 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | module.exports = function (context) { 13 | 14 | return { 15 | 16 | Property: function (node) { 17 | 18 | // getters, setters and computed properties are ignored 19 | if (node.kind === 'get' 20 | || node.kind === 'set' 21 | || node.computed 22 | || node.shorthand || node.method 23 | ) { 24 | return; 25 | } 26 | 27 | var type = node.value.type; 28 | var scope = context.getScope().childScopes[0]; 29 | if (type === 'FunctionExpression' 30 | && !node.value.id 31 | || type === 'ArrowFunctionExpression' 32 | && !scope.thisFound 33 | && node.value.body.type === 'BlockStatement' 34 | && node.value.body.body.length > 1 35 | ) { 36 | context.report( 37 | node.value, 38 | 'Expected MethodDefinition but saw {{type}}.', {type: type} 39 | ); 40 | } 41 | 42 | } 43 | }; 44 | 45 | }; 46 | 47 | module.exports.schema = []; 48 | -------------------------------------------------------------------------------- /lib/js/rules/use-property-shorthand.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to flag use of arguments 3 | * @author chris 4 | */ 5 | 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | module.exports = function (context) { 13 | var options = context.options[0] || {}; 14 | 15 | /** 16 | * Check SpreadProperty Node 17 | * 18 | * @param {Property} property property node 19 | * @return {boolean} [return description] 20 | */ 21 | function isSpreadProperty(property) { 22 | return property.type === 'SpreadProperty' 23 | || property.type === 'SpreadElement' 24 | || property.type === 'ExperimentalSpreadProperty' 25 | || property._babelType === 'SpreadProperty'; 26 | } 27 | 28 | return { 29 | 30 | ObjectExpression: function (node) { 31 | var result = node.properties.reduce(function (result, property) { 32 | if (options.spread && isSpreadProperty(property) 33 | || options.method && property.method 34 | || options.computed && property.computed 35 | || options.set && property.kind === 'set' 36 | || options.get && property.kind === 'get' 37 | ) { 38 | result.ignored++; 39 | } 40 | else if (property.shorthand) { 41 | result.shorthand++; 42 | } 43 | else if (!isSpreadProperty(property) 44 | && property.key.type === 'Identifier' 45 | && property.value.type === 'Identifier' 46 | && property.key.name === property.value.name 47 | ) { 48 | result.invalid.push(property); 49 | } 50 | 51 | return result; 52 | }, {shorthand: 0, ignored: 0, invalid: []}); 53 | 54 | if (result.shorthand + result.ignored + result.invalid.length === node.properties.length) { 55 | result.invalid.forEach(function (property) { 56 | context.report(property, 'Expected shorthand for `{{name}}`.', {name: property.key.name}); 57 | }); 58 | } 59 | } 60 | }; 61 | 62 | }; 63 | 64 | module.exports.schema = [ 65 | { 66 | type: 'object', 67 | properties: { 68 | spread: { 69 | type: 'boolean' 70 | }, 71 | get: { 72 | type: 'boolean' 73 | }, 74 | set: { 75 | type: 'boolean' 76 | }, 77 | computed: { 78 | type: 'boolean' 79 | }, 80 | method: { 81 | type: 'boolean' 82 | } 83 | } 84 | } 85 | ]; 86 | -------------------------------------------------------------------------------- /lib/js/rules/use-standard-promise.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Rule to enforce use standard Promise APIs. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | /** 12 | * 标准 Promise API 13 | * 14 | * @namespace 15 | */ 16 | var apis = { 17 | 'all': true, 18 | 'catch': true, 19 | 'race': true, 20 | 'resolve': true, 21 | 'reject': true, 22 | 'then': true 23 | }; 24 | 25 | module.exports = { 26 | meta: { 27 | docs: { 28 | description: 'enforce use standard Promise APIs', 29 | category: 'ECMAScript 6', 30 | recommended: true 31 | }, 32 | 33 | schema: [{ 34 | type: 'object', 35 | additionalProperties: true 36 | }] 37 | }, 38 | 39 | create: function (context) { 40 | var additions = context.options[0] || {}; 41 | 42 | function validate(node) { 43 | var callee = node.callee; 44 | if (callee.type !== 'MemberExpression' || callee.object.type !== 'Identifier') { 45 | return; 46 | } 47 | 48 | var objectName = callee.object.name.toLowerCase(); 49 | // Identifier or Literal 50 | var propertyName = callee.property.name || callee.property.value; 51 | 52 | if ( 53 | objectName === 'q' && propertyName === 'defer' 54 | || (objectName === 'jquery' || objectName === '$') && propertyName === 'Deferred' 55 | || objectName === 'promise' && !(propertyName in apis) && !(propertyName in additions) 56 | ) { 57 | context.report(callee.property, 'Expected to use standard Promise APIs.'); 58 | } 59 | } 60 | 61 | return { 62 | CallExpression: validate, 63 | NewExpression: validate 64 | }; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /lib/js/rules/valid-amd-id.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 检查 AMD 模块 id 的合法性 3 | * @author chris 4 | */ 5 | 6 | // 模块 id 必须符合以下约束条件: 7 | // 1. 类型为 string,并且是由 `/` 分割的一系列 terms 来组成。例如:`this/is/a/module`。 8 | // 2. term 应该符合 [a-zA-Z0-9_-]+ 规则。 9 | // 3. 不应该有 .js 后缀。 10 | // 4. 跟文件的路径保持一致。 11 | 12 | module.exports = function (context) { 13 | 14 | 'use strict'; 15 | 16 | var ID_REG = /^([a-zA-Z0-9_-]+\/)*[a-zA-Z0-9_-]+$/; 17 | 18 | function check(node) { 19 | var args = node.arguments; 20 | var argsCount = args.length; 21 | 22 | // 不是 define 的调用、只有一个参数、两个参数并且第一个是数组直接量,都不需要检查 id 23 | if (node.callee.name !== 'define' 24 | || argsCount < 2 25 | || argsCount === 2 && args[0].type === 'ArrayExpression' 26 | ) { 27 | return; 28 | } 29 | 30 | var id = args[0].value; 31 | 32 | // 第一个参数不是字符串直接量,或者字符串不符合模块 id term 的规则 33 | if (args[0].type !== 'Literal' || !ID_REG.test(id)) { 34 | context.report(node, 'Unexpected id of AMD module.'); 35 | } 36 | 37 | } 38 | 39 | 40 | return { 41 | CallExpression: check 42 | }; 43 | 44 | }; 45 | 46 | module.exports.schema = []; 47 | -------------------------------------------------------------------------------- /lib/less/checker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file less checker 3 | * @author chris 4 | */ 5 | 6 | var lesslint = require('lesslint'); 7 | 8 | var util = require('../util'); 9 | var Checker = require('../checker'); 10 | 11 | var checker = new Checker({ 12 | name: 'lesslint', 13 | type: 'css', 14 | suffix: 'less' 15 | }); 16 | 17 | 18 | /** 19 | * 执行对 LESS 文件内容的检查 20 | * 21 | * @param {string} contents 文件内容 22 | * @param {string} path 文件路径 23 | * @param {Object} cliOptions 命令行中传过来的配置项 24 | * @return {Promise.} 返回带错误信息的 Promise 对象 25 | */ 26 | checker.check = function (contents, path, cliOptions) { 27 | 28 | var options = this.options; 29 | var name = options.name; 30 | var type = options.type; 31 | 32 | var config = util.getConfig(name, cliOptions.lookup && path); 33 | 34 | var errors = []; 35 | 36 | var success = function (result) { 37 | result[0] && result[0].messages.forEach(function (error) { 38 | errors.push( 39 | util.parseError( 40 | { 41 | type: type, 42 | checker: name, 43 | rule: error.ruleName 44 | }, 45 | error 46 | ) 47 | ); 48 | }); 49 | 50 | return errors; 51 | }; 52 | 53 | var failure = function (reasons) { 54 | errors.push( 55 | util.parseError( 56 | { 57 | type: type, 58 | checker: name, 59 | code: '999' 60 | }, 61 | reasons[0] && reasons[0].messages[0] 62 | ) 63 | ); 64 | 65 | return errors; 66 | }; 67 | 68 | config['max-error'] = cliOptions.maxerr; 69 | return lesslint 70 | .checkString(contents, path, config) 71 | .then(success, failure); 72 | }; 73 | 74 | module.exports = checker; 75 | -------------------------------------------------------------------------------- /lib/less/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file CSS 检查规则配置模块 3 | * @author chris 4 | */ 5 | 6 | var util = require('../util'); 7 | 8 | // 读取当前目录下的 json 文件,以文件名为 key 对外 export 9 | // json 配置文件中可以使用 javascript 注释 10 | util.readConfigs(__dirname, exports); 11 | -------------------------------------------------------------------------------- /lib/less/lesslint.yml: -------------------------------------------------------------------------------- 1 | # @import 语句引用的文件必须(MUST)写在一对引号内,.less 后缀不得(MUST NOT)省略(与引入 CSS 文件时的路径格式一致)。 2 | # 引号使用 '' 和 " 均可,但在同一项目内必须(MUST)统一。 3 | import: true 4 | 5 | # `{` : 选择器和 { 之间必须(MUST)保留一个空格。 6 | require-before-space: 7 | - "{" 8 | 9 | # `:` : 1. 属性名后的冒号(:)与属性值之间必须(MUST)保留一个空格,冒号前不得(MUST NOT)保留空格。 10 | # 2. 定义变量时冒号(:)与变量值之间必须(MUST)保留一个空格,冒号前不得(MUST NOT)保留空格。 11 | # `,` : 1. 在用逗号(,)分隔的列表(Less 函数参数列表、以 , 分隔的属性值等)中,逗号后必须(MUST)保留一个空格, 12 | # 逗号前不得(MUST NOT)保留空格。 13 | # 2. 在给 mixin 传递参数时,在参数分隔符(, / ;)后必须(MUST)保留一个空格 14 | require-after-space: 15 | - ":" 16 | - "," 17 | 18 | # + / - / * / / 四个运算符两侧必须(MUST)保留一个空格。 19 | require-around-space: 20 | - "+" 21 | - "-" 22 | - "*" 23 | - "/" 24 | 25 | # + / - 两侧的操作数必须(MUST)有相同的单位,如果其中一个是变量,另一个数值必须(MUST)书写单位。 26 | operate-unit: 27 | - "+" 28 | - "-" 29 | 30 | # Mixin 和后面的括号之间不得(MUST NOT)包含空格。 31 | disallow-mixin-name-space: true 32 | 33 | # `selector` : 当多个选择器共享一个声明块时,每个选择器声明必须(MUST)独占一行。 34 | require-newline: 35 | - "selector" 36 | 37 | # 对于处于 (0, 1) 范围内的数值,小数点前的 0 可以(MAY)省略,同一项目中必须(MUST)保持一致。 38 | leading-zero: true 39 | 40 | # 当属性值为 0 时,必须(MUST)省略可省的单位(长度单位如 px、em,不包括时间、角度等如 s、deg)。 41 | zero-unit: true 42 | 43 | # 颜色定义必须(MUST)使用 #rrggbb 格式定义,并在可能时尽量(SHOULD)缩写为 #rgb 形式,且避免直接使用颜色名称与 rgb() 表达式。 44 | hex-color: true 45 | 46 | shorthand: true 47 | 48 | # 同一属性有不同私有前缀的,尽量(SHOULD)按前缀长度降序书写,标准形式必须(MUST)写在最后。 49 | # 且这一组属性以第一条的位置为准,尽量(SHOULD)按冒号的位置对齐。 50 | vendor-prefixes-sort: true 51 | 52 | # 必须(MUST)采用 4 个空格为一次缩进, 不得(MUST NOT)采用 TAB 作为缩进。 53 | block-indent: true 54 | 55 | # 变量命名必须采用 @foo-bar 形式,不得使用 @fooBar 形式 56 | variable-name: true 57 | 58 | # 使用继承时,如果在声明块内书写 :extend 语句,必须(MUST)写在开头: 59 | extend-must-firstline: true 60 | 61 | # 单行注释尽量使用 // 方式 62 | single-comment: true 63 | 64 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 日志输出 3 | * @author chris 4 | */ 5 | 6 | var chalk = require('chalk'); 7 | var util = require('util'); 8 | 9 | 10 | var fns = [ 11 | {name: 'trace', color: chalk.grey, level: 0}, 12 | {name: 'debug', color: chalk.grey, level: 1}, 13 | {name: 'info', color: chalk.green, level: 2}, 14 | {name: 'warn', color: chalk.yellow, level: 3}, 15 | {name: 'error', color: chalk.red, level: 4}, 16 | {name: 'fatal', color: chalk.red, level: 5} 17 | ]; 18 | 19 | 20 | /** 21 | * 日志模块 22 | * 23 | * @param {boolean} color 是否使用颜色高亮输出 24 | * @return {Object} 包含 trace/debug/info/warn/error/fatal 等方法的 log 对象 25 | */ 26 | module.exports = function (color) { 27 | var log = {}; 28 | var fixWidth = require('./util').fixWidth; 29 | var name = require('../').leadName; 30 | 31 | fns.forEach(function (item) { 32 | 33 | /** 34 | * 不同类型的 log 方法 35 | * 36 | * @param {string} format 要输出的内容. 37 | * @param {...*} args 变长参数. 38 | */ 39 | log[item.name] = color 40 | ? function (format, args) { 41 | 42 | var msg = util.format.apply(null, arguments); 43 | if (msg) { 44 | console.log(name + ' ' + item.color(fixWidth(item.name.toUpperCase(), 5)) + ' ' + msg); 45 | } 46 | else { 47 | console.log(); 48 | } 49 | } 50 | : function (format, args) { 51 | 52 | var msg = util.format.apply(null, arguments); 53 | if (msg) { 54 | console.log(name + ' [' + fixWidth(item.name.toUpperCase(), 5) + '] ' + msg); 55 | } 56 | else { 57 | console.log(); 58 | } 59 | }; 60 | }); 61 | 62 | return log; 63 | }; 64 | -------------------------------------------------------------------------------- /lib/reporter/baidu/csshint-map.yml: -------------------------------------------------------------------------------- 1 | no-bom: "001" 2 | block-indent: "002" 3 | require-before-space: "003" 4 | require-after-space: "004,005" 5 | max-length: "006" 6 | #require-after-linebreak: "007" 7 | require-newline: "008,011,044" 8 | require-around-space: "009" 9 | require-doublequotes: "010,024" 10 | always-semicolon: "012" 11 | disallow-overqualified-elements: "013" 12 | max-selector-nesting-level: "014" 13 | shorthand: "015,030" 14 | #group-properties: "017" 15 | disallow-important: "019" 16 | leading-zero: "025" 17 | disallow-quotes-in-url: "026" 18 | omit-protocol-in-url: "027" 19 | zero-unit: "028" 20 | hex-color: "029" 21 | disallow-named-color: "031" 22 | unifying-color-case-sensitive: "032" 23 | horizontal-vertical-position: "033" 24 | #font-family-space-in-quotes: "034" 25 | #font-family-sort: "035" 26 | unifying-font-family-case-sensitive: "036" 27 | min-font-size: "037" 28 | require-number: "039,040" 29 | require-transition-property: "041" 30 | vendor-prefixes-sort: "046" 31 | disallow-expression: "050" 32 | property-not-existed: "998" 33 | 34 | -------------------------------------------------------------------------------- /lib/reporter/baidu/htmlcs-map.yml: -------------------------------------------------------------------------------- 1 | asset-type: "027" 2 | bool-attribute-value: "019" 3 | button-name: "043" 4 | button-type: "042" 5 | charset: "024" 6 | css-in-head: "029" 7 | doctype: "021" 8 | html-lang: "023" 9 | ie-edge: "022" 10 | img-alt: "038" 11 | img-src: "036" 12 | img-title: "037" 13 | img-width-height: "039" 14 | indent-char: "001" 15 | lowercase-class-with-hyphen: "003" 16 | lowercase-id-with-hyphen: "006" 17 | nest: "013" 18 | rel-stylesheet: "026" 19 | script-in-tail: "030" 20 | title-required: 21 | "024": "032" 22 | "025": "033" 23 | unique-id: "005" 24 | no-duplication-id-and-name: "009" 25 | viewport: "035" 26 | label-for-input: "041" 27 | no-meta-css: "004" 28 | no-hook-class: "008" 29 | max-len: "002" 30 | no-bom: "025" 31 | attr-lowercase: "017" 32 | attr-no-duplication: "997" 33 | attr-unsafe-chars: "998" 34 | attr-value-double-quotes: "018" 35 | id-class-ad-disabled: "998" 36 | spec-char-escape: "997" 37 | self-close: "011" 38 | style-disabled: "028" 39 | tag-pair: "012" 40 | tagname-lowercase: "010" 41 | 42 | -------------------------------------------------------------------------------- /lib/reporter/baidu/lesslint-map.yml: -------------------------------------------------------------------------------- 1 | import: "997" 2 | require-before-space: "003" 3 | require-after-space: "004,005" 4 | require-around-space: "009" 5 | operate-unit: "998" 6 | disallow-mixin-name-space: "997" 7 | require-newline: "008" 8 | leading-zero: "025" 9 | zero-unit: "028" 10 | hex-color: "030" 11 | shorthand: "029" 12 | vendor-prefixes-sort: "046" 13 | block-indent: "002" 14 | variable-name: "997" 15 | extend-must-firstline: "997" 16 | single-comment: "997" 17 | 18 | -------------------------------------------------------------------------------- /lib/reporter/filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 校验错误信息输出前过滤 3 | * @author chris 4 | */ 5 | 6 | /** 7 | * 错误过滤模块 8 | * 9 | * @namespace 10 | */ 11 | var filter = { 12 | 13 | /** 14 | * 什么也不干 15 | * 16 | * @param {Object[]} errors 各 checker 检查出的错误信息 17 | * @return {Object[]} 18 | */ 19 | noop: function (errors) { 20 | return errors; 21 | }, 22 | 23 | /** 24 | * 组合过滤条件 25 | * 26 | * @param {Array.} fns 过滤函数数组 27 | * @return {Function} 组合得到的过滤操作函数 28 | */ 29 | compose: function (fns) { 30 | return function (errors) { 31 | return errors.filter(function (error) { 32 | return fns.every(function (fn) { 33 | return fn(error); 34 | }); 35 | }); 36 | }; 37 | }, 38 | 39 | /** 40 | * 行数过滤函数生成 41 | * 42 | * @param {string} expression 过滤行数的表达式 43 | * @return {Function} 能根据表达式过滤代码行错误的函数 44 | */ 45 | lines: function (expression) { 46 | var tokens = (expression + '').replace(/[^\(\)\[\],\d<>=]/g, '').split(','); 47 | 48 | var open = false; 49 | var exp = tokens.reduce(function (exp, token) { 50 | var op = token.replace(/\d+/, '')[0]; 51 | var line = token.replace(/\D+/, ''); 52 | 53 | if (op) { 54 | switch (op) { 55 | case '(': 56 | op = '>'; 57 | open = true; 58 | break; 59 | case '[': 60 | op = '>='; 61 | open = true; 62 | break; 63 | case '<': 64 | case '>': 65 | open = false; 66 | break; 67 | case ')': 68 | op = '<'; 69 | open = false; 70 | break; 71 | case ']': 72 | op = '<='; 73 | open = false; 74 | break; 75 | } 76 | 77 | exp += 'l' + op + line + (open ? '&&' : '||'); 78 | } 79 | else { 80 | exp += 'l===' + line + '||'; 81 | } 82 | 83 | return exp; 84 | 85 | }, ''); 86 | 87 | exp = exp.replace(/[\&\|]+$/, ''); 88 | 89 | return new Function('error', 'var l=error.line;if(typeof l===void 0)return true;return ' + exp); 90 | }, 91 | 92 | /** 93 | * 检查规则名过滤函数生成 94 | * 95 | * @param {string} expression 以逗号分隔的规则名组合 96 | * @return {Function} 能过滤指定规则名以外错误的函数 97 | */ 98 | rules: function (expression) { 99 | var rules = String(expression).toLowerCase().split(/,\s*/); 100 | 101 | return function (error) { 102 | var ruleName = (error.rule || '').toLowerCase(); 103 | 104 | return rules.some(function (rule) { 105 | return !rule || rule === ruleName; 106 | }); 107 | }; 108 | }, 109 | 110 | /** 111 | * 生成根据错误的 serverity 过滤的函数 112 | * 113 | * @param {(string | number)} expression 指定的错误等级 114 | * @return {Function} 能过滤指定错误等级之外错误的函数 115 | */ 116 | level: function (expression) { 117 | var level = (expression | 0) % 2; 118 | 119 | return function (error) { 120 | var severity = (error.severity | 0) % 2; 121 | return severity === level; 122 | }; 123 | }, 124 | 125 | /** 126 | * 获取过滤函数 127 | * 128 | * @param {Object} options cli 参数 129 | * @return {Function} 过滤函数 130 | */ 131 | get: function (options) { 132 | 133 | var fns = 'lines, rules, level'.split(/,\s*/).map(function (key) { 134 | return options[key] && filter[key](options[key]); 135 | }).filter(Boolean); 136 | 137 | if (fns.length) { 138 | return filter.compose(fns); 139 | } 140 | 141 | return filter.noop; 142 | } 143 | }; 144 | 145 | module.exports = filter; 146 | -------------------------------------------------------------------------------- /lib/version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 模块版本报告 3 | * @author chris 4 | */ 5 | 6 | /** 7 | * 获取当前安装模块的版本 8 | * 9 | * @param {string[]} modules 模块名称 10 | * @return {Object.} 模块名称及对应版本信息 11 | */ 12 | module.exports = function (modules) { 13 | return (modules || []).reduce(function (versions, name) { 14 | try { 15 | var pkg = require(name + '/package.json'); 16 | versions[pkg.name] = pkg.version; 17 | } 18 | catch (e) { 19 | versions[name] = 'N/A'; 20 | } 21 | 22 | return versions; 23 | }, {}); 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fecs", 3 | "version": "1.7.0-beta.1", 4 | "description": "Front End Code Style Suite", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "node ./bin/fecs --type=js --rule", 8 | "coverage": "istanbul cover ./node_modules/jasmine-node/bin/jasmine-node -x \"{cli,lib/css/rules}/*.js\" --captureExceptions test/", 9 | "test": "npm run lint && npm run coverage", 10 | "coveralls": "cat ./coverage/lcov.info | coveralls", 11 | "postinstall": "node scripts/install.js --allow-root" 12 | }, 13 | "bin": { 14 | "fecs": "./bin/fecs" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/ecomfe/fecs" 19 | }, 20 | "keywords": [ 21 | "fecs", 22 | "jscs", 23 | "jshint", 24 | "jslint", 25 | "eslint", 26 | "csslint", 27 | "csshint", 28 | "htmllint", 29 | "htmlhint", 30 | "jsbeautify", 31 | "cssbeautify", 32 | "htmlbeautify" 33 | ], 34 | "author": "chris ", 35 | "license": "MIT", 36 | "preferGlobal": true, 37 | "engines": { 38 | "node": ">=5" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/ecomfe/fecs/issues" 42 | }, 43 | "homepage": "https://github.com/ecomfe/fecs", 44 | "dependencies": { 45 | "babel-eslint": "^10.0.1", 46 | "chalk": "^2.1.0", 47 | "csscomb": "^4.2.0", 48 | "csshint": "stable", 49 | "doctrine2": "latest", 50 | "esformatter": "^0.10.0", 51 | "esformatter-braces": "^1.2.1", 52 | "esformatter-dot-notation": "^1.3.1", 53 | "esformatter-fecs": "latest", 54 | "esformatter-limit-linebreaks": "0.0.3", 55 | "esformatter-parseint": "^1.0.3", 56 | "esformatter-quotes": "^1.1.0", 57 | "esformatter-remove-trailing-commas": "^1.0.1", 58 | "esformatter-semicolons": "^1.1.2", 59 | "esformatter-spaced-lined-comment": "^2.0.1", 60 | "eslint": "^5.9.0", 61 | "eslint-plugin-babel": "^5.2.1", 62 | "eslint-plugin-html": "^5.0.0", 63 | "eslint-plugin-import": "^2.14.0", 64 | "eslint-plugin-react": "^7.4.0", 65 | "htmlcs": "stable", 66 | "lesslint": "stable", 67 | "loophole": "^1.1.0", 68 | "manis": "^0.3.4", 69 | "map-stream": "^0.1.0", 70 | "minimatch": "^3.0.2", 71 | "minimist": "^1.2.0", 72 | "msee": "^0.3.3", 73 | "reserved-words": "^0.1.2", 74 | "vinyl": "^2.2.0", 75 | "vinyl-fs": "^3.0.3" 76 | }, 77 | "devDependencies": { 78 | "coveralls": "^3.0.2", 79 | "istanbul": "^0.4.5", 80 | "jasmine-node": "^2.0.0", 81 | "mock-fs": "^4.7.0", 82 | "mock-require": "^3.0.2" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /scripts/install.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file hook esnext.checkNext in eslint.js 3 | * @author chris 4 | */ 5 | 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var eslintPath = require.resolve('eslint/lib/linter.js'); 9 | 10 | var eslintBackup = eslintPath + '.bak'; 11 | 12 | if (fs.existsSync(eslintBackup)) { 13 | console.log('had been injected.'); 14 | } 15 | else { 16 | console.log('inject eslint.js...'); 17 | 18 | var code = fs.readFileSync(eslintPath, 'utf-8'); 19 | 20 | fs.renameSync(eslintPath, eslintBackup); 21 | 22 | var esnextPath = path.join(__dirname, '..', 'lib/js/esnext'); 23 | var relativePath = path.relative(path.dirname(eslintPath), esnextPath) 24 | // 修复 windows 上的路径是 \ 的问题 25 | .replace(/\\/g, '/'); 26 | var injectCode = 'require("' + relativePath + '").detect(sourceCode.ast, config, this.currentFilename);'; 27 | 28 | code = code.replace(/(\s*)(const sourceCode = lastSourceCodes\.get\(this\);)/, '$1$2$1' + injectCode); 29 | 30 | fs.writeFileSync(eslintPath, code, 'utf-8'); 31 | 32 | console.log('finish.'); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /test/.fecsrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslint": { 3 | "rules": { 4 | "fecs-valid-jsdoc": 0, 5 | "max-nested-callbacks": [1, 5], 6 | "no-console": 0 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/fixture/checker/a.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/checker/a.spec -------------------------------------------------------------------------------- /test/fixture/checker/b.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/checker/b.spec -------------------------------------------------------------------------------- /test/fixture/checker/bar.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/checker/bar.spec -------------------------------------------------------------------------------- /test/fixture/checker/baz.x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/checker/baz.x -------------------------------------------------------------------------------- /test/fixture/checker/c.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/checker/c.spec -------------------------------------------------------------------------------- /test/fixture/checker/foo.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/checker/foo.spec -------------------------------------------------------------------------------- /test/fixture/ignored/.fecsignore: -------------------------------------------------------------------------------- 1 | *.spec 2 | -------------------------------------------------------------------------------- /test/fixture/ignored/.unfecsignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !a.min.js 3 | -------------------------------------------------------------------------------- /test/fixture/ignored/a.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/a.min.js -------------------------------------------------------------------------------- /test/fixture/ignored/a.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/a.spec -------------------------------------------------------------------------------- /test/fixture/ignored/a.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/a.spec.js -------------------------------------------------------------------------------- /test/fixture/ignored/b.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/b.min.js -------------------------------------------------------------------------------- /test/fixture/ignored/b.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/b.spec -------------------------------------------------------------------------------- /test/fixture/ignored/b.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/b.spec.js -------------------------------------------------------------------------------- /test/fixture/ignored/c.min.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/c.min.js -------------------------------------------------------------------------------- /test/fixture/ignored/c.spec: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/c.spec -------------------------------------------------------------------------------- /test/fixture/ignored/c.spec.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ecomfe/fecs/6b01e8fc808e450de6fd2fc24cb9aacd58ba5ef6/test/fixture/ignored/c.spec.js -------------------------------------------------------------------------------- /test/fixture/util/.eslintrc: -------------------------------------------------------------------------------- 1 | {"foofoo": "bar", "plugins": "foo", "globals": {"foo": true, "bar": true}} 2 | -------------------------------------------------------------------------------- /test/fixture/util/bar: -------------------------------------------------------------------------------- 1 | bar 2 | -------------------------------------------------------------------------------- /test/fixture/util/foo: -------------------------------------------------------------------------------- 1 | foo 2 | -------------------------------------------------------------------------------- /test/fixture/util/package.json: -------------------------------------------------------------------------------- 1 | {"fecs":{"test": {"foo": "bar"}}} 2 | -------------------------------------------------------------------------------- /test/fixture/util/parseExtends/.eslintrc: -------------------------------------------------------------------------------- 1 | {"plugins": ["foo", "bar"]} 2 | -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file spec helper 3 | * @author chris 4 | */ 5 | 6 | var util = require('../lib/util'); 7 | 8 | /** 9 | * 快速构建的流 10 | * 11 | * @param {Function} transform 转换方法 12 | * @param {?Function=} flush 结束方法 13 | * @return {module:through2} through2 的转换流 14 | */ 15 | exports.pass = function (transform, flush) { 16 | return util.mapStream( 17 | function (file, cb) { 18 | transform(file); 19 | cb(null, file); 20 | }, 21 | flush 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /test/index.spec.js: -------------------------------------------------------------------------------- 1 | var fecs = require('../index'); 2 | 3 | describe('API', function () { 4 | 5 | it('getOptions', function () { 6 | var options = fecs.getOptions(); 7 | expect(options._).toEqual([]); 8 | }); 9 | 10 | it('leadName', function () { 11 | var name = fecs.leadName; 12 | var pkg = require('../package'); 13 | 14 | expect(name).toBe(pkg.name); 15 | 16 | name = 'foo'; 17 | fecs.leadName = name; 18 | expect(fecs.leadName).toBe(name); 19 | fecs.leadName = pkg.name; 20 | }); 21 | 22 | it('check', function () { 23 | expect(typeof fecs.check).toBe('function'); 24 | }); 25 | 26 | it('format', function () { 27 | expect(typeof fecs.format).toBe('function'); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/lib/checker.spec.js: -------------------------------------------------------------------------------- 1 | var fs = require('vinyl-fs'); 2 | var File = require('vinyl'); 3 | 4 | var helper = require('../helper'); 5 | 6 | var Checker = require('../../lib/checker'); 7 | var checker = new Checker({ 8 | name: 'bbd', 9 | type: 'spec', 10 | suffix: 'spec', 11 | ignore: 'foo.spec,bar.spec' 12 | }); 13 | 14 | 15 | describe('checker', function () { 16 | 17 | it('isValid', function () { 18 | var invalidFiles = [ 19 | new File({contents: Buffer.from(''), path: 'test/a.foo.spec'}), 20 | new File({contents: Buffer.from(''), path: 'test/b.bar.spec'}), 21 | new File({contents: Buffer.from(''), path: 'test/baz.x'}) 22 | ]; 23 | 24 | var hasValid = invalidFiles.some(function (file) { 25 | return checker.isValid(file); 26 | }); 27 | 28 | expect(hasValid).toBeFalsy(); 29 | 30 | var validFiles = [ 31 | new File({contents: Buffer.from(''), path: 'test/a.spec'}), 32 | new File({contents: Buffer.from(''), path: 'test/b.spec'}), 33 | new File({contents: Buffer.from(''), path: 'test/c.spec'}) 34 | ]; 35 | 36 | var hasInvalid = validFiles.some(function (file) { 37 | return !checker.isValid(file); 38 | }); 39 | 40 | expect(hasInvalid).toBeFalsy(); 41 | 42 | }); 43 | 44 | it('isValid with no ignore', function () { 45 | checker.options.ignore = ''; 46 | 47 | var validFiles = [ 48 | new File({contents: Buffer.from(''), path: 'test/a.foo.spec'}), 49 | new File({contents: Buffer.from(''), path: 'test/b.bar.spec'}), 50 | new File({contents: Buffer.from(''), path: 'test/a.spec'}), 51 | new File({contents: Buffer.from(''), path: 'test/b.spec'}), 52 | new File({contents: Buffer.from(''), path: 'test/c.spec'}) 53 | ]; 54 | 55 | var hasInvalid = validFiles.some(function (file) { 56 | return !checker.isValid(file); 57 | }); 58 | 59 | expect(hasInvalid).toBeFalsy(); 60 | }); 61 | 62 | it('check() will throw', function () { 63 | expect(checker.check).toThrow(); 64 | }); 65 | 66 | it('check with callback', function (done) { 67 | 68 | checker.check = function (contents, path, cliOptions, callback) { 69 | expect(typeof callback).toBe('function'); 70 | return callback([]); 71 | }; 72 | 73 | fs.src('test/fixture/checker/**') 74 | .pipe(checker.exec({})) 75 | .on('end', done); 76 | 77 | }); 78 | 79 | it('check with promise', function (done) { 80 | checker.check = function (contents, path, cliOptions) { 81 | return Promise.resolve([true]); 82 | }; 83 | 84 | fs.src('test/fixture/checker/**') 85 | .pipe(checker.exec({})) 86 | .pipe(helper.pass( 87 | function (file) { 88 | if (!file.errors) { 89 | return; 90 | } 91 | 92 | expect(file.errors[0]).toBeTruthy(); 93 | }, done) 94 | ); 95 | }); 96 | 97 | it('check sync', function (done) { 98 | 99 | checker.check = function (contents, path, cliOptions) { 100 | return [true]; 101 | }; 102 | 103 | fs.src('test/fixture/checker/**') 104 | .pipe(checker.exec({})) 105 | .pipe(helper.pass( 106 | function (file) { 107 | if (!file.errors) { 108 | return; 109 | } 110 | 111 | expect(file.errors[0]).toBeTruthy(); 112 | }, done) 113 | ); 114 | }); 115 | 116 | }); 117 | -------------------------------------------------------------------------------- /test/lib/cli.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('mock-fs'); 2 | var msee = require('msee'); 3 | 4 | var cli = require('../../lib/cli'); 5 | var pkg = require('../../package'); 6 | 7 | 8 | describe('cli', function () { 9 | 10 | afterEach(function () { 11 | mock.restore(); 12 | }); 13 | 14 | it('display version when pass --version or -v', function () { 15 | var log = console.log; 16 | console.log = jasmine.createSpy('log'); 17 | 18 | process.argv = ['node', 'fecs', '--version']; 19 | cli.parse(); 20 | 21 | expect(console.log).toHaveBeenCalled(); 22 | expect(console.log).toHaveBeenCalledWith('%s %s', pkg.name, pkg.version); 23 | 24 | process.argv = ['node', 'fecs', '-v']; 25 | cli.parse(); 26 | 27 | expect(console.log.calls.count()).toEqual(2); 28 | expect(console.log.calls.mostRecent().args).toEqual(['%s %s', pkg.name, pkg.version]); 29 | 30 | process.argv = ['node', 'fecs', '-v', 'eslint']; 31 | cli.parse(); 32 | 33 | var eslint = require('eslint/package.json'); 34 | expect(console.log.calls.count()).toEqual(4); 35 | expect(console.log.calls.mostRecent().args).toEqual([' %s@%s', eslint.name, eslint.version]); 36 | 37 | console.log = log; 38 | }); 39 | 40 | it('display help when pass --help or -h', function () { 41 | var log = console.log; 42 | console.log = jasmine.createSpy('log'); 43 | 44 | var map = { 45 | doc: { 46 | 'check.md': 'check', 47 | 'fecs.md': 'fecs' 48 | } 49 | }; 50 | mock(map); 51 | 52 | process.argv = ['node', 'fecs', 'check', '--help']; 53 | cli.parse(); 54 | 55 | expect(console.log).toHaveBeenCalled(); 56 | expect(console.log).toHaveBeenCalledWith(msee.parse(map.doc['check.md'])); 57 | 58 | process.argv = ['node', 'fecs', '--help']; 59 | cli.parse(); 60 | 61 | expect(console.log.calls.mostRecent().args).toEqual([msee.parse(map.doc['fecs.md'])]); 62 | 63 | 64 | process.argv = ['node', 'fecs', 'N/A', '--help']; 65 | cli.parse(); 66 | 67 | expect(console.log.calls.mostRecent().args[0].indexOf('Have no help for command named')).toEqual(0); 68 | 69 | console.log = log; 70 | }); 71 | 72 | 73 | it('default for check command', function () { 74 | var check = require('../../cli/check'); 75 | var run = check.run; 76 | 77 | check.run = jasmine.createSpy('check-run'); 78 | 79 | process.argv = ['node', 'fecs']; 80 | cli.parse(); 81 | 82 | expect(check.run).toHaveBeenCalled(); 83 | 84 | process.argv = ['node', 'fecs', 'check']; 85 | cli.parse(); 86 | 87 | expect(check.run.calls.count()).toBe(2); 88 | 89 | check.run = run; 90 | }); 91 | 92 | it('check silent', function () { 93 | var check = require('../../cli/check'); 94 | var run = check.run; 95 | var log = console.log; 96 | 97 | check.run = jasmine.createSpy('check-run'); 98 | var unuseLog = console.log = jasmine.createSpy('unuse-log'); 99 | 100 | process.argv = ['node', 'fecs', '--silent']; 101 | cli.parse(); 102 | console.log('hidden'); 103 | 104 | expect(check.run).toHaveBeenCalled(); 105 | expect(unuseLog).not.toHaveBeenCalled(); 106 | 107 | check.run = run; 108 | console.log = log; 109 | }); 110 | 111 | it('default options', function () { 112 | 113 | var options = cli.getOptions([]); 114 | 115 | expect(options).toEqual(cli.getOptions()); 116 | }); 117 | }); 118 | -------------------------------------------------------------------------------- /test/lib/css/checker.spec.js: -------------------------------------------------------------------------------- 1 | var File = require('vinyl'); 2 | 3 | var checker = require('../../../lib/css/checker'); 4 | var cli = require('../../../lib/cli'); 5 | 6 | 7 | describe('checker', function () { 8 | 9 | 10 | it('options', function () { 11 | var options = checker.options; 12 | 13 | expect(options.name).toBe('csshint'); 14 | expect(options.type).toBe('css'); 15 | expect(options.suffix).toBe('css'); 16 | expect(options.ignore).toBe('m.css,min.css'); 17 | 18 | }); 19 | 20 | it('isValid', function () { 21 | var invalidFiles = [ 22 | new File({contents: Buffer.from(''), path: 'test/a.m.css'}), 23 | new File({contents: Buffer.from(''), path: 'test/b.min.css'}), 24 | new File({contents: Buffer.from(''), path: 'test/baz.x'}) 25 | ]; 26 | 27 | var hasValid = invalidFiles.some(function (file) { 28 | return checker.isValid(file); 29 | }); 30 | 31 | expect(hasValid).toBeFalsy(); 32 | 33 | var validFiles = [ 34 | new File({contents: Buffer.from(''), path: 'test/a.css'}), 35 | new File({contents: Buffer.from(''), path: 'test/b.css'}), 36 | new File({contents: Buffer.from(''), path: 'test/c.css'}) 37 | ]; 38 | 39 | var hasInvalid = validFiles.some(function (file) { 40 | return !checker.isValid(file); 41 | }); 42 | 43 | expect(hasInvalid).toBeFalsy(); 44 | 45 | }); 46 | 47 | it('isValid with no ignore', function () { 48 | checker.options.ignore = ''; 49 | 50 | var validFiles = [ 51 | new File({contents: Buffer.from(''), path: 'test/a.foo.css'}), 52 | new File({contents: Buffer.from(''), path: 'test/b.bar.css'}), 53 | new File({contents: Buffer.from(''), path: 'test/a.css'}), 54 | new File({contents: Buffer.from(''), path: 'test/b.css'}), 55 | new File({contents: Buffer.from(''), path: 'test/c.css'}) 56 | ]; 57 | 58 | var hasInvalid = validFiles.some(function (file) { 59 | return !checker.isValid(file); 60 | }); 61 | 62 | expect(hasInvalid).toBeFalsy(); 63 | }); 64 | 65 | 66 | it('check with promise', function (done) { 67 | 68 | var options = cli.getOptions(); 69 | 70 | checker 71 | .check('\nbody{}', 'path/to/file.css', options) 72 | .then(function (errors) { 73 | expect(errors.length).toBe(1); 74 | 75 | var error = errors[0]; 76 | expect(error.line).toBe(2); 77 | expect(error.column).toBe(5); 78 | expect(error.rule).toBe('require-before-space'); 79 | done(); 80 | }); 81 | 82 | }); 83 | 84 | it('check with invalid content', function (done) { 85 | 86 | var options = cli.getOptions(); 87 | 88 | checker 89 | .check('body{', 'path/to/file.css', options) 90 | .then(function (errors) { 91 | expect(errors.length).toBe(1); 92 | expect(errors[0].code).toBe('999'); 93 | done(); 94 | }); 95 | 96 | }); 97 | 98 | }); 99 | -------------------------------------------------------------------------------- /test/lib/css/formatter.spec.js: -------------------------------------------------------------------------------- 1 | var File = require('vinyl'); 2 | 3 | var formatter = require('../../../lib/css/formatter'); 4 | var cli = require('../../../lib/cli'); 5 | 6 | 7 | describe('formatter', function () { 8 | 9 | it('options', function () { 10 | var options = formatter.options; 11 | 12 | expect(options.name).toBe('csscomb'); 13 | expect(options.type).toBe('css'); 14 | expect(options.suffix).toEqual(/\.(?:c|le|sa|sc)ss$/); 15 | 16 | }); 17 | 18 | it('isValid', function () { 19 | var invalidFiles = [ 20 | new File({contents: Buffer.from(''), path: 'test/a.styl'}), 21 | new File({contents: Buffer.from(''), path: 'test/b.js'}), 22 | new File({contents: Buffer.from(''), path: 'test/baz.xcss'}) 23 | ]; 24 | 25 | var hasValid = invalidFiles.some(function (file) { 26 | return formatter.isValid(file); 27 | }); 28 | 29 | expect(hasValid).toBeFalsy(); 30 | 31 | var validFiles = [ 32 | new File({contents: Buffer.from(''), path: 'test/a.css'}), 33 | new File({contents: Buffer.from(''), path: 'test/b.less'}), 34 | new File({contents: Buffer.from(''), path: 'test/c.sass'}), 35 | new File({contents: Buffer.from(''), path: 'test/d.scss'}) 36 | ]; 37 | 38 | var hasInvalid = validFiles.some(function (file) { 39 | return !formatter.isValid(file); 40 | }); 41 | 42 | expect(hasInvalid).toBeFalsy(); 43 | 44 | }); 45 | 46 | 47 | it('format', function (done) { 48 | 49 | var options = cli.getOptions(); 50 | 51 | formatter 52 | .format('p{\nheight:0px}', 'path/to/file.css', options) 53 | .then(function (formatted) { 54 | expect(formatted).toEqual('p {\n height: 0;\n}\n'); 55 | done(); 56 | }); 57 | 58 | }); 59 | 60 | it('html files should be take as css', function (done) { 61 | 62 | var options = cli.getOptions(); 63 | 64 | formatter 65 | .format('p{\nheight:0px}', 'path/to/file.html', options) 66 | .then(function (formatted) { 67 | expect(formatted).toEqual('p {\n height: 0;\n}\n'); 68 | done(); 69 | }); 70 | }); 71 | 72 | it('one empty rule should be ignore', function (done) { 73 | 74 | var options = cli.getOptions(); 75 | 76 | formatter 77 | .format('a{}', 'path/to/file.css', options) 78 | .then(function (formatted) { 79 | expect(formatted).toEqual('a{}'); 80 | done(); 81 | }); 82 | }); 83 | 84 | it('one empty rule should be throw in debug', function (done) { 85 | 86 | var options = cli.getOptions(); 87 | options.debug = true; 88 | 89 | formatter 90 | .format('a{}', 'path/to/file.css', options) 91 | .catch(function (error) { 92 | expect(error != null).toBe(true); 93 | done(); 94 | }); 95 | 96 | 97 | }); 98 | 99 | }); 100 | -------------------------------------------------------------------------------- /test/lib/formatter.spec.js: -------------------------------------------------------------------------------- 1 | var fs = require('vinyl-fs'); 2 | var File = require('vinyl'); 3 | 4 | var helper = require('../helper'); 5 | 6 | var Formatter = require('../../lib/formatter'); 7 | var formatter = new Formatter({ 8 | name: 'bbd', 9 | type: 'spec', 10 | suffix: 'spec', 11 | ignore: 'foo.spec,bar.spec' 12 | }); 13 | 14 | 15 | describe('formatter', function () { 16 | 17 | it('isValid', function () { 18 | var invalidFiles = [ 19 | new File({contents: Buffer.from(''), path: 'test/a.foo.spec'}), 20 | new File({contents: Buffer.from(''), path: 'test/b.bar.spec'}), 21 | new File({contents: Buffer.from(''), path: 'test/baz.x'}) 22 | ]; 23 | 24 | var hasValid = invalidFiles.some(function (file) { 25 | return formatter.isValid(file); 26 | }); 27 | 28 | expect(hasValid).toBeFalsy(); 29 | 30 | var validFiles = [ 31 | new File({contents: Buffer.from(''), path: 'test/a.spec'}), 32 | new File({contents: Buffer.from(''), path: 'test/b.spec'}), 33 | new File({contents: Buffer.from(''), path: 'test/c.spec'}) 34 | ]; 35 | 36 | var hasInvalid = validFiles.some(function (file) { 37 | return !formatter.isValid(file); 38 | }); 39 | 40 | expect(hasInvalid).toBeFalsy(); 41 | 42 | }); 43 | 44 | it('isValid with no ignore', function () { 45 | formatter.options.ignore = ''; 46 | 47 | var validFiles = [ 48 | new File({contents: Buffer.from(''), path: 'test/a.foo.spec'}), 49 | new File({contents: Buffer.from(''), path: 'test/b.bar.spec'}), 50 | new File({contents: Buffer.from(''), path: 'test/a.spec'}), 51 | new File({contents: Buffer.from(''), path: 'test/b.spec'}), 52 | new File({contents: Buffer.from(''), path: 'test/c.spec'}) 53 | ]; 54 | 55 | var hasInvalid = validFiles.some(function (file) { 56 | return !formatter.isValid(file); 57 | }); 58 | 59 | expect(hasInvalid).toBeFalsy(); 60 | }); 61 | 62 | it('format() will throw', function () { 63 | expect(formatter.format).toThrow(); 64 | }); 65 | 66 | it('register should be call when exec', function (done) { 67 | var register = formatter.register; 68 | spyOn(formatter, 'register').and.callThrough(); 69 | 70 | formatter.format = function (contents, path, cliOptions) { 71 | return ''; 72 | }; 73 | 74 | fs.src('test/fixture/checker/**') 75 | .pipe(formatter.exec({})) 76 | .on('end', function () { 77 | expect(formatter.register).toHaveBeenCalled(); 78 | formatter.register = register; 79 | done(); 80 | }); 81 | 82 | }); 83 | 84 | it('format with callback', function (done) { 85 | 86 | formatter.format = function (contents, path, cliOptions, callback) { 87 | expect(typeof callback).toBe('function'); 88 | return callback(''); 89 | }; 90 | 91 | fs.src('test/fixture/checker/**') 92 | .pipe(formatter.exec({})) 93 | .on('end', done); 94 | 95 | }); 96 | 97 | it('format with promise', function (done) { 98 | 99 | formatter.format = function (contents, path, cliOptions) { 100 | return Promise.resolve([true]); 101 | }; 102 | 103 | fs.src('test/fixture/checker/**') 104 | .pipe(formatter.exec({})) 105 | .pipe(helper.pass( 106 | function (file) { 107 | if (!file.errors) { 108 | return; 109 | } 110 | 111 | expect(file.errors[0]).toBeTruthy(); 112 | }, done) 113 | ); 114 | }); 115 | 116 | }); 117 | -------------------------------------------------------------------------------- /test/lib/ignored.spec.js: -------------------------------------------------------------------------------- 1 | var fs = require('vinyl-fs'); 2 | var helper = require('../helper'); 3 | 4 | describe('ignored', function () { 5 | 6 | var ignored = require('../../lib/ignored'); 7 | 8 | it('specials can\'t be ignored', function (done) { 9 | var specials = ['lib/c.spec.js']; 10 | 11 | var checkSpecials = function (file) { 12 | var found = specials.some(function (special) { 13 | var filepath = file.relative.replace('\\', '/'); 14 | return filepath.indexOf(special) > -1; 15 | }); 16 | 17 | expect(found).toBeTruthy(); 18 | 19 | }; 20 | 21 | fs.src('test/fixture/ignored/*.spec.js') 22 | .pipe(ignored({ignore: '**/*.spec.js'}, specials)) 23 | .pipe(helper.pass(checkSpecials, done)); 24 | }); 25 | 26 | it('should be ignored simply', function (done) { 27 | var check = jasmine.createSpy('check'); 28 | 29 | fs.src('test/fixture/ignored/*.js') 30 | .pipe(ignored({ignore: ['*.min.js', '*.spec.js']}, [])) 31 | .pipe(helper.pass( 32 | check, 33 | function () { 34 | expect(check).not.toHaveBeenCalled(); 35 | done(); 36 | } 37 | )); 38 | }); 39 | 40 | 41 | it('should be ignored by .fecsignore', function (done) { 42 | var check = jasmine.createSpy('check'); 43 | 44 | fs.src('test/fixture/ignored/*.spec') 45 | .pipe(ignored({}, [], 'test/fixture/ignored/.fecsignore')) 46 | .pipe(helper.pass( 47 | check, 48 | function () { 49 | expect(check).not.toHaveBeenCalled(); 50 | done(); 51 | } 52 | )); 53 | }); 54 | 55 | it('can be unignored/specials by .fecsignore', function (done) { 56 | var check = jasmine.createSpy('check'); 57 | 58 | fs.src('test/fixture/ignored/*.js') 59 | .pipe(ignored({}, [], 'test/fixture/ignored/.unfecsignore')) 60 | .pipe( 61 | helper.pass( 62 | check, 63 | function () { 64 | expect(check.calls.count()).toEqual(1); 65 | done(); 66 | } 67 | ) 68 | ); 69 | }); 70 | 71 | it('should be loged when ignored on debug', function (done) { 72 | var check = jasmine.createSpy('check'); 73 | var log = console.log; 74 | 75 | console.log = jasmine.createSpy('log'); 76 | 77 | fs.src('test/fixture/ignored/*.js') 78 | .pipe(ignored({debug: true}, [], 'test/fixture/ignored/.unfecsignore')) 79 | .pipe( 80 | helper.pass( 81 | check, 82 | function () { 83 | expect(console.log).toHaveBeenCalledWith('%s is ignored by %s.', 'a.min.js', '*.js'); 84 | console.log = log; 85 | done(); 86 | } 87 | ) 88 | ); 89 | 90 | }); 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /test/lib/js/formatter.spec.js: -------------------------------------------------------------------------------- 1 | var File = require('vinyl'); 2 | 3 | var formatter = require('../../../lib/js/formatter'); 4 | var cli = require('../../../lib/cli'); 5 | 6 | 7 | describe('formatter', function () { 8 | 9 | it('options', function () { 10 | var options = formatter.options; 11 | 12 | expect(options.name).toBe('esformatter'); 13 | expect(options.type).toBe('js'); 14 | expect(options.suffix).toBe('js,jsx,es,es6'); 15 | 16 | }); 17 | 18 | it('isValid', function () { 19 | var invalidFiles = [ 20 | new File({contents: Buffer.from(''), path: 'test/a.html'}), 21 | new File({contents: Buffer.from(''), path: 'test/b.css'}), 22 | new File({contents: Buffer.from(''), path: 'test/baz.less'}) 23 | ]; 24 | 25 | var hasValid = invalidFiles.some(function (file) { 26 | return formatter.isValid(file); 27 | }); 28 | 29 | expect(hasValid).toBeFalsy(); 30 | 31 | var validFiles = [ 32 | new File({contents: Buffer.from(''), path: 'test/a.js'}), 33 | new File({contents: Buffer.from(''), path: 'test/b.m.js'}), 34 | new File({contents: Buffer.from(''), path: 'test/c.min.js'}) 35 | ]; 36 | 37 | var hasInvalid = validFiles.some(function (file) { 38 | return !formatter.isValid(file); 39 | }); 40 | 41 | expect(hasInvalid).toBeFalsy(); 42 | 43 | }); 44 | 45 | 46 | it('fix', function () { 47 | 48 | var options = cli.getOptions(); 49 | options.fix = true; 50 | 51 | var formatted = formatter.format('if (typeof alert ==\'function\') {}', 'path/to/file.js', options); 52 | 53 | expect(formatted).toEqual('if (typeof alert === \'function\') {\n}\n'); 54 | 55 | }); 56 | 57 | it('format', function () { 58 | 59 | var options = cli.getOptions(); 60 | 61 | var formatted = formatter.format('var foo=bar', 'path/to/file.js', options); 62 | 63 | expect(formatted).toEqual('var foo = bar;\n'); 64 | 65 | }); 66 | 67 | 68 | it('syntax error without --fix', function () { 69 | 70 | var options = cli.getOptions(); 71 | 72 | var formatted = formatter.format('var foo=', 'path/to/file.js', options); 73 | 74 | expect(formatted).toEqual('var foo='); 75 | 76 | }); 77 | 78 | it('syntax error with --fix', function () { 79 | 80 | var options = cli.getOptions(); 81 | options.fix = true; 82 | 83 | var formatted = formatter.format('var foo=', 'path/to/file.js', options); 84 | 85 | expect(formatted).toEqual('var foo='); 86 | 87 | }); 88 | 89 | 90 | it('syntax error should be throw in debug', function () { 91 | 92 | var options = cli.getOptions(); 93 | options.debug = true; 94 | 95 | var format = function () { 96 | formatter.format('var foo=', 'path/to/file.js', options); 97 | }; 98 | 99 | expect(format).toThrow(); 100 | 101 | }); 102 | 103 | }); 104 | -------------------------------------------------------------------------------- /test/lib/js/rules/arrow-body-style.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Tests for arrow-body-style 3 | * @author Alberto Rodríguez 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/arrow-body-style'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('arrow-body-style', rule, { 21 | valid: [ 22 | 'var foo = () => 0;', 23 | 'var addToB = (a) => { b = b + a };', 24 | 'var foo = (retv, name) => {\nretv[name] = true;\nreturn retv;\n};', 25 | 'var foo = () => { bar(); };', 26 | 'var foo = () => { b = a };', 27 | 'let foo = () => {};', 28 | 'var foo = () => { /* do nothing */ };', 29 | { 30 | code: 'var foo = () => { return {}; };', 31 | options: [ 32 | 'as-needed', 33 | { 34 | ObjectExpression: true 35 | } 36 | ] 37 | }, 38 | { 39 | code: 'var foo = () => { return 0; };', 40 | options: ['always'] 41 | }, 42 | { 43 | code: 'var foo = () => { return bar(); };', 44 | options: ['always'] 45 | }, 46 | { 47 | code: 'var foo = () => ({});', 48 | options: [ 49 | 'as-needed', 50 | { 51 | ObjectExpression: true 52 | } 53 | ] 54 | }, 55 | { 56 | code: 'var foo = () => ({});', 57 | options: ['always'] 58 | } 59 | ], 60 | invalid: [ 61 | { 62 | code: 'var foo = () => { bar: 1 };', 63 | errors: [ 64 | { 65 | line: 1, 66 | type: 'ArrowFunctionExpression', 67 | message: 'Expected block statement surrounding arrow body.' 68 | } 69 | ] 70 | }, 71 | { 72 | code: 'var foo = () => 0;', 73 | options: ['always'], 74 | errors: [ 75 | { 76 | line: 1, 77 | column: 17, 78 | type: 'ArrowFunctionExpression', 79 | message: 'Expected block statement surrounding arrow body.' 80 | } 81 | ] 82 | }, 83 | { 84 | code: 'var foo = () => { return 0; };', 85 | options: ['as-needed'], 86 | errors: [ 87 | { 88 | line: 1, 89 | column: 17, 90 | type: 'ArrowFunctionExpression', 91 | message: 'Unexpected block statement surrounding arrow body.' 92 | } 93 | ] 94 | }, 95 | { 96 | code: 'var foo = () => { return bar(); };', 97 | options: ['as-needed'], 98 | errors: [ 99 | { 100 | line: 1, 101 | column: 17, 102 | type: 'ArrowFunctionExpression', 103 | message: 'Unexpected block statement surrounding arrow body.' 104 | } 105 | ] 106 | } 107 | ] 108 | }); 109 | -------------------------------------------------------------------------------- /test/lib/js/rules/eol-last.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check, that file is ended with newline, and there are no multiple empty lines at the end. 3 | * @author Nodeca Team 4 | * @author chris 5 | */ 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Requirements 10 | //------------------------------------------------------------------------------ 11 | 12 | var rule = require('../../../../lib/js/rules/eol-last'); 13 | var RuleTester = require('eslint').RuleTester; 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester(); 19 | 20 | ruleTester.run('eol-last', rule, { 21 | 22 | valid: [ 23 | '\n', 24 | '\r\n', 25 | 'var a = 123;\n', 26 | 'var a = 123;\r\n', 27 | { 28 | code: 'var a = 123;\n\n', 29 | options: [true] 30 | }, 31 | { 32 | code: 'var a = 123;\n \n', 33 | options: [true] 34 | }, 35 | { 36 | code: 'var a = 123;\r\n \r\n', 37 | options: [true] 38 | }, 39 | { 40 | code: 'var a = 123;\r\n\r\n', 41 | options: [true] 42 | } 43 | ], 44 | 45 | invalid: [ 46 | { 47 | code: 'var a = 123;', 48 | errors: [{message: 'Newline required at end of file but not found.', type: 'Program'}] 49 | }, 50 | { 51 | code: 'var a = 123;\n ', 52 | errors: [{message: 'Newline required at end of file but not found.', type: 'Program'}] 53 | }, 54 | { 55 | code: 'var a = 123;\r\n ', 56 | errors: [{message: 'Newline required at end of file but not found.', type: 'Program'}] 57 | }, 58 | { 59 | code: 'var a = 123;\r\n \r\n', 60 | errors: [{message: 'Unexpected blank line at end of file.', type: 'Program'}] 61 | } 62 | ] 63 | }); 64 | -------------------------------------------------------------------------------- /test/lib/js/rules/esnext-ext.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check that filename 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/esnext-ext'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('esnext-ext', rule, { 20 | 21 | valid: [ 22 | 'var a = 123;\n', 23 | 'var a = 123;\r\n', 24 | { 25 | code: 'var a = 123;', 26 | filename: 'foo/bar.js' 27 | }, 28 | { 29 | code: 'export default bar = ({foo}) => (
{foo}bar
);', 30 | filename: 'foo/bar.jsx', 31 | options: [['js', 'jsx']] 32 | } 33 | ], 34 | 35 | invalid: [ 36 | { 37 | code: 'var a = 123;', 38 | filename: 'foo/bar.es', 39 | options: ['js'], 40 | errors: [ 41 | {message: 'Expected file extension `js` but found `es`.baidu602', type: 'Program'} 42 | ] 43 | }, 44 | { 45 | code: 'var a = 123;', 46 | filename: 'foo/bar.ts', 47 | options: [['js', 'es']], 48 | errors: [ 49 | {message: 'Expected file extension `js` or `es` but found `ts`.baidu602', type: 'Program'} 50 | ] 51 | }, 52 | { 53 | code: 'var a = 123;', 54 | filename: 'foo/bar.es', 55 | options: [['js', 'es']], 56 | errors: [ 57 | {message: 'Expected file extension `js` but found `es`.baidu601', type: 'Program'} 58 | ] 59 | }, 60 | { 61 | code: 'var a = 123;', 62 | filename: 'foo/bar.ts', 63 | options: ['ts'], 64 | errors: [ 65 | {message: 'Expected file extension `js` but found `ts`.baidu601', type: 'Program'} 66 | ] 67 | }, 68 | { 69 | code: 'var a = 123;', 70 | filename: 'foo/bar.ts', 71 | options: [['ts']], 72 | errors: [ 73 | {message: 'Expected file extension `js` but found `ts`.baidu601', type: 'Program'} 74 | ] 75 | } 76 | ] 77 | }); 78 | -------------------------------------------------------------------------------- /test/lib/js/rules/export-on-declare.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check export declares. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/export-on-declare'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('export-on-declare', rule, { 20 | 21 | valid: [ 22 | 'export function foo() {}', 23 | 'import foo from "./foo";export {foo}', 24 | 'import * as foo from "./foo";export {foo}', 25 | 'let bar = true;export default function foo() {}', 26 | 'export const foo = 1;', 27 | 'export default function () {}', 28 | 'let foo = true;\nif (foo) {}\nexport default foo;', 29 | 'let foo = true;\nif (foo) {}\nexport default [foo, bar];', 30 | 'let foo = true;\nif (foo) {}\nexport {foo};', 31 | 'let foo = true;\nif (foo) {}\nexport {foo, bar};', 32 | 'export default from "xxx";', 33 | 'export default [foo, bar]', 34 | 'export default {foo, bar}', 35 | 'export default class {}', 36 | 'export class Foo {}', 37 | 'export default (function (foo) {return foo;})();', 38 | 'export {a};', 39 | 'export const foo = {a, bar};', 40 | 'export {};', 41 | ` 42 | import router from 'react-router'; 43 | import store from 'store'; 44 | let app = new Map({router, store}); 45 | export {router, store}; 46 | `, 47 | ` 48 | // @TODO: it need fixed when node.declaration.type is ExperimentalSpreadProperty. 49 | import foo from "./foo"; 50 | import bar from "./bar"; 51 | 52 | export default { 53 | ...foo 54 | }; 55 | ` 56 | ], 57 | 58 | invalid: [ 59 | { 60 | code: 'let foo = true;export {foo};', 61 | errors: [{message: 'Use `export` on declare.', type: 'Identifier'}] 62 | }, 63 | { 64 | code: 'let foo = true;export {foo, bar};', 65 | errors: [{message: 'Use `export` on declare.', type: 'Identifier'}] 66 | }, 67 | { 68 | code: 'let foo = true;export default {foo, bar};', 69 | errors: [{message: 'Use `export` on declare.', type: 'Identifier'}] 70 | }, 71 | { 72 | code: 'let foo = true;export default [foo, bar];', 73 | errors: [{message: 'Use `export` on declare.', type: 'Identifier'}] 74 | }, 75 | { 76 | code: 'const foo = true;export default foo;', 77 | errors: [{message: 'Use `export` on declare.', type: 'Identifier'}] 78 | } 79 | ] 80 | }); 81 | -------------------------------------------------------------------------------- /test/lib/js/rules/imports-on-top.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check the imports. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/imports-on-top'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('imports-on-top', rule, { 20 | 21 | valid: [ 22 | '"use strict";\nimport React from "react"', 23 | 'import React from "react"', 24 | '\nimport React from "react"\n', 25 | '\n \nimport React from "react"', 26 | '\n \nimport React from "react"\n ', 27 | '\n \nimport React from "react"\nimport {combineReducers} from "react-redux"', 28 | '\nimport React from "react";function foo() {}' 29 | ], 30 | 31 | invalid: [ 32 | { 33 | code: 'if (true) {}import React from "react";', 34 | errors: [{ 35 | message: 'All `import` statements must be at the top of the module.', 36 | type: 'ImportDeclaration' 37 | }] 38 | }, 39 | { 40 | code: 'function foo() {}\nimport React from "react";', 41 | errors: [{ 42 | message: 'All `import` statements must be at the top of the module.', 43 | type: 'ImportDeclaration' 44 | }] 45 | }, 46 | { 47 | code: ';;\nimport React from "react";function foo() {}', 48 | errors: [{ 49 | message: 'All `import` statements must be at the top of the module.', 50 | type: 'ImportDeclaration' 51 | }] 52 | } 53 | ] 54 | }); 55 | -------------------------------------------------------------------------------- /test/lib/js/rules/jsx-var.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check variables in JSX. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/jsx-var'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('jsx-var', rule, { 20 | 21 | valid: [ 22 | 'function aa() {};render();', 23 | 'render(
);', 24 | 'import App from "./app";\nrender();', 25 | 'import App from "./app";\nrender();', 26 | 'import App from "./app";\nrender();', 27 | 'import App from "./app";\nrender();', 28 | 'import App from "./app";\nlet store = createStore();\nrender();' 29 | ], 30 | 31 | invalid: [ 32 | { 33 | code: 'render();', 34 | errors: [{ 35 | message: '`unknownTagName` is not defined', 36 | type: 'JSXIdentifier' 37 | }] 38 | }, 39 | { 40 | code: 'render();', 41 | parserOptions: {sourceType: 'script'}, 42 | errors: [{ 43 | message: '`App` is not defined', 44 | type: 'JSXIdentifier' 45 | }] 46 | }, 47 | { 48 | code: 'render();', 49 | parserOptions: {sourceType: 'script'}, 50 | errors: [{ 51 | message: '`App` is not defined', 52 | type: 'JSXIdentifier' 53 | }] 54 | }, 55 | { 56 | code: 'render();', 57 | parserOptions: {sourceType: 'script'}, 58 | errors: [{ 59 | message: '`App` is not defined', 60 | type: 'JSXIdentifier' 61 | }] 62 | } 63 | ] 64 | }); 65 | -------------------------------------------------------------------------------- /test/lib/js/rules/max-calls-in-template.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for max-calls-in-template 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/max-calls-in-template'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('max-calls-in-template', rule, { 21 | valid: [ 22 | 'let foo = `foo`;', 23 | 'let foo = `foo ${bar}`;', 24 | 'let foobar = `${foo()} bar`', 25 | 'fo(`${foo}-bar`)', 26 | 'let foobar = `${foo} ${bar}`;', 27 | 'let foobar = `${foo()} ${bar()}`;' 28 | ], 29 | invalid: [ 30 | { 31 | code: 'let foobar = `${foo(bar())}`;', 32 | errors: [ 33 | { 34 | line: 1, 35 | type: 'TemplateLiteral', 36 | message: 'Too many nest function calls (2). Maximum allowed is 1.' 37 | } 38 | ] 39 | }, 40 | { 41 | code: 'f(`${foo(bar())}`);', 42 | errors: [ 43 | { 44 | line: 1, 45 | type: 'TemplateLiteral', 46 | message: 'Too many nest function calls (2). Maximum allowed is 1.' 47 | } 48 | ] 49 | }, 50 | { 51 | code: 'f(`${foo(bar(baz()), baz())}`);', 52 | options: [2], 53 | errors: [ 54 | { 55 | line: 1, 56 | type: 'TemplateLiteral', 57 | message: 'Too many nest function calls (3). Maximum allowed is 2.' 58 | } 59 | ] 60 | } 61 | ] 62 | }); 63 | -------------------------------------------------------------------------------- /test/lib/js/rules/no-anonymous-before-rest.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for no-anonymous-before-rest 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/no-anonymous-before-rest'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('no-anonymous-before-rest', rule, { 21 | valid: [ 22 | 'let [a, ...b] = c;', 23 | 'let [a, b, ...c] = d;', 24 | 'function foo(a, ...b) {}' 25 | ], 26 | invalid: [ 27 | { 28 | code: 'let [a, b,, ...other] = c;', 29 | errors: [ 30 | { 31 | line: 1, 32 | type: 'ArrayPattern', 33 | message: 'No anonymous elements (found 1) before rest element.' 34 | } 35 | ] 36 | }, 37 | { 38 | code: 'let [a,,, ...other] = b;', 39 | errors: [ 40 | { 41 | line: 1, 42 | type: 'ArrayPattern', 43 | message: 'No anonymous elements (found 2) before rest element.' 44 | } 45 | ] 46 | }, 47 | { 48 | code: 'let [,,, ...other] = a;', 49 | errors: [ 50 | { 51 | line: 1, 52 | type: 'ArrayPattern', 53 | message: 'No anonymous elements (found 3) before rest element.' 54 | } 55 | ] 56 | } 57 | ] 58 | }); 59 | -------------------------------------------------------------------------------- /test/lib/js/rules/no-eval.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check evals. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/no-eval'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('no-eval', rule, { 20 | 21 | valid: [ 22 | '(0, eval)("foo = true")', 23 | 'var foo = eval;foo("bar = true")', 24 | 'var foo = window.eval;foo("bar = true")' 25 | ], 26 | 27 | invalid: [ 28 | { 29 | code: 'eval("foo=true")', 30 | errors: [{ 31 | message: 'Avoid to use eval or window.eval.', 32 | type: 'CallExpression' 33 | }] 34 | }, 35 | { 36 | code: 'window.eval("foo=true")', 37 | errors: [{ 38 | message: 'Avoid to use eval or window.eval.', 39 | type: 'CallExpression' 40 | }] 41 | } 42 | ] 43 | }); 44 | -------------------------------------------------------------------------------- /test/lib/js/rules/no-forin-array.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check for-in. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/no-forin-array'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('no-forin-array', rule, { 20 | 21 | valid: [ 22 | 'for (var key in {foo: true}) {}', 23 | 'var foo = {bar: true};for (var key in foo) {}', 24 | 'for (var key in list) {}', 25 | 'var list;for (var key in list) {}', 26 | 'if (foo) {for (var key in list) {}}', 27 | 'function foo(list) {for (var key in list) {}}', 28 | 'function foo({list}) {for (var key in list) {}}', 29 | 'var foo = function () {for (var key in list) {}}', 30 | 'var foo = () => {for (var key in list) {}}' 31 | ], 32 | 33 | invalid: [ 34 | { 35 | code: 'var list = [1, 2, 3];for (var key in list) {}', 36 | errors: [{ 37 | message: 'Don\'t traverse Array with for-in.', 38 | type: 'ForInStatement' 39 | }] 40 | }, 41 | { 42 | code: 'for (var key in [1, 2, 3]) {}', 43 | errors: [{ 44 | message: 'Don\'t traverse Array with for-in.', 45 | type: 'ForInStatement' 46 | }] 47 | }, 48 | { 49 | code: 'for (var key in new Array(1, 2, 3)) {}', 50 | errors: [{ 51 | message: 'Don\'t traverse Array with for-in.', 52 | type: 'ForInStatement' 53 | }] 54 | }, 55 | { 56 | code: 'var list = new Array(1, 2, 3);for (var key in list) {}', 57 | errors: [{ 58 | message: 'Don\'t traverse Array with for-in.', 59 | type: 'ForInStatement' 60 | }] 61 | }, 62 | { 63 | code: 'var list = Array(1, 2, 3);for (var key in list) {}', 64 | errors: [{ 65 | message: 'Don\'t traverse Array with for-in.', 66 | type: 'ForInStatement' 67 | }] 68 | }, 69 | { 70 | code: 'for (var key in Array(1, 2, 3)) {}', 71 | errors: [{ 72 | message: 'Don\'t traverse Array with for-in.', 73 | type: 'ForInStatement' 74 | }] 75 | }, 76 | { 77 | code: 'function foo(...list) {for (var key in list) {}}', 78 | errors: [{ 79 | message: 'Don\'t traverse Array with for-in.', 80 | type: 'ForInStatement' 81 | }] 82 | }, 83 | // { 84 | // code: 'function foo({list: []}) {for (var key in list) {}}', 85 | // errors: [{ 86 | // message: 'Don\'t traverse Array with for-in.', 87 | // type: 'ForInStatement' 88 | // }] 89 | // }, 90 | { 91 | code: '/**\n * foo\n * @param {Array} list xxx\n */\nvar foo = function (list) {for (var key in list) {}}', 92 | errors: [{ 93 | message: 'Don\'t traverse Array with for-in.', 94 | type: 'ForInStatement' 95 | }] 96 | }, 97 | { 98 | code: [ 99 | '/**', 100 | ' * foo', 101 | ' * @param {Array} list xxx', 102 | ' */', 103 | 'function foo(list) {if (list) for (var key in list) {}}' 104 | ].join('\n'), 105 | errors: [{ 106 | message: 'Don\'t traverse Array with for-in.', 107 | type: 'ForInStatement' 108 | }] 109 | } 110 | ] 111 | }); 112 | -------------------------------------------------------------------------------- /test/lib/js/rules/no-this-arrow.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for no-this-arrow 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/no-this-arrow'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('no-this-arrow', rule, { 21 | valid: [ 22 | 'let foo = {bar: () => {}};foo.bar.baz.call(null)', 23 | 'let foo = {baz: {bar: () => {}}};foo.bar.baz.call(null)', 24 | 'let foo = {baz() {this.a = true;baz(() => {this.a = false;})}}', 25 | 'function foo() {}foo.apply(null)', 26 | 'let bar = () => {};foo.apply(null)', 27 | 'foo.apply(null)', 28 | 'foo.call(null)', 29 | 'foo().call(null)' 30 | ], 31 | invalid: [ 32 | { 33 | code: 'let foo = () => {};foo.call(null)', 34 | errors: [ 35 | { 36 | line: 1, 37 | type: 'ArrowFunctionExpression', 38 | message: 'Disallow to use arrow function expression within `this` context.' 39 | } 40 | ] 41 | }, 42 | { 43 | code: 'let foo = {bar: () => {}};foo.bar.call(null)', 44 | errors: [ 45 | { 46 | line: 1, 47 | type: 'ArrowFunctionExpression', 48 | message: 'Disallow to use arrow function expression within `this` context.' 49 | } 50 | ] 51 | }, 52 | { 53 | code: 'let foo = {bar: {baz: () => {}}};foo.bar.baz.call(null)', 54 | errors: [ 55 | { 56 | line: 1, 57 | type: 'ArrowFunctionExpression', 58 | message: 'Disallow to use arrow function expression within `this` context.' 59 | } 60 | ] 61 | } 62 | ] 63 | }); 64 | -------------------------------------------------------------------------------- /test/lib/js/rules/prefer-class.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check class definitions. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/prefer-class'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('prefer-class', rule, { 20 | 21 | valid: [ 22 | 'function foo() {}', 23 | 'var foo = function () {}', 24 | 'var foo = function () {return "bar";}', 25 | '(function () {})()', 26 | 'class Foo {}', 27 | 'class Foo extends Bar {}', 28 | 'export class Foo{}', 29 | 'export default class {}', 30 | 'export default class Foo extends Bar {}', 31 | 'export default function () {}', 32 | 'export default function Foo(props, context) {}', 33 | 'let Foo = function (props, context) {}', 34 | 'let Foo = function ({foo, bar}, context) {return
;}', 35 | 'let Foo = function ({foo, bar}, context) {return foo ? null : ;}', 36 | 'let Foo = function ({foo, bar}) {return foo ? :
;}', 37 | 'let Foo = function ({foo, bar}) {return foo && ;}', 38 | 'let Foo = function ({foo, bar}) {let jsx = foo ? :
;return jsx;}', 39 | 'let Foo = ({foo, bar}) => (
)', 40 | 'let Foo = ({foo, bar}, context) =>
', 41 | 'let Foo = ({foo, bar}, {baz}) =>
', 42 | 'let Foo = (prop, ctx) => (
)', 43 | 'let Foo = ({onClick}) => (
onClick()} />)' 44 | ], 45 | 46 | invalid: [ 47 | { 48 | code: 'let Foo = function ({foo, bar}, context) {return;}', 49 | errors: [{ 50 | message: 'Expected `class Foo` but found `FunctionExpression`.', 51 | type: 'FunctionExpression' 52 | }] 53 | }, 54 | { 55 | code: 'function Foo({foo, bar}) {return null;}', 56 | errors: [{ 57 | message: 'Expected `class Foo` but found `FunctionDeclaration`.', 58 | type: 'FunctionDeclaration' 59 | }] 60 | }, 61 | { 62 | code: 'let Foo = function ({foo, bar}, context) {return jsx;}', 63 | errors: [{ 64 | message: 'Expected `class Foo` but found `FunctionExpression`.', 65 | type: 'FunctionExpression' 66 | }] 67 | }, 68 | { 69 | code: 'var Foo = function () {}', 70 | errors: [{ 71 | message: 'Expected `class Foo` but found `FunctionExpression`.', 72 | type: 'FunctionExpression' 73 | }] 74 | }, 75 | { 76 | code: 'function Foo() {}', 77 | errors: [{ 78 | message: 'Expected `class Foo` but found `FunctionDeclaration`.', 79 | type: 'FunctionDeclaration' 80 | }] 81 | } 82 | ] 83 | }); 84 | -------------------------------------------------------------------------------- /test/lib/js/rules/prefer-destructure.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for prefer-destructure 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/prefer-destructure'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('prefer-destructure', rule, { 21 | valid: [ 22 | 'let foo = 1, bar = 2;', 23 | 'function foobar() {let foo = 1, bar = 2;}', 24 | 'let foobar = function () {let foo = 1, bar = 2;}', 25 | 'foo((x, y) => x = y + 1);', 26 | 'let temp = x;y = temp;x = 1;', 27 | 'let temp = x;y = temp;x = temp;', 28 | 'let {a, b} = c.d', 29 | 'let foo = {bar: 1, baz: 2};export let bar = foo.bar;export let baz = foo.baz;', 30 | { 31 | code: [ 32 | 'function foo(dom) {', 33 | ' let x = dom.get().x;', 34 | ' let y = dom.y;', 35 | ' return {x, y};', 36 | '}' 37 | ].join('\n') 38 | }, 39 | { 40 | code: [ 41 | 'function getXY(element) {', 42 | ' let x = 0;', 43 | ' let y = 0;', 44 | ' while (element.offsetParent) {', 45 | ' y += element.offsetTop;', 46 | ' x += element.offsetLeft;', 47 | ' element = element.offsetParent;', 48 | ' }', 49 | ' return {x, y};', 50 | '}' 51 | ].join('\n') 52 | }, 53 | { 54 | code: [ 55 | 'function remove(node, keepChildren) {', 56 | ' let parent = node.parentNode;', 57 | ' let child;', 58 | ' if (parent) {', 59 | ' if (keepChildren && node.hasChildNodes()) {', 60 | ' while (child = node.firstChild) {', 61 | ' parent.insertBefore(child, node);', 62 | ' }', 63 | ' }', 64 | ' parent.removeChild(node);', 65 | ' }', 66 | ' return node;', 67 | '}' 68 | ].join('\n') 69 | } 70 | ], 71 | invalid: [ 72 | { 73 | code: 'function foo(x, y) {\n' 74 | + ' let temp = x;\n' 75 | + ' x = y;\n' 76 | + ' y = temp;\n' 77 | + '}', 78 | errors: [ 79 | { 80 | line: 2, 81 | type: 'Identifier', 82 | message: 'Expected to reduce `temp` by destructuring `[x, y]`.' 83 | } 84 | ] 85 | }, 86 | { 87 | code: '' 88 | + 'let foo = foobar.foo;\n' 89 | + 'let bar = foobar.bar;', 90 | errors: [ 91 | { 92 | line: 1, 93 | type: 'Identifier', 94 | message: 'Expected to reduce `foo, bar` by destructuring `foobar`.' 95 | } 96 | ] 97 | }, 98 | { 99 | code: '' 100 | + 'let temp = x;\n' 101 | + 'x = y;\n' 102 | + 'y = temp', 103 | errors: [ 104 | { 105 | line: 1, 106 | type: 'Identifier', 107 | message: 'Expected to reduce `temp` by destructuring `[x, y]`.' 108 | } 109 | ] 110 | } 111 | ] 112 | }); 113 | -------------------------------------------------------------------------------- /test/lib/js/rules/prefer-super.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check `Super` callings. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/prefer-super'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('prefer-super', rule, { 20 | 21 | valid: [ 22 | 'function foo() {}foo()', 23 | 'var foo = function () {};foo()', 24 | 'class Foo {bar() {}}', 25 | 'class Foo extends Bar {bar() {}}', 26 | 'class Foo extends Bar {bar() {super.bar()}}', 27 | 'class Foo extends null {bar() {}}' 28 | ], 29 | 30 | invalid: [ 31 | { 32 | code: 'class Foo extends Bar{bar() {Bar.prototype.bar.call(this)}}', 33 | errors: [{ 34 | message: 'Use `super` call instead.', 35 | type: 'CallExpression' 36 | }] 37 | }, 38 | { 39 | code: 'class Foo extends Bar{bar() {Bar.prototype.bar.call(this, true)}}', 40 | errors: [{ 41 | message: 'Use `super` call instead.', 42 | type: 'CallExpression' 43 | }] 44 | }, 45 | { 46 | code: 'class Foo extends Bar{bar() {Bar.prototype.bar.apply(this)}}', 47 | errors: [{ 48 | message: 'Use `super` call instead.', 49 | type: 'CallExpression' 50 | }] 51 | }, 52 | { 53 | code: 'class Foo extends Bar{bar() {Bar.prototype.bar.apply(this, true)}}', 54 | errors: [{ 55 | message: 'Use `super` call instead.', 56 | type: 'CallExpression' 57 | }] 58 | } 59 | ] 60 | }); 61 | -------------------------------------------------------------------------------- /test/lib/js/rules/shim-promise.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for shim-promise 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/shim-promise'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('shim-promise', rule, { 21 | valid: [ 22 | 'var Promise = function () {};', 23 | 'function Promise() {}', 24 | 'class Promise {}', 25 | '(function () {Promise = function () {};})()', 26 | 'Promise = (function () {var Promise = function () {};return Promise;})()', 27 | 'Promise = (function () {function foo() {};return foo;})()', 28 | '(function () {var Promise1 = function () {};})()', 29 | '(function (global) {var Promise = function () {};global.Promise = Promise;})(window)', 30 | { 31 | code: '' 32 | + '(function (factory) {\n' 33 | + ' window.Promise = factory();\n' 34 | + '})(function () {\n' 35 | + ' var Promise = function () {};\n' 36 | + ' return Promise;\n' 37 | + '});' 38 | }, 39 | { 40 | code: '(function (win) {var Promise = function () {};win.Promise = Promise;})(window)', 41 | options: ['win'] 42 | } 43 | ], 44 | invalid: [ 45 | { 46 | code: '(function () {var Promise;Promise = function () {};})()', 47 | errors: [ 48 | { 49 | line: 1, 50 | type: 'Identifier', 51 | message: 'Promise should be shimmed to global scope.' 52 | } 53 | ] 54 | }, 55 | { 56 | code: '(function () {var Promise = function () {};})()', 57 | errors: [ 58 | { 59 | line: 1, 60 | type: 'Identifier', 61 | message: 'Promise should be shimmed to global scope.' 62 | } 63 | ] 64 | }, 65 | { 66 | code: '(function (global) {var Promise = function () {};global.Promise = null;})(window)', 67 | errors: [ 68 | { 69 | line: 1, 70 | type: 'Identifier', 71 | message: 'Promise should be shimmed to global scope.' 72 | } 73 | ] 74 | }, 75 | { 76 | code: '(function (global) {var Promise = function () {};Promise = null;})(window)', 77 | errors: [ 78 | { 79 | line: 1, 80 | type: 'Identifier', 81 | message: 'Promise should be shimmed to global scope.' 82 | } 83 | ] 84 | } 85 | ] 86 | }); 87 | -------------------------------------------------------------------------------- /test/lib/js/rules/use-async-require.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file test for use-async-require. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/use-async-require'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('use-async-require', rule, { 20 | 21 | valid: [ 22 | 'var require = function () {};\nrequire("foo");', 23 | 'Require("foo");', 24 | 'foo.require("foo");', 25 | 'function foo(require) {\nrequire("foo");\n}', 26 | 'require(["foo"], callback);', 27 | 'var deps = ["foo", "bar"];require(deps, callback)' 28 | ], 29 | 30 | invalid: [ 31 | { 32 | code: 'require(foo, callback)', 33 | errors: [{ 34 | message: 'Global require should be called as async.', 35 | type: 'Identifier' 36 | }] 37 | }, 38 | { 39 | code: 'require(["foo"]);', 40 | errors: [{ 41 | message: 'Global require should be called as async.', 42 | type: 'ArrayExpression' 43 | }] 44 | }, 45 | { 46 | code: 'require("foo");', 47 | errors: [ 48 | { 49 | message: 'Global require should be called as async.', 50 | type: 'Literal' 51 | } 52 | ] 53 | }, 54 | { 55 | code: 'var foo = "foo";require(foo, callback);', 56 | errors: [ 57 | { 58 | message: 'Global require should be called as async.', 59 | type: 'Identifier' 60 | } 61 | ] 62 | } 63 | ] 64 | }); 65 | -------------------------------------------------------------------------------- /test/lib/js/rules/use-method-definition.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check method definition. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/use-method-definition'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('use-method-definition', rule, { 20 | 21 | valid: [ 22 | 'let foo = {bar: {}}', 23 | 'let foo = {bar() {}}', 24 | 'let foo = {["bar"]() {}}', 25 | 'class Foo {bar() {}}', 26 | 'class Foo extends Bar {bar() {}}', 27 | 'class Foo extends null {["bar"]() {}}', 28 | 'let foo = {bar: () => {}}', 29 | 'let foo = {bar: (n) => {return n * 10;}}', 30 | 'let foo = {baz: 1, bar: () => {this.baz++; return this.baz;}}', 31 | 'let foo = {baz: 1, values: list => list.map(item => item.value)}' 32 | ], 33 | 34 | invalid: [ 35 | { 36 | code: 'let foo = {bar: n => {var baz = n;return baz + n;}}', 37 | errors: [{ 38 | message: 'Expected MethodDefinition but saw ArrowFunctionExpression.', 39 | type: 'ArrowFunctionExpression' 40 | }] 41 | }, 42 | { 43 | code: 'let foo = {bar: function () {}}', 44 | errors: [{ 45 | message: 'Expected MethodDefinition but saw FunctionExpression.', 46 | type: 'FunctionExpression' 47 | }] 48 | }, 49 | { 50 | code: 'Foo.prototype = {bar: function () {}}', 51 | errors: [{ 52 | message: 'Expected MethodDefinition but saw FunctionExpression.', 53 | type: 'FunctionExpression' 54 | }] 55 | } 56 | ] 57 | }); 58 | -------------------------------------------------------------------------------- /test/lib/js/rules/use-property-shorthand.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check method definition. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/use-property-shorthand'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('use-property-shorthand', rule, { 20 | 21 | valid: [ 22 | 'let foo = true,bar = {foo}', 23 | 'let foo = true,bar = false,baz = {foo, bar}', 24 | { 25 | code: 'let foo = true,bar = {foo, baz() {}}', 26 | options: [{method: true}] 27 | }, 28 | { 29 | code: 'let foo = true,bar = {foo: foo, baz() {}}', 30 | options: [{method: false}] 31 | }, 32 | { 33 | code: 'let foo = true,bar = {foo, ["baz"]() {}}', 34 | options: [{computed: true}] 35 | }, 36 | { 37 | code: 'let foo = true,bar = {foo: foo, ["baz"]() {}}', 38 | options: [{computed: false}] 39 | }, 40 | { 41 | code: 'let foo = true,bar = {foo, ...args}', 42 | options: [{spread: true}] 43 | }, 44 | { 45 | code: 'let foo = true,bar = {foo: foo, ...args}', 46 | options: [{spread: false}] 47 | }, 48 | { 49 | code: 'let foo = true,bar = {foo, get baz() {}}', 50 | options: [{get: true}] 51 | }, 52 | { 53 | code: 'let foo = true,bar = {foo, get baz() {}}', 54 | options: [{get: false}] 55 | }, 56 | { 57 | code: 'let foo = true,bar = {foo, set baz(v) {}}', 58 | options: [{set: true}] 59 | }, 60 | { 61 | code: 'let foo = true,bar = {foo, set baz(v) {}}', 62 | options: [{set: false}] 63 | } 64 | ], 65 | 66 | invalid: [ 67 | { 68 | code: 'let foo = true,bar = {foo: foo}', 69 | errors: [{ 70 | message: 'Expected shorthand for `foo`.', 71 | type: 'Property' 72 | }] 73 | }, 74 | { 75 | code: 'let foo = true,bar = {foo: foo, baz() {}}', 76 | options: [{method: true}], 77 | errors: [{ 78 | message: 'Expected shorthand for `foo`.', 79 | type: 'Property' 80 | }] 81 | }, 82 | { 83 | code: 'let foo = true,bar = {foo: foo, ["baz"]() {}}', 84 | options: [{computed: true}], 85 | errors: [{ 86 | message: 'Expected shorthand for `foo`.', 87 | type: 'Property' 88 | }] 89 | }, 90 | { 91 | code: 'let foo = true,bar = {foo: foo, ...args}', 92 | options: [{spread: true}], 93 | errors: [{ 94 | message: 'Expected shorthand for `foo`.', 95 | type: 'Property' 96 | }] 97 | }, 98 | { 99 | code: 'let foo = true,bar = {foo: foo, get baz() {}}', 100 | options: [{get: true}], 101 | errors: [{ 102 | message: 'Expected shorthand for `foo`.', 103 | type: 'Property' 104 | }] 105 | }, 106 | { 107 | code: 'let foo = true,bar = {foo: foo, set baz(v) {}}', 108 | options: [{set: true}], 109 | errors: [{ 110 | message: 'Expected shorthand for `foo`.', 111 | type: 'Property' 112 | }] 113 | } 114 | ] 115 | }); 116 | -------------------------------------------------------------------------------- /test/lib/js/rules/use-standard-promise.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for use-standard-promise 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/use-standard-promise'); 12 | var RuleTester = require('eslint').RuleTester; 13 | 14 | //------------------------------------------------------------------------------ 15 | // Tests 16 | //------------------------------------------------------------------------------ 17 | 18 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 19 | 20 | ruleTester.run('use-standard-promise', rule, { 21 | valid: [ 22 | 'let defer = defer();', 23 | 'let defer = new Deferred();', 24 | 'let defer = window.jQuery.Deferred();', 25 | 'let p = Promise.all([p1, p2, p3]);', 26 | 'let p = Promise["all"]([p1, p2, p3]);', 27 | 'let p = Promise.race([p1, p2, p3]);', 28 | 'let p = promise["race"]([p1, p2, p3]);', 29 | 'let p = promise.resolve();', 30 | 'let p = promise.reject();', 31 | 'let p = promise.then(resolve);', 32 | 'let p = promise.catch(reason);', 33 | { 34 | code: 'let p = Promise.any([p1, p2, p3]);', 35 | options: [{any: true}] 36 | } 37 | ], 38 | invalid: [ 39 | { 40 | code: 'promise.done();', 41 | errors: [ 42 | { 43 | line: 1, 44 | type: 'Identifier', 45 | message: 'Expected to use standard Promise APIs.' 46 | } 47 | ] 48 | }, 49 | { 50 | code: 'promise.finally(callback);', 51 | errors: [ 52 | { 53 | line: 1, 54 | type: 'Identifier', 55 | message: 'Expected to use standard Promise APIs.' 56 | } 57 | ] 58 | }, 59 | { 60 | code: 'let p = Promise.any([p1, p2, p3]);', 61 | errors: [ 62 | { 63 | line: 1, 64 | type: 'Identifier', 65 | message: 'Expected to use standard Promise APIs.' 66 | } 67 | ] 68 | }, 69 | { 70 | code: 'let p = Promise["any"]([p1, p2, p3]);', 71 | errors: [ 72 | { 73 | line: 1, 74 | type: 'Literal', 75 | message: 'Expected to use standard Promise APIs.' 76 | } 77 | ] 78 | }, 79 | { 80 | code: 'let defer = Q.defer();', 81 | errors: [ 82 | { 83 | line: 1, 84 | type: 'Identifier', 85 | message: 'Expected to use standard Promise APIs.' 86 | } 87 | ] 88 | }, 89 | { 90 | code: 'let defer = $.Deferred();', 91 | errors: [ 92 | { 93 | line: 1, 94 | type: 'Identifier', 95 | message: 'Expected to use standard Promise APIs.' 96 | } 97 | ] 98 | }, 99 | { 100 | code: 'let defer = new jQuery.Deferred();', 101 | errors: [ 102 | { 103 | line: 1, 104 | type: 'Identifier', 105 | message: 'Expected to use standard Promise APIs.' 106 | } 107 | ] 108 | } 109 | ] 110 | }); 111 | -------------------------------------------------------------------------------- /test/lib/js/rules/valid-amd-id.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check AMD module id. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/valid-amd-id'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('valid-amd-id', rule, { 20 | 21 | valid: [ 22 | 'requirejs.define(function (require) {})', 23 | 'define(function (require) {})', 24 | 'define({})', 25 | 'define([])', 26 | 'define(["require"], function (require) {})', 27 | 'define("foo", {})', 28 | 'define("foo", function (require) {})', 29 | 'define("foo/bar", function (require) {})', 30 | 'define("foo-bar", function (require) {})', 31 | 'define("foo-bar/baz", function (require) {})', 32 | 'define("foo-bar/bla-bla/99b", function (require) {})', 33 | 'define("0", function (require) {})', 34 | 'define("0/1", function (require) {})' 35 | ], 36 | 37 | invalid: [ 38 | { 39 | code: 'define("$foo", {})', 40 | errors: [{ 41 | message: 'Unexpected id of AMD module.', 42 | type: 'CallExpression' 43 | }] 44 | }, 45 | { 46 | code: 'define("/foo", {})', 47 | errors: [{ 48 | message: 'Unexpected id of AMD module.', 49 | type: 'CallExpression' 50 | }] 51 | }, 52 | { 53 | code: 'define("@foo", {})', 54 | errors: [{ 55 | message: 'Unexpected id of AMD module.', 56 | type: 'CallExpression' 57 | }] 58 | }, 59 | { 60 | code: 'define("#foo", {})', 61 | errors: [{ 62 | message: 'Unexpected id of AMD module.', 63 | type: 'CallExpression' 64 | }] 65 | }, 66 | { 67 | code: 'define("foo/bar%", {})', 68 | errors: [{ 69 | message: 'Unexpected id of AMD module.', 70 | type: 'CallExpression' 71 | }] 72 | }, 73 | { 74 | code: 'define("(foo)/[bar]", {})', 75 | errors: [{ 76 | message: 'Unexpected id of AMD module.', 77 | type: 'CallExpression' 78 | }] 79 | }, 80 | { 81 | code: 'var foo = true;define(foo, function (require) {})', 82 | errors: [{ 83 | message: 'Unexpected id of AMD module.', 84 | type: 'CallExpression' 85 | }] 86 | } 87 | ] 88 | }); 89 | -------------------------------------------------------------------------------- /test/lib/js/rules/valid-constructor.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for valid-constructor. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/valid-constructor'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('valid-constructor', rule, { 20 | 21 | valid: [ 22 | 'foo = {}', 23 | 'foo.bar = {}', 24 | { 25 | code: [ 26 | 'var F = new Function();', 27 | 'F.prototype = superClass.prototype;', 28 | 'subClass.prototype = new F();', 29 | 'subClass.prototype.constructor = subClass;' 30 | ].join('\n') 31 | }, 32 | { 33 | code: [ 34 | 'var protos = {', 35 | ' constructor: Animal,', 36 | ' jump: function () {', 37 | ' }', 38 | '};', 39 | 'Animal.prototype = protos;' 40 | ].join('\n') 41 | }, 42 | { 43 | code: [ 44 | 'Animal.prototype = protos;' 45 | ].join('\n') 46 | }, 47 | { 48 | code: [ 49 | 'Animal.prototype = {', 50 | ' constructor: Animal,', 51 | ' jump: function () {', 52 | ' }', 53 | '};' 54 | ].join('\n') 55 | } 56 | ], 57 | 58 | invalid: [ 59 | { 60 | code: [ 61 | 'var F = new Function();', 62 | 'F.prototype = superClass.prototype;', 63 | 'subClass.prototype = new F();' 64 | ].join('\n'), 65 | errors: [{ 66 | message: 'Expected to fix up `constructor` after override `prototype`.baidu110', 67 | type: 'NewExpression' 68 | }] 69 | }, 70 | { 71 | code: [ 72 | 'Animal.prototype = {', 73 | ' jump: function () {', 74 | ' }', 75 | '};' 76 | ].join('\n'), 77 | errors: [{ 78 | message: 'Expected to fix up `constructor` after override `prototype`.baidu111', 79 | type: 'ObjectExpression' 80 | }] 81 | } 82 | ] 83 | }); 84 | -------------------------------------------------------------------------------- /test/lib/js/rules/valid-super.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Check `Super` callings. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/valid-super'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('valid-super', rule, { 20 | 21 | valid: [ 22 | 'let foo = {bar() {}}', 23 | 'class Foo extends Bar {constructor() {super()}}', 24 | 'class Foo extends Bar {bar() {super.bar()}}', 25 | 'class Foo extends Bar {bar() {super.baz()}}', 26 | 'let bar = true;class Foo extends Bar {[bar]() {super.baz()}}' 27 | ], 28 | 29 | invalid: [ 30 | { 31 | code: 'class Foo {bar() {super()}}', 32 | errors: [{ 33 | message: 'Invalid `super`.', 34 | type: 'CallExpression' 35 | }] 36 | }, 37 | { 38 | code: 'let bar = true;class Foo {[bar]() {super.bar()}}', 39 | errors: [{ 40 | message: 'Invalid `super`.', 41 | type: 'CallExpression' 42 | }] 43 | }, 44 | { 45 | code: 'class Foo extends null {bar() {super()}}', 46 | errors: [{ 47 | message: 'Invalid `super`.', 48 | type: 'CallExpression' 49 | }] 50 | }, 51 | { 52 | code: 'class Foo extends Bar {bar() {super()}}', 53 | errors: [{ 54 | message: 'Expected `super.bar` but found `super`.', 55 | type: 'CallExpression' 56 | }] 57 | } 58 | ] 59 | }); 60 | -------------------------------------------------------------------------------- /test/lib/js/rules/valid-var-jsdoc.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tests for valid-var-jsdoc. 3 | * @author chris 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Requirements 9 | //------------------------------------------------------------------------------ 10 | 11 | var rule = require('../../../../lib/js/rules/valid-var-jsdoc'); 12 | var RuleTester = require('eslint').RuleTester; 13 | //------------------------------------------------------------------------------ 14 | // Tests 15 | //------------------------------------------------------------------------------ 16 | 17 | var ruleTester = new RuleTester({parser: 'babel-eslint'}); 18 | 19 | ruleTester.run('valid-var-jsdoc', rule, { 20 | 21 | valid: [ 22 | 'var foo;', 23 | 'var [foo] = bar;', 24 | 'const foo = 1;', 25 | 'var foo = 1;', 26 | 'const FOO = 1;', 27 | 'const FOO_BAR = 1;', 28 | 'const F3_BAR = 1;', 29 | 'var isFoo = true;', 30 | 'var hasFoo = false;', 31 | '/**\n * isFoo\n * @type {boolean}\n */\nvar isFoo = true;', 32 | '/**\n * hasFoo\n * @type {boolean}\n */\nvar hasFoo = false;', 33 | 'var FOO = 1;', 34 | 'var Foo = {BAR: 1, BAZ: 2}', 35 | 'var Foo = function () {}', 36 | '/**\n * foo\n * @const {number}\n */\nconst FOO = 1;', 37 | '/**\n * foo\n * @namespace\n */\nvar foo = {};', 38 | '/**\n * fooBar\n * @namespace\n */\nvar fooBar = {};', 39 | '/**\n * Foo\n * @enum {number}\n */\nconst Foo = {BAR: 1, BAZ: 2};', 40 | 41 | // more then one blank line before statement 42 | '/**\n * foo\n * @const\n */\n\nconst F2_B_OOBAR = 1;', 43 | 44 | // error from valid-jsdoc, not valid-var-jsdoc 45 | '/**\n * foo\n * @const\n * @type {number\n */\nconst FOO = 1;' 46 | ], 47 | 48 | invalid: [ 49 | { 50 | code: '/**\n * Validate result\n * @type {boolean}\n */\nvar valid = validate()', 51 | errors: [{ 52 | message: 'Expected boolean variables with `is` or `has` prefix.baidu036', 53 | type: 'Identifier' 54 | }] 55 | }, 56 | { 57 | code: '/**\n * Foo\n * @enum {number}\n */\nvar foo = {BAR: 1, BAZ: 2}', 58 | errors: [{ 59 | message: 'Enumerable variables should be named as `Pascal`.baidu031', 60 | type: 'Identifier' 61 | }] 62 | }, 63 | { 64 | code: 'var Foo = {BAR: 1, baz: 2}', 65 | errors: [{ 66 | message: 'Property key of enum object should be named as constant variable.baidu031', 67 | type: 'Property' 68 | }] 69 | }, 70 | { 71 | code: '/**\n * foo\n * @namespace\n */\nvar Foo = {};', 72 | errors: [{ 73 | message: 'Namespace should be named as `Camel`.baidu032', 74 | type: 'Identifier' 75 | }] 76 | }, 77 | { 78 | code: '/**\n * foo\n * @const {number}\n */\nconst foo = 1;', 79 | errors: [{ 80 | message: 'Identifier `foo` should be only uppercase and underscore.baidu026', 81 | type: 'Identifier' 82 | }] 83 | }, 84 | { 85 | code: '/**\n * foo\n * @const\n */\nconst F2_B_OOBAR = 1;', 86 | errors: [{ 87 | message: 'Constant variables should be tagged with description, @const and @type.baidu060', 88 | type: 'Identifier' 89 | }] 90 | } 91 | ] 92 | }); 93 | -------------------------------------------------------------------------------- /test/lib/less/checker.spec.js: -------------------------------------------------------------------------------- 1 | var File = require('vinyl'); 2 | 3 | var checker = require('../../../lib/less/checker'); 4 | var cli = require('../../../lib/cli'); 5 | 6 | 7 | describe('checker', function () { 8 | 9 | 10 | it('options', function () { 11 | var options = checker.options; 12 | 13 | expect(options.name).toBe('lesslint'); 14 | expect(options.type).toBe('css'); 15 | expect(options.suffix).toBe('less'); 16 | 17 | }); 18 | 19 | it('isValid', function () { 20 | var invalidFiles = [ 21 | new File({contents: Buffer.from(''), path: 'test/a.styl'}), 22 | new File({contents: Buffer.from(''), path: 'test/b.scss'}), 23 | new File({contents: Buffer.from(''), path: 'test/b.sass'}), 24 | new File({contents: Buffer.from(''), path: 'test/baz.css'}) 25 | ]; 26 | 27 | var hasValid = invalidFiles.some(function (file) { 28 | return checker.isValid(file); 29 | }); 30 | 31 | expect(hasValid).toBeFalsy(); 32 | 33 | var validFiles = [ 34 | new File({contents: Buffer.from(''), path: 'test/a.m.less'}), 35 | new File({contents: Buffer.from(''), path: 'test/b.min.less'}), 36 | new File({contents: Buffer.from(''), path: 'test/c.less'}) 37 | ]; 38 | 39 | var hasInvalid = validFiles.some(function (file) { 40 | return !checker.isValid(file); 41 | }); 42 | 43 | expect(hasInvalid).toBeFalsy(); 44 | 45 | }); 46 | 47 | it('check with promise', function (done) { 48 | 49 | var options = cli.getOptions(); 50 | 51 | checker 52 | .check('\np {\n height: 0px;\n}', 'path/to/file.less', options) 53 | .then(function (errors) { 54 | expect(errors.length).toBe(1); 55 | 56 | var error = errors[0]; 57 | expect(error.line).toBe(3); 58 | expect(error.rule).toBe('zero-unit'); 59 | done(); 60 | }); 61 | 62 | }); 63 | 64 | it('check with invalid content', function (done) { 65 | 66 | var options = cli.getOptions(); 67 | 68 | checker 69 | .check('body{', 'path/to/file.less', options) 70 | .then(function (errors) { 71 | expect(errors.length).toBe(1); 72 | expect(errors[0].code).toBe('999'); 73 | done(); 74 | }); 75 | 76 | }); 77 | 78 | }); 79 | -------------------------------------------------------------------------------- /test/lib/log.spec.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | 3 | var log = require('../../lib/log'); 4 | var util = require('../../lib/util'); 5 | 6 | describe('log', function () { 7 | 8 | it('color false', function () { 9 | var logger = log(false); 10 | 11 | expect(logger).not.toBeNull(); 12 | 13 | var consoleLog = console.log; 14 | spyOn(console, 'log'); 15 | ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(function (name, i) { 16 | expect(typeof logger[name]).toBe('function'); 17 | 18 | logger[name](); 19 | expect(console.log).toHaveBeenCalled(); 20 | expect(console.log.calls.count()).toBe(i * 2 + 1); 21 | expect(console.log.calls.mostRecent().args[0]).toBe(); 22 | 23 | logger[name]('foo'); 24 | expect(console.log.calls.count()).toBe((i + 1) * 2); 25 | expect(console.log.calls.mostRecent().args[0]) 26 | .toBe('fecs [' + util.fixWidth(name.toUpperCase(), 5) + '] foo'); 27 | }); 28 | console.log = consoleLog; 29 | }); 30 | 31 | it('color true', function () { 32 | var logger = log(true); 33 | var fns = [ 34 | {name: 'trace', color: chalk.grey, level: 0}, 35 | {name: 'debug', color: chalk.grey, level: 1}, 36 | {name: 'info', color: chalk.green, level: 2}, 37 | {name: 'warn', color: chalk.yellow, level: 3}, 38 | {name: 'error', color: chalk.red, level: 4}, 39 | {name: 'fatal', color: chalk.red, level: 5} 40 | ]; 41 | 42 | var consoleLog = console.log; 43 | spyOn(console, 'log'); 44 | ['trace', 'debug', 'info', 'warn', 'error', 'fatal'].forEach(function (name, i) { 45 | expect(typeof logger[name]).toBe('function'); 46 | expect(fns[i].name).toBe(name); 47 | 48 | logger[name](); 49 | expect(console.log).toHaveBeenCalled(); 50 | expect(console.log.calls.count()).toBe(i * 2 + 1); 51 | expect(console.log.calls.mostRecent().args[0]).toBe(); 52 | 53 | logger[name]('foo'); 54 | expect(console.log.calls.count()).toBe((i + 1) * 2); 55 | expect(console.log.calls.mostRecent().args[0]) 56 | .toBe('fecs ' + fns[i].color(util.fixWidth(name.toUpperCase(), 5)) + ' foo'); 57 | }); 58 | console.log = consoleLog; 59 | }); 60 | 61 | 62 | }); 63 | -------------------------------------------------------------------------------- /test/lib/version.spec.js: -------------------------------------------------------------------------------- 1 | var mock = require('mock-fs'); 2 | var version = require('../../lib/version'); 3 | 4 | describe('version of module', function () { 5 | 6 | afterEach(function () { 7 | mock.restore(); 8 | }); 9 | 10 | it('not specify', function () { 11 | var versions = version(); 12 | 13 | expect(Object.keys(versions)).toEqual([]); 14 | }); 15 | 16 | it('unknown module', function () { 17 | var versions = version(['foobar']); 18 | 19 | expect(Object.keys(versions)).toEqual(['foobar']); 20 | expect(versions.foobar).toBe('N/A'); 21 | 22 | }); 23 | 24 | it('detect eslint & babel-eslint', function () { 25 | var versions = version(['eslint', 'babel-eslint']); 26 | 27 | expect(Object.keys(versions)).toEqual(['eslint', 'babel-eslint']); 28 | 29 | var eslint = require('eslint/package.json'); 30 | expect(versions.eslint).toBe(eslint.version); 31 | 32 | var babelESlint = require('babel-eslint/package.json'); 33 | expect(versions['babel-eslint']).toBe(babelESlint.version); 34 | 35 | }); 36 | }); 37 | --------------------------------------------------------------------------------