├── .gitignore ├── .npmignore ├── tests ├── fixtures │ ├── esnext-parse-error │ │ ├── bad-file.js │ │ ├── good-file.js │ │ ├── another-bad-file.js │ │ └── .jscsrc │ ├── issue-found │ │ ├── .jscsrc │ │ └── index.js │ ├── esnext │ │ ├── .jscsrc │ │ └── index.js │ ├── no-issues-found │ │ ├── .jscsrc │ │ └── index.js │ ├── no-jscsrc │ │ └── index.js │ ├── code-config │ │ └── index.js │ ├── excludes │ │ ├── excluded.js │ │ ├── glob-match-1.js │ │ ├── glob-match-2.js │ │ ├── included.js │ │ └── .jscsrc │ └── only-jscsrc │ │ └── .jscsrc-with-unique-name └── index.js ├── .travis.yml ├── CHANGELOG.md ├── appveyor.yml ├── LICENSE ├── package.json ├── ember-addon-main.js ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /tests 2 | .gitignore 3 | .travis.yml 4 | appveyor.yml 5 | -------------------------------------------------------------------------------- /tests/fixtures/esnext-parse-error/bad-file.js: -------------------------------------------------------------------------------- 1 | module foo from 'bar'; 2 | -------------------------------------------------------------------------------- /tests/fixtures/esnext-parse-error/good-file.js: -------------------------------------------------------------------------------- 1 | import foo from 'bar'; 2 | -------------------------------------------------------------------------------- /tests/fixtures/issue-found/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "validateIndentation": 2 3 | } -------------------------------------------------------------------------------- /tests/fixtures/esnext/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "requireLineFeedAtFileEnd": true 3 | } -------------------------------------------------------------------------------- /tests/fixtures/issue-found/index.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/no-issues-found/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "validateIndentation": 2 3 | } -------------------------------------------------------------------------------- /tests/fixtures/no-jscsrc/index.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/code-config/index.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return null; 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/esnext-parse-error/another-bad-file.js: -------------------------------------------------------------------------------- 1 | module baz from 'qux'; 2 | -------------------------------------------------------------------------------- /tests/fixtures/excludes/excluded.js: -------------------------------------------------------------------------------- 1 | function excluded() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/excludes/glob-match-1.js: -------------------------------------------------------------------------------- 1 | function excluded() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/excludes/glob-match-2.js: -------------------------------------------------------------------------------- 1 | function excluded() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/excludes/included.js: -------------------------------------------------------------------------------- 1 | function included() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/no-issues-found/index.js: -------------------------------------------------------------------------------- 1 | function test() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/esnext/index.js: -------------------------------------------------------------------------------- 1 | export default function test() { 2 | return null; 3 | } -------------------------------------------------------------------------------- /tests/fixtures/esnext-parse-error/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": ["*bad-file.js"] 3 | } 4 | -------------------------------------------------------------------------------- /tests/fixtures/only-jscsrc/.jscsrc-with-unique-name: -------------------------------------------------------------------------------- 1 | { 2 | "validateIndentation": 2 3 | } -------------------------------------------------------------------------------- /tests/fixtures/excludes/.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "excludeFiles": ["excluded.js", "glob-match*.js"], 3 | "validateIndentation": 2 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | script: npm run-script cover 6 | 7 | after_script: 8 | - cat coverage/lcov.info | node_modules/coveralls/bin/coveralls.js 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### v2.0.0 4 | 5 | - [#77](https://github.com/kellyselden/broccoli-jscs/pull/77) Updating JSCS to final major release ver ^3.0.0 [@alexdiliberto](https://github.com/alexdiliberto) 6 | 7 | ### v1.4.0 8 | 9 | - [#69](https://github.com/kellyselden/broccoli-jscs/pull/69) Use project.generateTestFile() if available [@Turbo87](https://github.com/Turbo87) 10 | 11 | ### v1.3.1 12 | 13 | - [#66](https://github.com/kellyselden/broccoli-jscs/pull/66) Fix .jscsrc path issues [@Turbo87](https://github.com/Turbo87) 14 | 15 | ### v1.3.0 16 | 17 | - [#59](https://github.com/kellyselden/broccoli-jscs/pull/59) Add Mocha Support [@Turbo87](https://github.com/Turbo87) 18 | - [#58](https://github.com/kellyselden/broccoli-jscs/pull/58) Fix broken test suite [@Turbo87](https://github.com/Turbo87) 19 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # http://www.appveyor.com/docs/appveyor-yml 2 | 3 | # Fix line endings in Windows. (runs before repo cloning) 4 | init: 5 | - git config --global core.autocrlf true 6 | 7 | # Test against these versions of Node.js. 8 | environment: 9 | matrix: 10 | - nodejs_version: "0.12" 11 | 12 | # Install scripts. (runs after repo cloning) 13 | install: 14 | # Get the latest stable version of Node 0.STABLE.latest 15 | - ps: Install-Product node $env:nodejs_version 16 | # Typical npm stuff. 17 | - md C:\nc 18 | - npm install -g npm@latest 19 | # Workaround https://github.com/npm/npm/wiki/Troubleshooting#upgrading-on-windows 20 | - set PATH=%APPDATA%\npm;%PATH% 21 | - npm config set cache C:\nc 22 | - npm version 23 | - npm install 24 | 25 | # Post-install test scripts. 26 | test_script: 27 | # Output useful info for debugging. 28 | - npm version 29 | - cmd: npm test 30 | 31 | # Don't actually build. 32 | build: off 33 | 34 | # Set build version format here instead of in the admin panel. 35 | version: "{build}" 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Kelly Selden 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "broccoli-jscs", 3 | "version": "2.0.0", 4 | "description": "Broccoli plugin for jscs", 5 | "main": "index.js", 6 | "dependencies": { 7 | "broccoli-merge-trees": "^1.1.1", 8 | "broccoli-persistent-filter": "^1.0.0", 9 | "broccoli-funnel": "^1.0.1", 10 | "ember-cli-version-checker": "^1.0.2", 11 | "findup-sync": "^0.4.0", 12 | "js-string-escape": "^1.0.0", 13 | "jscs": "^3.0.0", 14 | "json-stable-stringify": "^1.0.0", 15 | "minimatch": "^3.0.0" 16 | }, 17 | "devDependencies": { 18 | "broccoli": "^0.16.9", 19 | "coveralls": "^2.11.2", 20 | "expect.js": "^0.3.1", 21 | "istanbul": "^0.4.2", 22 | "mocha": "^3.0.0" 23 | }, 24 | "scripts": { 25 | "test": "mocha tests", 26 | "cover": "istanbul cover node_modules/mocha/bin/_mocha tests" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "https://github.com/kellyselden/broccoli-jscs.git" 31 | }, 32 | "keywords": [ 33 | "broccoli-plugin", 34 | "jscs", 35 | "javascript", 36 | "ember-addon" 37 | ], 38 | "author": "Kelly Selden", 39 | "license": "MIT", 40 | "bugs": { 41 | "url": "https://github.com/kellyselden/broccoli-jscs/issues" 42 | }, 43 | "homepage": "https://github.com/kellyselden/broccoli-jscs", 44 | "ember-addon": { 45 | "main": "ember-addon-main.js" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ember-addon-main.js: -------------------------------------------------------------------------------- 1 | var JSCSFilter = require('broccoli-jscs'); 2 | var mergeTrees = require('broccoli-merge-trees'); 3 | var Funnel = require('broccoli-funnel'); 4 | var checker = require('ember-cli-version-checker'); 5 | var jsStringEscape = require('js-string-escape'); 6 | 7 | module.exports = { 8 | name: 'broccoli-jscs', 9 | 10 | shouldSetupRegistryInIncluded: function() { 11 | return !checker.isAbove(this, '0.1.10'); 12 | }, 13 | 14 | lintTree: function(type, tree) { 15 | var jscsOptions = this.app.options.jscsOptions || {}; 16 | 17 | if (!jscsOptions.enabled) { 18 | return tree; 19 | } 20 | 21 | var project = this.project; 22 | if (!jscsOptions.testGenerator && project.generateTestFile) { 23 | jscsOptions.testGenerator = function(relativePath, errors) { 24 | if (errors) { 25 | errors = jsStringEscape('\n' + errors); 26 | } 27 | 28 | return project.generateTestFile('JSCS - ' + relativePath, [{ 29 | name: 'should pass jscs', 30 | passed: !errors, 31 | errorMessage: relativePath + ' should pass jscs.' + errors 32 | }]); 33 | } 34 | } 35 | 36 | var jscsTree = new JSCSFilter(tree, jscsOptions); 37 | 38 | if (jscsTree.bypass || jscsTree.disableTestGenerator) { 39 | return tree; 40 | } 41 | 42 | return jscsTree; 43 | }, 44 | 45 | included: function(app) { 46 | var addonContext = this; 47 | this.app = app; 48 | this._super.included.apply(this, arguments); 49 | 50 | if (app.tests && this.shouldSetupRegistryInIncluded()) { 51 | app.registry.add('js', { 52 | name: 'broccoli-jscs', 53 | ext: 'js', 54 | toTree: function(tree, inputPath, outputPath, options) { 55 | var jscsTree = addonContext.lintTree('unknown-type', tree); 56 | 57 | // I can't get ember-cli@0.1.10 to run to test this 58 | if (jscsTree === tree) { 59 | return tree; 60 | } 61 | 62 | return mergeTrees([ 63 | tree, 64 | new Funnel(jscsTree, { 65 | srcDir: '/', 66 | destDir: outputPath + '/tests/' 67 | }) 68 | ], { overwrite: true }); 69 | } 70 | }); 71 | } 72 | } 73 | }; 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | broccoli-jscs 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/kellyselden/broccoli-jscs.svg?branch=master)](https://travis-ci.org/kellyselden/broccoli-jscs) 5 | [![Build status](https://ci.appveyor.com/api/projects/status/w2yk4p8c1mtu1c96/branch/master?svg=true)](https://ci.appveyor.com/project/kellyselden/broccoli-jscs/branch/master) 6 | [![Code Climate](https://codeclimate.com/github/kellyselden/broccoli-jscs/badges/gpa.svg)](https://codeclimate.com/github/kellyselden/broccoli-jscs) 7 | [![Coverage Status](https://coveralls.io/repos/kellyselden/broccoli-jscs/badge.svg?branch=master)](https://coveralls.io/r/kellyselden/broccoli-jscs?branch=master) 8 | 9 | [![npm version](https://badge.fury.io/js/broccoli-jscs.svg)](http://badge.fury.io/js/broccoli-jscs) 10 | [![dependencies Status](https://david-dm.org/kellyselden/broccoli-jscs/status.svg)](https://david-dm.org/kellyselden/broccoli-jscs) 11 | [![devDependencies Status](https://david-dm.org/kellyselden/broccoli-jscs/dev-status.svg)](https://david-dm.org/kellyselden/broccoli-jscs?type=dev) 12 | 13 | ## JSCS was merged into ESLint, so this project is no longer being worked on. 14 | 15 | Broccoli plugin for [jscs](https://github.com/jscs-dev/node-jscs) 16 | 17 | ## Usage 18 | 19 | ```js 20 | var jscs = require('broccoli-jscs'); 21 | 22 | // assuming someTree is a built up tree 23 | var tree = jscs(someTree); 24 | // or 25 | var tree = jscs('folderName'); 26 | ``` 27 | 28 | As a Ember CLI Addon, simply `npm install --save-dev broccoli-jscs` and supply the options you would like: 29 | 30 | ```js 31 | var app = new EmberApp({ 32 | jscsOptions: { 33 | configPath: '/my/path/.jscsrc', 34 | enabled: true, 35 | disableTestGenerator: false 36 | } 37 | }); 38 | ``` 39 | 40 | You can also supply the options in the `.jscsrc` file if you wish: 41 | 42 | ```js 43 | { 44 | "excludeFiles": ["path/to/file"], 45 | // more rules... 46 | } 47 | ``` 48 | 49 | ## Documentation 50 | 51 | ### `jscs(inputTree, options)` 52 | 53 | --- 54 | 55 | `options.configPath` *{String}* 56 | 57 | Will look in the root of the provided tree for a `.jscsrc`. Use this option if you would like to use a `.jscsrc` 58 | file from a different path. 59 | 60 | Default: **'.jscsrc'** 61 | 62 | --- 63 | 64 | `options.config` *{Object}* 65 | 66 | Specify the options for JSCS programatically, accepts the same kind of JSON 67 | style object as you would provide in the `.jscsrc` file. This option will be 68 | overriden when a `.jscsrc` file is in the root or when `options.configPath` is 69 | set. 70 | 71 | Default: **'{}'** 72 | 73 | --- 74 | 75 | `options.enabled` *{true|false}* 76 | 77 | This will eliminate processing altogether. 78 | 79 | Default: **false** 80 | 81 | --- 82 | 83 | `options.logError` *{Function}* 84 | 85 | The function used to log to console. You can provide a custom function to log anywhere, perhaps a file or web service. 86 | 87 | The function receives the following argument: 88 | 89 | * `message` - A generated string of errors found. 90 | 91 | --- 92 | 93 | `options.disableTestGenerator` *{true|false}* 94 | 95 | If `true` tests will not be generated. 96 | 97 | Default: **false** 98 | 99 | --- 100 | 101 | `options.testFramework` *{String}* 102 | 103 | This setting determines what kind of tests are generated. If a custom `testGenerator` is set `testFramework` will be ignored. 104 | 105 | This setting currently supports the following test frameworks: 106 | 107 | * `qunit` *(default)* 108 | * `mocha` 109 | 110 | --- 111 | 112 | `options.testGenerator` *{Function}* 113 | 114 | The function used to generate test modules. You can provide a custom function for your client side testing framework of choice. 115 | 116 | The function receives the following arguments: 117 | 118 | * `relativePath` - The relative path to the file being tested. 119 | * `errors` - A generated string of errors found. 120 | 121 | Default generates QUnit style tests: 122 | 123 | ```js 124 | var path = require('path'); 125 | 126 | function(relativePath, errors) { 127 | return "module('JSCS - " + path.dirname(relativePath) + "');\n" + 128 | "test('" + relativePath + " should pass jscs', function() {\n" + 129 | " ok(" + !errors + ", '" + relativePath + " should pass jscs." + errors + "');\n" + 130 | "});\n"; 131 | }; 132 | ``` 133 | 134 | --- 135 | 136 | `options.excludeFiles` *{String}* 137 | 138 | Exclude files or directories from processing. Supports globbing. Example: 139 | 140 | ```js 141 | var app = new EmberApp({ 142 | jscsOptions: { 143 | excludeFiles: ['ember-runtime/ext/rsvp.js'] 144 | // or 145 | excludeFiles: ['webclient/tests/**'] 146 | } 147 | }); 148 | ``` 149 | 150 | ## Tests 151 | 152 | Running the tests: 153 | 154 | ``` 155 | npm install 156 | npm test 157 | ``` 158 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Filter = require('broccoli-persistent-filter'); 4 | var jscs = require('jscs'); 5 | var config = require('jscs/lib/cli-config'); 6 | var path = require('path'); 7 | var minimatch = require('minimatch'); 8 | var stringify = require('json-stable-stringify'); 9 | var crypto = require('crypto'); 10 | var findup = require('findup-sync'); 11 | 12 | var jsStringEscape = require('js-string-escape'); 13 | 14 | function _makeDictionary() { 15 | var cache = Object.create(null); 16 | cache['_dict'] = null; 17 | delete cache['_dict']; 18 | return cache; 19 | } 20 | 21 | var JSCSFilter = function(inputTree, _options) { 22 | if (!(this instanceof JSCSFilter)) { return new JSCSFilter(inputTree, _options); } 23 | 24 | var options = _options || {}; 25 | if (!options.hasOwnProperty('persist')) { 26 | options.persist = true; 27 | } 28 | 29 | Filter.call(this, inputTree, options); 30 | 31 | this.options = options; 32 | this.inputTree = inputTree; 33 | this.enabled = true; 34 | 35 | this._excludeFileCache = _makeDictionary(); 36 | 37 | for (var key in options) { 38 | if (options.hasOwnProperty(key)) { 39 | this[key] = options[key]; 40 | } 41 | } 42 | 43 | }; 44 | 45 | JSCSFilter.prototype = Object.create(Filter.prototype); 46 | JSCSFilter.prototype.constructor = JSCSFilter; 47 | JSCSFilter.prototype.extensions = ['js']; 48 | JSCSFilter.prototype.targetExtension = 'js'; 49 | 50 | JSCSFilter.prototype.baseDir = function() { 51 | return __dirname; 52 | }; 53 | 54 | JSCSFilter.prototype.build = function () { 55 | this.configure(); 56 | return Filter.prototype.build.call(this); 57 | }; 58 | 59 | JSCSFilter.prototype.configure = function () { 60 | if (this.enabled) { 61 | this.rules = this.loadRules(this.inputPaths[0]); 62 | 63 | this.bypass = Object.keys(this.rules).length === 0; 64 | if (!this.bypass) { 65 | var checker = new jscs(); 66 | checker.registerDefaultRules(); 67 | checker.configure(this.rules); 68 | this.checker = checker; 69 | 70 | if (!this.disableTestGenerator) { 71 | this.targetExtension = 'jscs-test.js'; 72 | } 73 | } 74 | } 75 | }; 76 | 77 | JSCSFilter.prototype.loadRules = function (rootPath) { 78 | return config.load(this.configPath || this.findJSCSRC(rootPath)) || this.config || {}; 79 | }; 80 | 81 | JSCSFilter.prototype.findJSCSRC = function (rootPath) { 82 | return findup('.jscsrc', {cwd: rootPath, nocase: true}); 83 | }; 84 | 85 | JSCSFilter.prototype.processString = function(content, relativePath) { 86 | if (this.enabled && !this.bypass) { 87 | if (this.shouldExcludeFile(relativePath)) { 88 | return this.disableTestGenerator ? content : ''; 89 | } 90 | 91 | var errors = this.checker.checkString(content, relativePath); 92 | 93 | var errorText = this.processErrors(errors, true); 94 | if (errorText) { 95 | this.logError(errorText); 96 | } 97 | 98 | if (!this.disableTestGenerator) { 99 | errorText = this.processErrors(errors, false); 100 | return this.testGenerator(relativePath, errorText); 101 | } 102 | } 103 | 104 | return content; 105 | }; 106 | 107 | JSCSFilter.prototype.processErrors = function(errors, colorize) { 108 | return errors.getErrorList().map(function(error) { 109 | return errors.explainError(error, colorize); 110 | }).join('\n'); 111 | }; 112 | 113 | JSCSFilter.prototype.testGenerator = function(relativePath, errors) { 114 | if (errors) { 115 | errors = this.escapeErrorString('\n' + errors); 116 | } 117 | 118 | if (this.testFramework === 'mocha') { 119 | return "describe('JSCS - " + relativePath + "', function() {\n" + 120 | " it('should pass jscs', function() {\n" + 121 | " expect(" + !errors + ", '" + relativePath + " should pass jscs." + errors + "').to.be.ok;\n" + 122 | " });\n" + 123 | "});\n"; 124 | 125 | } else { 126 | return "module('JSCS | " + path.dirname(relativePath) + "');\n" + 127 | "test('" + relativePath + " should pass jscs', function() {\n" + 128 | " ok(" + !errors + ", '" + relativePath + " should pass jscs." + errors + "');\n" + 129 | "});\n"; 130 | } 131 | }; 132 | 133 | JSCSFilter.prototype.logError = function(message) { 134 | console.log(message); 135 | }; 136 | 137 | JSCSFilter.prototype.escapeErrorString = jsStringEscape; 138 | 139 | JSCSFilter.prototype.shouldExcludeFile = function(relativePath) { 140 | if (this.rules.excludeFiles) { 141 | // The user specified an "excludeFiles" list. 142 | // Must pattern match or find a cache hit to determine if this relativePath is an actual JSCS exclusion. 143 | var excludeFileCache = this._excludeFileCache; 144 | 145 | if (excludeFileCache[relativePath] !== undefined) { 146 | // This relativePath is in the cache, so we've already run minimatch. 147 | return excludeFileCache[relativePath]; 148 | } 149 | 150 | var i, l, pattern; 151 | 152 | // This relativePath is NOT in the cache. Execute _matchesPattern(). 153 | for (i = 0, l = this.rules.excludeFiles.length; i < l; i++) { 154 | pattern = this.rules.excludeFiles[i]; 155 | if (this._matchesPattern(relativePath, pattern)) { 156 | // User has specified "excludeFiles" and this relativePath did match at least 1 exclusion. 157 | return excludeFileCache[relativePath] = true; 158 | } 159 | } 160 | 161 | // User has specified excludeFiles but this relativePath did NOT match any exclusions. 162 | excludeFileCache[relativePath] = false; 163 | } 164 | 165 | // The user has NOT specified an "excludeFiles" list. Continue processing like normal. 166 | return false; 167 | }; 168 | 169 | JSCSFilter.prototype._matchesPattern = function(relativePath, pattern) { 170 | return minimatch(relativePath, pattern); 171 | }; 172 | 173 | JSCSFilter.prototype.optionsHash = function() { 174 | if (!this._optionsHash) { 175 | this._optionsHash = crypto.createHash('md5') 176 | .update(stringify(this.options), 'utf8') 177 | .update(stringify(this.rules) || '', 'utf8') 178 | .update(this.testGenerator.toString(), 'utf8') 179 | .update(this.logError.toString(), 'utf8') 180 | .update(this.escapeErrorString.toString(), 'utf8') 181 | .digest('hex'); 182 | } 183 | 184 | return this._optionsHash; 185 | }; 186 | 187 | JSCSFilter.prototype.cacheKeyProcessString = function(string, relativePath) { 188 | return this.optionsHash() + Filter.prototype.cacheKeyProcessString.call(this, string, relativePath); 189 | }; 190 | 191 | module.exports = JSCSFilter; 192 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | var jscsTree = require('..'); 2 | var expect = require('expect.js'); 3 | var root = process.cwd(); 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var broccoli = require('broccoli'); 8 | 9 | var builder; 10 | 11 | describe('broccoli-jscs', function() { 12 | var loggerOutput; 13 | 14 | function readFile(path) { 15 | return fs.readFileSync(path, { encoding: 'utf8' }); 16 | } 17 | 18 | beforeEach(function() { 19 | loggerOutput = []; 20 | }); 21 | 22 | afterEach(function() { 23 | if (builder) { 24 | builder.cleanup(); 25 | } 26 | }); 27 | 28 | describe('jscsrc', function() { 29 | it('bypass if no jscsrc file is found', function() { 30 | var sourcePath = 'tests/fixtures/no-jscsrc'; 31 | var tree = new jscsTree(sourcePath, {}); 32 | 33 | builder = new broccoli.Builder(tree); 34 | return builder.build().then(function() { 35 | expect(tree.bypass).to.be.ok(); 36 | }); 37 | }); 38 | 39 | it('don\'t bypass if jscsrc file is found', function() { 40 | var sourcePath = 'tests/fixtures/issue-found'; 41 | 42 | var tree = new jscsTree(sourcePath); 43 | 44 | expect(tree.bypass).to.not.be.ok(); 45 | }); 46 | 47 | it('defaults to .jscsrc in source path folder', function() { 48 | var sourcePath = 'tests/fixtures/issue-found'; 49 | 50 | var tree = new jscsTree(sourcePath, { 51 | persist: false, 52 | logError: function(message) { loggerOutput.push(message); } 53 | }); 54 | 55 | builder = new broccoli.Builder(tree); 56 | return builder.build().then(function() { 57 | expect(loggerOutput.join('\n')).to.contain('Expected indentation of 2 characters'); 58 | }); 59 | }); 60 | 61 | it('uses the jscsrc as configuration', function() { 62 | var sourcePath = 'tests/fixtures/issue-found'; 63 | 64 | var tree = new jscsTree(sourcePath, { 65 | persist: false, 66 | configPath: path.join(sourcePath, '.jscsrc'), 67 | logError: function(message) { loggerOutput.push(message); } 68 | }); 69 | 70 | builder = new broccoli.Builder(tree); 71 | return builder.build().then(function() { 72 | expect(loggerOutput.join('\n')).to.contain('Expected indentation of 2 characters'); 73 | }); 74 | }); 75 | 76 | it('supply your own jscsrc file', function() { 77 | var sourcePath = 'tests/fixtures/no-jscsrc'; 78 | 79 | var tree = new jscsTree(sourcePath, { 80 | persist: false, 81 | configPath: 'tests/fixtures/only-jscsrc/.jscsrc-with-unique-name', 82 | logError: function(message) { loggerOutput.push(message); } 83 | }); 84 | 85 | builder = new broccoli.Builder(tree); 86 | return builder.build().then(function() { 87 | expect(loggerOutput.length).to.not.eql(0); 88 | }); 89 | }); 90 | 91 | it('supply your own in code config object', function() { 92 | var sourcePath = 'tests/fixtures/code-config'; 93 | 94 | var tree = new jscsTree(sourcePath, { 95 | persist: false, 96 | config: { "validateIndentation": 2 }, 97 | logError: function(message) { loggerOutput.push(message); } 98 | }); 99 | 100 | builder = new broccoli.Builder(tree); 101 | return builder.build().then(function() { 102 | expect(loggerOutput.length).to.not.eql(0); 103 | }); 104 | }); 105 | }); 106 | 107 | describe('options', function() { 108 | it('doesn\'t run if disabled', function() { 109 | var sourcePath = 'tests/fixtures/issue-found'; 110 | 111 | var tree = new jscsTree(sourcePath, { 112 | persist: false, 113 | enabled: false, 114 | logError: function(message) { loggerOutput.push(message); } 115 | }); 116 | 117 | builder = new broccoli.Builder(tree); 118 | return builder.build().then(function() { 119 | expect(loggerOutput.length).to.eql(0); 120 | }); 121 | }); 122 | 123 | it('runs with esnext enabled', function() { 124 | var sourcePath = 'tests/fixtures/esnext'; 125 | 126 | var tree = new jscsTree(sourcePath, { 127 | persist: false, 128 | esnext: true, 129 | logError: function(message) { loggerOutput.push(message); } 130 | }); 131 | 132 | builder = new broccoli.Builder(tree); 133 | return builder.build().then(function() { 134 | expect(loggerOutput.length).to.eql(1); 135 | }); 136 | }); 137 | }); 138 | 139 | describe('jscs', function() { 140 | it('passes all rules', function() { 141 | var sourcePath = 'tests/fixtures/no-issues-found'; 142 | 143 | var tree = new jscsTree(sourcePath, { 144 | logError: function(message) { loggerOutput.push(message); } 145 | }); 146 | 147 | builder = new broccoli.Builder(tree); 148 | return builder.build().then(function() { 149 | expect(loggerOutput.length).to.eql(0); 150 | }); 151 | }); 152 | 153 | it('finds an expected issue', function() { 154 | var sourcePath = 'tests/fixtures/issue-found'; 155 | 156 | var tree = new jscsTree(sourcePath, { 157 | persist: false, 158 | logError: function(message) { loggerOutput.push(message); } 159 | }); 160 | 161 | builder = new broccoli.Builder(tree); 162 | return builder.build().then(function() { 163 | expect(loggerOutput.join('\n')).to.match(/Expected indentation/); 164 | }); 165 | }); 166 | }); 167 | 168 | describe('testGenerator', function() { 169 | it('retains targetExtension when disableTestGenerator is true', function() { 170 | var sourcePath = 'tests/fixtures/no-issues-found'; 171 | 172 | var tree = jscsTree(sourcePath, { 173 | disableTestGenerator: true 174 | }); 175 | 176 | builder = new broccoli.Builder(tree); 177 | return builder.build().then(function(results) { 178 | expect(tree.targetExtension).to.eql('js'); 179 | }); 180 | }); 181 | 182 | it('sets targetExtension correctly when disableTestGenerator is false', function() { 183 | var sourcePath = 'tests/fixtures/no-issues-found'; 184 | 185 | var tree = jscsTree(sourcePath, {}); 186 | 187 | builder = new broccoli.Builder(tree); 188 | return builder.build().then(function(results) { 189 | expect(tree.targetExtension).to.eql('jscs-test.js'); 190 | }); 191 | }); 192 | 193 | it('does not generate tests if disableTestGenerator is set', function() { 194 | var sourcePath = 'tests/fixtures/no-issues-found'; 195 | 196 | var tree = jscsTree(sourcePath, { 197 | disableTestGenerator: true 198 | }); 199 | 200 | builder = new broccoli.Builder(tree); 201 | return builder.build().then(function(results) { 202 | var dir = results.directory; 203 | expect(readFile(dir + '/index.' + tree.targetExtension)).to.not.match(/ok\(true, 'index.js should pass jscs.'\);/); 204 | }); 205 | }); 206 | 207 | it('generates test files for jscs issues', function() { 208 | var sourcePath = 'tests/fixtures/no-issues-found'; 209 | 210 | var tree = jscsTree(sourcePath, {}); 211 | 212 | builder = new broccoli.Builder(tree); 213 | return builder.build().then(function(results) { 214 | var dir = results.directory; 215 | expect(readFile(dir + '/index.' + tree.targetExtension)).to.match(/ok\(true, 'index.js should pass jscs.'\);/); 216 | }); 217 | }); 218 | 219 | it('generates empty content for excluded files with test generation', function() { 220 | var sourcePath = 'tests/fixtures/esnext-parse-error'; 221 | 222 | var tree = jscsTree(sourcePath); 223 | 224 | builder = new broccoli.Builder(tree); 225 | return builder.build().then(function(results) { 226 | var dir = results.directory; 227 | expect(readFile(dir + '/bad-file.' + tree.targetExtension)).to.be(''); 228 | expect(readFile(dir + '/another-bad-file.' + tree.targetExtension)).to.be(''); 229 | expect(readFile(dir + '/good-file.' + tree.targetExtension)).to.match(/ok\(true, 'good-file.js should pass jscs.'\);/); 230 | }); 231 | }); 232 | 233 | it('generates original content for excluded files without test generation', function() { 234 | var sourcePath = 'tests/fixtures/esnext-parse-error'; 235 | 236 | var tree = jscsTree(sourcePath, { 237 | disableTestGenerator: true 238 | }); 239 | 240 | builder = new broccoli.Builder(tree); 241 | return builder.build().then(function(results) { 242 | var dir = results.directory; 243 | expect(readFile(dir + '/bad-file.' + tree.targetExtension)).to.be(readFile(sourcePath + '/bad-file.js')); 244 | expect(readFile(dir + '/another-bad-file.' + tree.targetExtension)).to.be(readFile(sourcePath + '/another-bad-file.js')); 245 | expect(readFile(dir + '/good-file.' + tree.targetExtension)).to.be(readFile(sourcePath + '/good-file.js')); 246 | }); 247 | }); 248 | 249 | it('generates test files with custom test generator', function() { 250 | var sourcePath = 'tests/fixtures/no-issues-found'; 251 | 252 | var tree = jscsTree(sourcePath, { 253 | testGenerator: function() { 254 | return 'foo'; 255 | } 256 | }); 257 | 258 | builder = new broccoli.Builder(tree); 259 | return builder.build().then(function(results) { 260 | var dir = results.directory; 261 | expect(readFile(dir + '/index.' + tree.targetExtension)).to.equal('foo'); 262 | }); 263 | }); 264 | 265 | it('generates test files for the mocha test framework', function() { 266 | var sourcePath = 'tests/fixtures/no-issues-found'; 267 | 268 | var tree = jscsTree(sourcePath, { 269 | testFramework: 'mocha' 270 | }); 271 | 272 | builder = new broccoli.Builder(tree); 273 | return builder.build().then(function(results) { 274 | var dir = results.directory; 275 | expect(readFile(dir + '/index.' + tree.targetExtension)).to.match(/expect\(true, 'index.js should pass jscs.'\).to.be.ok;/); 276 | }); 277 | }); 278 | }); 279 | 280 | describe('excludeFiles', function() { 281 | it('excludes single file and glob', function() { 282 | var sourcePath = 'tests/fixtures/excludes'; 283 | 284 | tree = jscsTree(sourcePath, { 285 | persist: false, 286 | 287 | logError: function(message) { loggerOutput.push(message); } 288 | }); 289 | 290 | builder = new broccoli.Builder(tree); 291 | return builder.build().then(function() { 292 | expect(loggerOutput.join('\n')).to.match(/function included/); 293 | expect(loggerOutput.join('\n')).to.not.match(/function excluded/); 294 | }); 295 | }); 296 | 297 | it('test file exclude caching - glob matching should only run once for a given relative path', function() { 298 | var sourcePath = 'tests/fixtures/esnext-parse-error'; 299 | var matchesPatternCalled = 0; 300 | var tree; 301 | 302 | tree = jscsTree(sourcePath, {}); 303 | tree.inputPaths = [sourcePath]; 304 | tree.configure(); 305 | 306 | tree._matchesPattern = function() { 307 | matchesPatternCalled++; 308 | return true; 309 | }; 310 | 311 | expect(tree._excludeFileCache).to.be.empty(); 312 | 313 | tree.processString(readFile(sourcePath + '/another-bad-file.js'), 'another-bad-file.js'); 314 | tree.processString(readFile(sourcePath + '/bad-file.js'), 'bad-file.js'); 315 | tree.processString(readFile(sourcePath + '/good-file.js'), 'good-file.js'); 316 | 317 | expect(tree._excludeFileCache['another-bad-file.js']).to.be.ok(); 318 | expect(tree._excludeFileCache['bad-file.js']).to.be.ok(); 319 | expect(tree._excludeFileCache['good-file.js']).to.be.ok(); 320 | expect(tree._excludeFileCache).to.only.have.keys('another-bad-file.js', 'bad-file.js', 'good-file.js'); 321 | expect(matchesPatternCalled).to.be(3); 322 | 323 | tree.processString(readFile(sourcePath + '/another-bad-file.js'), 'another-bad-file.js'); 324 | tree.processString(readFile(sourcePath + '/bad-file.js'), 'bad-file.js'); 325 | tree.processString(readFile(sourcePath + '/good-file.js'), 'good-file.js'); 326 | 327 | expect(tree._excludeFileCache).to.only.have.keys('another-bad-file.js', 'bad-file.js', 'good-file.js'); 328 | expect(matchesPatternCalled).to.be(3); 329 | }); 330 | }); 331 | 332 | describe('escapeErrorString', function() { 333 | var tree; 334 | 335 | beforeEach(function() { 336 | tree = jscsTree('.', {}); 337 | }); 338 | 339 | it('escapes single quotes properly', function() { 340 | expect(tree.escapeErrorString("'")).to.equal('\\\''); 341 | }); 342 | 343 | it('escapes new lines properly', function() { 344 | expect(tree.escapeErrorString("\n")).to.equal('\\n'); 345 | }); 346 | }); 347 | }); 348 | --------------------------------------------------------------------------------