├── test ├── fixture │ ├── config │ │ ├── bad_config │ │ │ └── package.json │ │ │ │ └── .gitkeep │ │ ├── filenames │ │ │ └── csf.config.js │ │ ├── flags │ │ │ └── csf.config.js │ │ └── path_configs │ │ │ └── csf.config.js │ ├── meta.js │ ├── test_node.js │ ├── css │ │ ├── property_sort.css │ │ ├── hex_lower_case.css │ │ ├── hex_redundant.css │ │ ├── missing_integer.css │ │ ├── invalid_format.css │ │ ├── at_rule_empty_line.css │ │ ├── trailing_newlines.css │ │ ├── missing_list_values_space.css │ │ ├── missing_selector_space.css │ │ ├── needless_quotes.css │ │ ├── trailing_comma.css │ │ ├── missing_newlines.css │ │ ├── invalid_border_reset.css │ │ └── needless_unit.css │ ├── eslint-plugin-custom-lint.js │ ├── test.js │ ├── result.xml │ ├── test.jsp │ └── junit-4.xsd ├── lint_js_rules │ ├── no_extra_semi.js │ ├── catch_arg_name.js │ ├── catch_format.js │ ├── no_undef.js │ ├── multiple_vars.js │ ├── format_constants.js │ ├── array_spacing.js │ ├── dot_notation.js │ ├── sort_requires.js │ ├── no_use_before_define.js │ ├── liferay_language_get.js │ ├── no_unused_vars.js │ ├── array_spacing_chars.js │ ├── sort_constants.js │ ├── no_is_prefix.js │ ├── liferay_provide_format.js │ ├── function_spacing.js │ ├── no_multiple_return.js │ ├── sort_vars.js │ ├── sort_props.js │ ├── format_multiline_vars.js │ └── format_args.js ├── logger.js ├── test_utils │ └── index.js ├── stylelint_rule_tester.js ├── engine_rules │ ├── html.js │ ├── html_js.js │ ├── common.js │ └── js.js ├── formatter.js ├── file.js ├── config.js ├── deprecation.js ├── lint_css_rules │ └── at_rule_empty_line.js ├── re.js ├── rule_utils.js └── junit.js ├── .travis.yml ├── lib ├── engine_rules │ ├── html.js │ ├── html_js.js │ ├── common.js │ ├── js.js │ └── css.js ├── rules.js ├── lint_js_rules │ ├── no_extra_semi.js │ ├── catch_arg_name.js │ ├── catch_format.js │ ├── multiple_vars.js │ ├── dot_notation.js │ ├── format_constants.js │ ├── array_spacing_chars.js │ ├── liferay_language_get.js │ ├── no_undef.js │ ├── no_multiple_return.js │ ├── array_spacing.js │ ├── no_use_before_define.js │ ├── sort_constants.js │ ├── sort_requires.js │ ├── liferay_provide_format.js │ ├── no_unused_vars.js │ ├── no_is_prefix.js │ ├── function_spacing.js │ ├── sort_vars.js │ ├── format_multiline_vars.js │ ├── sort_props.js │ └── format_args.js ├── config │ ├── eslint_jsp.js │ ├── eslint_es6.js │ ├── eslint.js │ └── stylelint.js ├── tpl │ ├── lint_rules │ │ └── js │ │ │ ├── test.js │ │ │ └── rule.js │ └── junit_report.tpl ├── base.js ├── logger.js ├── file.js ├── re.js ├── regex.js ├── deprecation.js ├── lint_css.js ├── formatter.js ├── argv.js ├── junit.js ├── config.js ├── lint_css_rules │ └── at_rule_empty_line.js ├── rule_utils.js ├── lint_js.js ├── js.js ├── css.js └── meta.js ├── .gitignore ├── .npmignore ├── appveyor.yml ├── bin └── index.js ├── LICENSE ├── package.json └── gulpfile.js /test/fixture/config/bad_config/package.json/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixture/meta.js: -------------------------------------------------------------------------------- 1 | exports.check = function(config) { 2 | }; -------------------------------------------------------------------------------- /test/fixture/config/filenames/csf.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | }; -------------------------------------------------------------------------------- /test/fixture/test_node.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log('foo'); -------------------------------------------------------------------------------- /test/fixture/css/property_sort.css: -------------------------------------------------------------------------------- 1 | .property-sort { 2 | padding: 0; 3 | background: red; 4 | } -------------------------------------------------------------------------------- /test/fixture/css/hex_lower_case.css: -------------------------------------------------------------------------------- 1 | .hex-lower-case { 2 | background-color: #FFF; 3 | color: #fff; 4 | } -------------------------------------------------------------------------------- /test/fixture/css/hex_redundant.css: -------------------------------------------------------------------------------- 1 | .hex-redundant { 2 | background-color: #FFF; 3 | color: #FFFFFF; 4 | } -------------------------------------------------------------------------------- /test/fixture/css/missing_integer.css: -------------------------------------------------------------------------------- 1 | .missing-integer { 2 | padding: .2em; 3 | 4 | padding: 0.2em; 5 | } -------------------------------------------------------------------------------- /test/fixture/css/invalid_format.css: -------------------------------------------------------------------------------- 1 | .invalid-format { 2 | color:#FFF; 3 | color: #FFF; 4 | 5 | color: #FFF; 6 | } -------------------------------------------------------------------------------- /test/fixture/css/at_rule_empty_line.css: -------------------------------------------------------------------------------- 1 | .at-rules { 2 | background: red; 3 | @include fakemixin(); 4 | color: white; 5 | } -------------------------------------------------------------------------------- /test/fixture/css/trailing_newlines.css: -------------------------------------------------------------------------------- 1 | .trailing-newlines1 { 2 | color: #FFF; 3 | 4 | } 5 | 6 | .trailing-newlines2 { 7 | color: #FFF; 8 | } -------------------------------------------------------------------------------- /test/fixture/css/missing_list_values_space.css: -------------------------------------------------------------------------------- 1 | .missing-list-values-space { 2 | color: rgba(0,0,0,0); 3 | 4 | color: rgba(255, 0, 0, 0.5); 5 | } -------------------------------------------------------------------------------- /test/fixture/css/missing_selector_space.css: -------------------------------------------------------------------------------- 1 | .missing-selector-space1{ 2 | color: #FFF; 3 | } 4 | 5 | .missing-selector-space3 { 6 | color: #FFF; 7 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "9" 5 | - "8" 6 | - "7" 7 | - "6" 8 | after_success: 9 | - npm run coveralls -------------------------------------------------------------------------------- /test/fixture/config/flags/csf.config.js: -------------------------------------------------------------------------------- 1 | var getRule = require('../../../test_utils').getRule; 2 | 3 | module.exports = { 4 | js: { 5 | lint: getRule(0, true) 6 | } 7 | }; -------------------------------------------------------------------------------- /lib/engine_rules/html.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | anonymousBlockContainers: { 3 | message: 'Block level containers need either a class or id: {1}', 4 | regex: // 5 | } 6 | }; -------------------------------------------------------------------------------- /test/fixture/css/needless_quotes.css: -------------------------------------------------------------------------------- 1 | .needless-quotes { 2 | background-image: url('../images/foo.png'); 3 | background-image: url("../images/foo.png"); 4 | 5 | background-image: url(../images/foo.png); 6 | } -------------------------------------------------------------------------------- /test/fixture/css/trailing_comma.css: -------------------------------------------------------------------------------- 1 | .trailing-comma5 { 2 | padding: 1px; 3 | color: #FFF; 4 | } 5 | 6 | .trailing-comma1, { 7 | color: #FFF; 8 | } 9 | 10 | .trailing-comma2 { 11 | color: #FFF; 12 | } -------------------------------------------------------------------------------- /test/fixture/eslint-plugin-custom-lint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | rules: { 3 | foo: function(context) { 4 | return { 5 | 'Program:exit': function() { 6 | } 7 | }; 8 | } 9 | } 10 | }; -------------------------------------------------------------------------------- /lib/rules.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | common: require('./engine_rules/common'), 3 | css: require('./engine_rules/css'), 4 | 5 | html: require('./engine_rules/html'), 6 | 7 | htmlJS: require('./engine_rules/html_js'), 8 | js: require('./engine_rules/js') 9 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/no_extra_semi.js: -------------------------------------------------------------------------------- 1 | module.exports = context => ({ 2 | EmptyStatement(node) { 3 | var afterText = context.getSource(node, 0, 10); 4 | 5 | if (afterText !== ';(function(') { 6 | context.report(node, 'Unnecessary semicolon.'); 7 | } 8 | } 9 | }); -------------------------------------------------------------------------------- /test/fixture/config/path_configs/csf.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | flags: { 3 | quiet: true 4 | }, 5 | 6 | 'path:**/foo.css': { 7 | flags: { 8 | quiet: false 9 | } 10 | }, 11 | 12 | 'path:**/foo.js': { 13 | flags: { 14 | quiet: true 15 | } 16 | } 17 | }; -------------------------------------------------------------------------------- /test/fixture/css/missing_newlines.css: -------------------------------------------------------------------------------- 1 | .missing-new-lines1 { 2 | color: #FFF; 3 | } 4 | .missing-new-lines2 { 5 | .missing-new-lines3 { 6 | color: #FFF; 7 | } 8 | .missing-new-lines4 { 9 | color: #FFF; 10 | } 11 | 12 | .missing-new-lines5 { 13 | color: #FFF; 14 | } 15 | } -------------------------------------------------------------------------------- /test/fixture/test.js: -------------------------------------------------------------------------------- 1 | /* if(){ "Hello" */ 2 | 3 | var x = 'foo'; 4 | 5 | if(foo){ 6 | alert('foo'); 7 | } else { 8 | alert('else'); 9 | } 10 | 11 | foo({ 12 | 13 | }); 14 | 15 | foo( 16 | function () { 17 | 18 | } 19 | ); 20 | 21 | console.log('foo'); 22 | 23 | var something = 'foo'; 24 | if (something) { 25 | 26 | } -------------------------------------------------------------------------------- /lib/lint_js_rules/catch_arg_name.js: -------------------------------------------------------------------------------- 1 | var sub = require('string-sub'); 2 | 3 | module.exports = context => ({ 4 | CatchClause(node) { 5 | var paramName = node.param.name; 6 | 7 | if (paramName != 'e') { 8 | var message = sub('Catch statement param should be "e", not "{0}"', paramName); 9 | 10 | context.report(node, message); 11 | } 12 | } 13 | }); -------------------------------------------------------------------------------- /test/fixture/css/invalid_border_reset.css: -------------------------------------------------------------------------------- 1 | .invalid-border-reset { 2 | border: 0; 3 | border: none; 4 | border-bottom: 0; 5 | border-bottom: none; 6 | border-left: 0; 7 | border-left: none; 8 | border-right: 0; 9 | border-right: none; 10 | border-top: 0; 11 | border-top: none; 12 | 13 | border-bottom-width: 0; 14 | border-left-width: 0; 15 | border-right-width: 0; 16 | border-top-width: 0; 17 | border-width: 0; 18 | } -------------------------------------------------------------------------------- /lib/lint_js_rules/catch_format.js: -------------------------------------------------------------------------------- 1 | var sub = require('string-sub'); 2 | 3 | module.exports = context => ({ 4 | CatchClause(node) { 5 | var end = node.loc.end.line; 6 | var start = node.loc.start.line; 7 | 8 | var shouldEnd = start + 1; 9 | 10 | if (!node.body.body.length && end != shouldEnd) { 11 | var message = sub('Empty catch statement should be closed on line {0}', shouldEnd); 12 | 13 | context.report(node, message); 14 | } 15 | } 16 | }); -------------------------------------------------------------------------------- /lib/engine_rules/html_js.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | liferayLanguage: { 3 | message: 'Do not use Liferay.Language.get() outside of .js files: {1}', 4 | regex: /Liferay\.Language\.get/ 5 | }, 6 | liferayProvide: { 7 | message: 'You can\'t have a Liferay.provide call in a script taglib that has a "use" attribute', 8 | regex: /Liferay\.provide/, 9 | test(content, regex, rule, context) { 10 | return context.asyncAUIScript && this.test(content, regex); 11 | } 12 | } 13 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/multiple_vars.js: -------------------------------------------------------------------------------- 1 | var sub = require('string-sub'); 2 | 3 | module.exports = context => ({ 4 | VariableDeclaration(node) { 5 | var declarations = node.declarations; 6 | 7 | if (declarations.length > 1) { 8 | var vars = declarations.map( 9 | (item, index) => item.id.name 10 | ); 11 | 12 | var message = sub('Each variable should have it\'s own var statement: {0}', vars.join(', ')); 13 | 14 | context.report(node, message); 15 | } 16 | } 17 | }); -------------------------------------------------------------------------------- /lib/config/eslint_jsp.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'globals': { 3 | '$': true, 4 | '_': true, 5 | 'A': true 6 | }, 7 | 8 | 'rules': { 9 | 'block-scoped-var': 0, 10 | 'csf-dot-notation': 2, 11 | 'csf-no-undef': [ 12 | 2, 13 | {} 14 | ], 15 | 'csf-no-unused-vars': [ 16 | 2, 17 | { 18 | 'jsp': true 19 | } 20 | ], 21 | 'indent': [0, 'tab'], 22 | 'dot-notation': 0, 23 | 'no-trailing-spaces': 0, 24 | 'no-undef': 0, 25 | 'no-unused-vars': 0, 26 | 'no-use-before-define': 0, 27 | 'lines-around-comment': 0 28 | } 29 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/no_extra_semi.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | ';(function(){});' 16 | ], 17 | 18 | invalid: [ 19 | { code: ';;(function(){});', errors: [{ message: 'Unnecessary semicolon.'}] } 20 | ] 21 | } 22 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/dot_notation.js: -------------------------------------------------------------------------------- 1 | var REGEX = require('../regex'); 2 | 3 | var dotNotation = require('eslint/lib/rules/dot-notation'); 4 | 5 | var customDotNotation = context => ({ 6 | MemberExpression(node) { 7 | var propertyType = node.property.type; 8 | var propertyValue = node.property.value; 9 | 10 | if (propertyType === 'Literal' && !REGEX.STUBS.test(propertyValue)) { 11 | dotNotation.create(context).MemberExpression(node); 12 | } 13 | } 14 | }); 15 | 16 | Object.assign(customDotNotation, dotNotation); 17 | 18 | module.exports = customDotNotation; -------------------------------------------------------------------------------- /test/logger.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var path = require('path'); 3 | 4 | var Logger = require('../lib/logger'); 5 | 6 | chai.use(require('chai-string')); 7 | 8 | var assert = chai.assert; 9 | 10 | describe( 11 | 'Logger', 12 | function() { 13 | it( 14 | 'should log failures properly', 15 | function() { 16 | var logger = new Logger.constructor(); 17 | 18 | logger.log(1, 'Has error', 'foo.js', 'error'); 19 | 20 | assert.isObject(logger.testStats); 21 | assert.equal(logger.testStats.failures, 1); 22 | } 23 | ); 24 | } 25 | ); -------------------------------------------------------------------------------- /lib/tpl/lint_rules/js/test.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require(`../../lib/lint_js_rules/${path.basename(__filename)}`), 13 | { 14 | valid: [ 15 | // Code that won't give a warning 16 | ], 17 | 18 | invalid: [ 19 | // { 20 | // code: '', 21 | // errors: [ { message: 'Something went wrong' } ] 22 | // } 23 | ] 24 | } 25 | ); -------------------------------------------------------------------------------- /lib/tpl/lint_rules/js/rule.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var sub = require('string-sub'); 3 | 4 | var base = require('../base'); 5 | var utils = require('../rule_utils'); 6 | 7 | module.exports = { 8 | meta: { 9 | docs: { 10 | description: '<%= description %>', 11 | category: 'Fill me in', 12 | recommended: false 13 | }, 14 | fixable: null, // or "code" or "whitespace" 15 | schema: [ 16 | // fill in your schema 17 | ] 18 | }, 19 | 20 | create(context) { 21 | return { 22 | 23 | // ReturnStatement: function(node) { 24 | // .... 25 | // } 26 | 27 | }; 28 | } 29 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/catch_arg_name.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | 'try{}catch(e){}' 16 | ], 17 | 18 | invalid: [ 19 | { 20 | code: 'try{}catch(err){}', 21 | errors: [ { message: 'Catch statement param should be "e", not "err"' } ] 22 | } 23 | ] 24 | } 25 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/catch_format.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | 'try{}catch(e){\n}' 16 | ], 17 | 18 | invalid: [ 19 | { 20 | code: 'try{}catch(e){}', 21 | errors: [ { message: 'Empty catch statement should be closed on line 2' } ] 22 | } 23 | ] 24 | } 25 | ); -------------------------------------------------------------------------------- /test/fixture/css/needless_unit.css: -------------------------------------------------------------------------------- 1 | @charset "UTF-8"; 2 | 3 | .needless-unit { 4 | padding: 0px; 5 | 6 | padding: 0; 7 | @include test; 8 | 9 | } 10 | 11 | .needless-unit-2 { 12 | @include foo(1, 3); 13 | @include foo(1, 3); 14 | @include test(2, 3); 15 | 16 | @include ml(3, 3) { 17 | content: ''; 18 | }; 19 | @include xs { 20 | content: ''; 21 | }; 22 | 23 | @media print { 24 | .foo { 25 | border: 1px solid black; 26 | } 27 | } 28 | 29 | @keyframes foo { 30 | 0% { 31 | border: 1px solid red; 32 | } 33 | to { 34 | border: 1px solid green; 35 | } 36 | } 37 | 38 | padding: 0; 39 | } -------------------------------------------------------------------------------- /lib/lint_js_rules/format_constants.js: -------------------------------------------------------------------------------- 1 | var utils = require('../rule_utils'); 2 | 3 | module.exports = context => { 4 | var checkDistance = node => { 5 | var constants = utils.getConstants(node); 6 | 7 | constants.forEach( 8 | (item, index, coll) => { 9 | var prev = coll[index - 1]; 10 | 11 | if (prev) { 12 | var diff = utils.getLineDistance(prev, item); 13 | 14 | if (diff < 2) { 15 | context.report(item, 'Constants should be separated by a single new line'); 16 | } 17 | } 18 | } 19 | ); 20 | }; 21 | 22 | return { 23 | BlockStatement: checkDistance, 24 | Program: checkDistance 25 | }; 26 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # Compiled binary addons (http://nodejs.org/api/addons.html) 20 | build/Release 21 | 22 | # Dependency directory 23 | # Deployed apps should consider commenting this line out: 24 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 25 | node_modules 26 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | # Logs 3 | logs 4 | *.log 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # Compiled binary addons (http://nodejs.org/api/addons.html) 21 | build/Release 22 | 23 | # Dependency directory 24 | # Deployed apps should consider commenting this line out: 25 | # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git 26 | node_modules -------------------------------------------------------------------------------- /lib/tpl/junit_report.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{#files}} 4 | 5 | {{#errors}} 6 | 7 | {{#failure}} 8 | 14 | {{/failure}} 15 | 16 | {{/errors}} 17 | 18 | {{/files}} 19 | -------------------------------------------------------------------------------- /test/lint_js_rules/no_undef.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | '_PN_foo()', 16 | '_PN_()', 17 | '_PN_.foo', 18 | 'var a = _PN_;', 19 | 'var b = _EL_EXPRESSION_1' 20 | ], 21 | 22 | invalid: [ 23 | { 24 | code: 'var a = b;', 25 | errors: [ { message: "'b' is not defined." } ] 26 | } 27 | ] 28 | } 29 | ); -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Test against the latest version of this Node.js version 2 | environment: 3 | matrix: 4 | # node.js 5 | - nodejs_version: "10" 6 | - nodejs_version: "9" 7 | - nodejs_version: "8" 8 | - nodejs_version: "7" 9 | - nodejs_version: "6" 10 | 11 | # Install scripts. (runs after repo cloning) 12 | install: 13 | # Get the latest stable version of Node.js or io.js 14 | - ps: Install-Product node $env:nodejs_version 15 | # install modules 16 | - npm install 17 | 18 | # Post-install test scripts. 19 | test_script: 20 | # Output useful info for debugging. 21 | - node --version 22 | - npm --version 23 | # run tests 24 | - npm test 25 | 26 | # Don't actually build. 27 | build: off -------------------------------------------------------------------------------- /lib/base.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | require('lodash-namespace')(_); 4 | require('lodash-bindright')(_); 5 | 6 | var REGEX_NEWLINE = /\r?\n/; 7 | 8 | var iterateLines = (contents, iterator) => { 9 | var lines = contents.split(REGEX_NEWLINE); 10 | 11 | return lines.map(iterator).join('\n'); 12 | }; 13 | 14 | var jspLintStubs = { 15 | echoScriptlet: '_ECHO_SCRIPTLET', 16 | elExpression: '_EL_EXPRESSION_', 17 | namespace: '_PN_', 18 | scriptlet: '_SCRIPTLET_' 19 | }; 20 | 21 | var stubs = _.transform( 22 | jspLintStubs, 23 | (result, item, index) => { 24 | result[item] = true; 25 | }, 26 | {} 27 | ); 28 | 29 | module.exports = { 30 | INDENT: ' ', 31 | REGEX_NEWLINE, 32 | 33 | iterateLines, 34 | jspLintStubs, 35 | stubs 36 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/multiple_vars.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | 'var x = 1;\nvar y = 2;', 16 | 'var x;\nvar y;' 17 | ], 18 | 19 | invalid: [ 20 | { 21 | code: 'var x = 2, y = 2;', 22 | errors: [ { message: 'Each variable should have it\'s own var statement: x, y' } ] 23 | }, 24 | { 25 | code: 'var x, y;', 26 | errors: [ { message: 'Each variable should have it\'s own var statement: x, y' } ] 27 | } 28 | ] 29 | } 30 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/format_constants.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | 'var FOO1 = \'\';\n\nvar FOO2 = \'\';' 16 | ], 17 | 18 | invalid: [ 19 | { 20 | code: 'var FOO1 = \'\';\nvar FOO2 = \'\';', 21 | errors: [ { message: 'Constants should be separated by a single new line' } ] 22 | }, 23 | { 24 | code: 'var FOO1 = \'\';var FOO2 = \'\';', 25 | errors: [ { message: 'Constants should be separated by a single new line' } ] 26 | } 27 | ] 28 | } 29 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/array_spacing.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | '[1, 2, 3]' 16 | ], 17 | 18 | invalid: [ 19 | { 20 | code: '[ 1, 2, 3]', 21 | errors: [ { message: 'Remove leading spaces: [ ...' } ] 22 | }, 23 | { 24 | code: '[1, 2, 3 ]', 25 | errors: [ { message: 'Remove trailing spaces: ... ]' } ] 26 | }, 27 | { 28 | code: '[ 1, 2, 3 ]', 29 | errors: [ { message: 'Remove leading and trailing spaces: [ ... ]' } ] 30 | } 31 | ] 32 | } 33 | ); -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | var Logger = require('content-logger'); 2 | 3 | var contentLogger = Logger.create( 4 | { 5 | prototype: { 6 | init() { 7 | this.testStats = { 8 | failures: 0 9 | }; 10 | 11 | this._errorStack = []; 12 | 13 | this.on( 14 | 'add', 15 | function(error) { 16 | if (error.type !== 'ignored') { 17 | this.testStats.failures++; 18 | } 19 | } 20 | ); 21 | }, 22 | 23 | filterFileErrors(file, fn) { 24 | var fileErrors; 25 | 26 | var errors = this.getErrors(file); 27 | 28 | this._errorStack.push(errors); 29 | 30 | var filteredErrors = fn(errors); 31 | 32 | filteredErrors.errorMap = {}; 33 | 34 | this.fileErrors[file] = filteredErrors; 35 | 36 | return fileErrors; 37 | } 38 | } 39 | } 40 | ); 41 | 42 | module.exports = new contentLogger(); -------------------------------------------------------------------------------- /lib/file.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var util = require('util'); 3 | 4 | var colors = require('cli-color-keywords')(); 5 | 6 | exports.handleFileReadError = (err, file) => { 7 | var errMsg = 'Could not open file'; 8 | 9 | if (err.code === 'ENOENT') { 10 | errMsg = 'File does not exist'; 11 | } 12 | else if (err.code === 'EISDIR') { 13 | errMsg = ''; 14 | } 15 | 16 | if (errMsg) { 17 | errMsg = util.format('%s: %s', colors.error(errMsg), path.resolve(file)); 18 | } 19 | 20 | return errMsg; 21 | }; 22 | 23 | exports.handleFileWriteError = (err, file) => { 24 | var errMsg = 'Could not write to file'; 25 | 26 | if (file == '') { 27 | errMsg = 'Can\'t write to (no file name provided)'; 28 | } 29 | else { 30 | file = path.resolve(file); 31 | } 32 | 33 | return util.format('%s: %s', colors.error(errMsg), file); 34 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/dot_notation.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | var base = require('../../lib/base.js'); 4 | 5 | var lint = require('../../lib/lint_js'); 6 | 7 | var linter = lint.linter; 8 | var RuleTester = lint.eslint.RuleTester; 9 | 10 | var ruleTester = new RuleTester(); 11 | 12 | var validRules = [ 13 | 'document["some-prop"];' 14 | ]; 15 | 16 | _.forEach( 17 | base.stubs, 18 | function(item, index) { 19 | validRules.push('document["' + index + '"];'); 20 | } 21 | ); 22 | 23 | ruleTester.run( 24 | path.basename(__filename, '.js'), 25 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 26 | { 27 | valid: validRules, 28 | 29 | invalid: [ 30 | { 31 | code: 'document["test"]', 32 | errors: [ { message: '["test"] is better written in dot notation.' } ] 33 | } 34 | ] 35 | } 36 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/array_spacing_chars.js: -------------------------------------------------------------------------------- 1 | var sub = require('string-sub'); 2 | 3 | var REGEX = require('../regex'); 4 | 5 | var isSingleLine = node => node.loc.start.line === node.loc.end.line; 6 | 7 | module.exports = context => ({ 8 | ArrayExpression(node) { 9 | if (isSingleLine(node)) { 10 | var source = context.getSource(node); 11 | 12 | var tmpSource = source.replace(/(['"]).*?\1/g, '$1$1'); 13 | 14 | if (REGEX.ARRAY_INTERNAL_SPACE.test(tmpSource)) { 15 | var missingSpaces = []; 16 | 17 | source.replace( 18 | REGEX.ARRAY_INTERNAL_SPACE, 19 | (item, index, str) => { 20 | missingSpaces.push(item.replace('\t', '\\t')); 21 | } 22 | ); 23 | 24 | var message = sub('Array items should be separated by exactly one space:{0}', missingSpaces.join('')); 25 | 26 | context.report(node, message); 27 | } 28 | } 29 | } 30 | }); -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var ConfigStore = require('configstore'); 4 | var updateNotifier = require('update-notifier'); 5 | 6 | var deprecationCheck = require('../lib/deprecation'); 7 | 8 | var pkg = require('../package.json'); 9 | 10 | var notifier = updateNotifier({pkg}); 11 | 12 | if (notifier.update) { 13 | notifier.notify(); 14 | } 15 | 16 | var config = new ConfigStore( 17 | pkg.name, 18 | { 19 | lastPackageVersion: pkg.version, 20 | lastUpdateCheck: Date.now() 21 | } 22 | ); 23 | 24 | var cli = require('../lib/cli').init().then( 25 | function(results) { 26 | var deprecated = deprecationCheck( 27 | { 28 | config, 29 | pkg, 30 | scriptName: process.argv[1] 31 | } 32 | ); 33 | 34 | if (deprecated) { 35 | console.log(deprecated); 36 | } 37 | 38 | if (results.EXIT_WITH_FAILURE === true) { 39 | process.exitCode = 1; 40 | } 41 | } 42 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/sort_requires.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | '({requires: []})', 16 | '({requires: ["a"]})', 17 | '({requires: ["a", "b"]})', 18 | '({requires: ["a", xyz, "b"]})', 19 | '({required: []})', 20 | '({requires: 1})' 21 | ], 22 | 23 | invalid: [ 24 | { 25 | code: '({requires: ["b", "a"]})', 26 | errors: [ { message: 'Sort modules in "requires" array: b > a' } ] 27 | }, 28 | { 29 | code: '({requires: ["b", "c", "a"]})', 30 | errors: [ { message: 'Sort modules in "requires" array: c > a' } ] 31 | } 32 | ] 33 | } 34 | ); -------------------------------------------------------------------------------- /test/test_utils/index.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var lintRules = require('../../lib/config/eslint').rules; 4 | 5 | exports.nl = function() { 6 | return _.toArray(arguments).join('\n'); 7 | } 8 | 9 | exports.addES6 = function(config) { 10 | var parserOptions = _.merge({ ecmaVersion: 6 }, config); 11 | 12 | return function(item, index) { 13 | item.parserOptions = parserOptions; 14 | 15 | return item; 16 | }; 17 | }; 18 | 19 | var invertValue = _.cond([[_.partial(_.eq, 0), _.constant(2)], [_.partial(_.eq, 2), _.constant(0)]]); 20 | 21 | exports.getRule = function(ruleName, invert, obj) { 22 | var rule; 23 | 24 | var rules = obj || lintRules; 25 | 26 | if (_.isNumber(ruleName)) { 27 | ruleName = Object.keys(rules)[ruleName]; 28 | } 29 | 30 | var retVal = _.pick(rules, ruleName); 31 | 32 | if (invert) { 33 | retVal[ruleName] = invertValue(retVal[ruleName]); 34 | } 35 | 36 | return retVal; 37 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/liferay_language_get.js: -------------------------------------------------------------------------------- 1 | module.exports = context => { 2 | var REGEX_STRING = /^(['"]).*\1$/; 3 | 4 | var isMemberExpression = obj => obj.type === 'MemberExpression'; 5 | 6 | return { 7 | CallExpression(node) { 8 | var callee = node.callee; 9 | 10 | if (isMemberExpression(callee) && isMemberExpression(callee.object) && callee.object.object.name === 'Liferay' && callee.object.property.name === 'Language' && callee.property.name === 'get') { 11 | 12 | var args = node.arguments; 13 | 14 | if (args.length === 1) { 15 | var arg = args[0]; 16 | 17 | if (arg.type !== 'Literal' || !REGEX_STRING.test(arg.raw)) { 18 | context.report(node, 'You should only pass a single string literal to Liferay.Language.get()'); 19 | } 20 | } 21 | else { 22 | context.report(node, 'Liferay.Language.get() only accepts a single string as an argument'); 23 | } 24 | } 25 | } 26 | }; 27 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/no_undef.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var base = require('../base'); 4 | var stubs = base.stubs; 5 | 6 | module.exports = context => { 7 | var noUndef = require('eslint/lib/rules/no-undef').create; 8 | 9 | var collectedReport = []; 10 | 11 | var mockContext = { 12 | report(obj) { 13 | collectedReport.push(obj); 14 | } 15 | }; 16 | 17 | _.defaults( 18 | mockContext, 19 | context, 20 | { 21 | options: context.options 22 | } 23 | ); 24 | 25 | return { 26 | 'Program:exit': function(node) { 27 | noUndef(mockContext)['Program:exit'](node); 28 | 29 | collectedReport.forEach( 30 | (item, index) => { 31 | var name = item.node.name; 32 | 33 | if (/_EL_EXPRESSION_\d+/.test(name)) { 34 | name = '_EL_EXPRESSION_'; 35 | } 36 | 37 | if (!stubs[name] && name.indexOf('_PN_') !== 0) { 38 | context.report(item); 39 | } 40 | } 41 | ); 42 | } 43 | }; 44 | }; -------------------------------------------------------------------------------- /test/stylelint_rule_tester.js: -------------------------------------------------------------------------------- 1 | var stylelint = require('stylelint'); 2 | var chai = require('chai'); 3 | 4 | chai.use(require('chai-string')); 5 | 6 | var assert = chai.assert; 7 | 8 | function assertEquality(processCss, context) { 9 | const describeFn = (context.only) ? describe.only : describe; 10 | 11 | describeFn( 12 | context.caseDescription, 13 | function() { 14 | it( 15 | context.completeAssertionDescription, 16 | function() { 17 | return processCss.then( 18 | function(comparisons) { 19 | comparisons.forEach( 20 | function(item, index) { 21 | var actual = item.actual; 22 | var expected = item.expected; 23 | var description = item.description; 24 | 25 | assert.equal(actual, expected, description); 26 | } 27 | ); 28 | } 29 | ); 30 | } 31 | ); 32 | } 33 | ); 34 | } 35 | 36 | module.exports = stylelint.createRuleTester(assertEquality); 37 | 38 | module.exports.stylelint = stylelint; -------------------------------------------------------------------------------- /lib/re.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var getObject = require('getobject'); 3 | 4 | var REGEX = require('./regex'); 5 | var RULES = require('./rules'); 6 | 7 | var re = require('roolz'); 8 | 9 | re.prototype.hasExtraNewLines = function(item, index, collection) { 10 | var extraNewLines = false; 11 | 12 | if (item === '') { 13 | var length = collection.length; 14 | 15 | extraNewLines = (index === 0 && length > 1) || collection[index - 1] === '' || (index === length - 1 && length > 1); 16 | } 17 | 18 | if (extraNewLines) { 19 | this.emit( 20 | 'message', 21 | { 22 | context: { 23 | rawContent: item, 24 | item, 25 | lineNum: index + 1 26 | }, 27 | message: 'Extra new line' 28 | } 29 | ); 30 | } 31 | 32 | return extraNewLines; 33 | }; 34 | 35 | re.prototype.hasHex = item => { 36 | var match = item.match(REGEX.HEX); 37 | 38 | return match && match[0]; 39 | }; 40 | 41 | re.prototype.hasProperty = item => REGEX.PROPERTY.test(item); 42 | 43 | module.exports = re; -------------------------------------------------------------------------------- /test/lint_js_rules/no_use_before_define.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | { code: "function a() { alert(b); } var b = 1;", options: [{'samescope': true}] } 16 | ], 17 | invalid: [ 18 | { code: "function a() { alert(b); } var b = 1;", options: [{'samescope': false}], errors: [{ message: "'b' was used before it was defined.", type: "Identifier" }] }, 19 | { code: "function a() { alert(b); } var b = 1;", options: ['nofunc'], errors: [{ message: "'b' was used before it was defined.", type: "Identifier" }] }, 20 | { code: "function a() { alert(b); var b = 1; }", options: [{'samescope': true}], errors: [{ message: "'b' was used before it was defined.", type: "Identifier" }] } 21 | ] 22 | } 23 | ); -------------------------------------------------------------------------------- /test/engine_rules/html.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var _ = require('lodash'); 3 | 4 | chai.use(require('chai-string')); 5 | 6 | var RE = require('../../lib/re'); 7 | 8 | var re = new RE(require('../../lib/rules')); 9 | var sub = require('string-sub'); 10 | 11 | var assert = chai.assert; 12 | 13 | describe( 14 | 'HTML Rule Engine Tests', 15 | function() { 16 | 'use strict'; 17 | 18 | it( 19 | 'should detect anonymous block containers', 20 | function() { 21 | var rule = re.rules.html.anonymousBlockContainers; 22 | 23 | var tests = ['
', '
foo
', '
']; 24 | 25 | _.forEach( 26 | tests, 27 | function(content, index) { 28 | var context = { 29 | content: content 30 | }; 31 | 32 | var result = re.testContent(rule, context); 33 | var lineNum = 1; 34 | 35 | assert.isTrue(result, sub('Expected {0} to match', content)); 36 | assert.startsWith(re.getMessage(result, rule, context), rule.message.split(':')[0]); 37 | } 38 | ); 39 | } 40 | ); 41 | } 42 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/no_multiple_return.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | meta: { 3 | docs: { 4 | category: 'Fill me in', 5 | description: 'Enforces keeping a single return within a function', 6 | recommended: false 7 | }, 8 | fixable: null, 9 | schema: [] 10 | }, 11 | 12 | create(context) { 13 | var funcInfo = null; 14 | 15 | var checkReturns = node => { 16 | if (funcInfo.returnCount > 1) { 17 | context.report( 18 | { 19 | message: 'Functions should only have one return statement', 20 | node 21 | } 22 | ); 23 | } 24 | }; 25 | 26 | return { 27 | 'ArrowFunctionExpression:exit': checkReturns, 28 | 'FunctionDeclaration:exit': checkReturns, 29 | 'FunctionExpression:exit': checkReturns, 30 | 31 | onCodePathStart(codePath) { 32 | funcInfo = { 33 | returnCount: 0, 34 | upper: funcInfo 35 | }; 36 | }, 37 | 38 | onCodePathEnd() { 39 | funcInfo = funcInfo.upper; 40 | }, 41 | 42 | 'Program:exit': checkReturns, 43 | 44 | ReturnStatement(node) { 45 | funcInfo.returnCount += 1; 46 | } 47 | }; 48 | } 49 | }; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Nate Cavanaugh 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. -------------------------------------------------------------------------------- /lib/regex.js: -------------------------------------------------------------------------------- 1 | var base = require('./base'); 2 | 3 | var REGEX = { 4 | ARRAY_INTERNAL_SPACE: /[^,]*?,((?! )| {2,})[^,]+?/g, 5 | ARRAY_SURROUNDING_SPACE: /\[\s|\s\]/g, 6 | 7 | AUI_SCRIPT: /<(aui:)?script(.*?)>([\s\S]*?)[ \t]*<\/\1script>/, 8 | 9 | BRACE_CLOSING: /\}\s*?$/, 10 | BRACE_OPENING: /\{\s*?$/, 11 | 12 | CSS_COMMA_END: /,\s*?$/, 13 | 14 | DIGITS: /\d+/, 15 | 16 | HEX: /#[0-9A-Fa-f]{3,6}\b/, 17 | HEX_REDUNDANT: /#([0-9A-Fa-f])\1([0-9A-Fa-f])\2([0-9A-Fa-f])\3/, 18 | 19 | LANG_EMPTYFN: /(^|(A\.)?(Lang\.)?)emptyFn(True|False)?/, 20 | 21 | LEADING_INCLUDE: /^@include /, 22 | LEADING_SPACE: /^\s+/, 23 | 24 | NEWLINE: base.REGEX_NEWLINE, 25 | 26 | PROP_KEY: /^\s*(?:@include\s)?([^:]+)(?:)/, 27 | PROPERTY: /^\t*([^:]+:|@include\s)[^;]+;$/, 28 | 29 | REGEX: /\/.*\/([gim]{1,3})?/g, 30 | 31 | SCRIPTLET_STUB: new RegExp(`^\\s*${base.jspLintStubs.scriptlet}$`), 32 | 33 | SERVICE_PROPS: /(^@)|((.*?) = (.*?))/, 34 | 35 | STUBS: new RegExp(`^_*(${Object.keys(base.stubs).join('|')})_*`), 36 | 37 | STYLE: /<(style)(.*?)>([\s\S]*?)<\/\1>/, 38 | 39 | VAR_IS: /^_?is[A-Z]/, 40 | 41 | REPLACE_HEX_REDUNDANT: '#$1$2$3', 42 | }; 43 | 44 | module.exports = REGEX; -------------------------------------------------------------------------------- /test/formatter.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var path = require('path'); 3 | 4 | var Config = require('../lib/config'); 5 | var Formatter = require('../lib/formatter'); 6 | var Logger = require('../lib/logger'); 7 | 8 | chai.use(require('chai-string')); 9 | 10 | var assert = chai.assert; 11 | 12 | describe( 13 | 'Formatter', 14 | function() { 15 | 'use strict'; 16 | 17 | var logger = new Logger.constructor(); 18 | 19 | it( 20 | 'should have a config object', 21 | function() { 22 | var formatter = Formatter.get('foo.css', logger, {}); 23 | 24 | assert.isTrue(typeof formatter._config === 'function'); 25 | assert.isObject(formatter._config._paths); 26 | } 27 | ); 28 | 29 | it( 30 | 'should get merged config by path', 31 | function() { 32 | var cwd = path.join(__dirname, 'fixture/config/path_configs'); 33 | 34 | return (new Config.Loader).load(cwd).then( 35 | function(config) { 36 | var formatter = Formatter.get('foo.css', logger, {quiet: true}); 37 | 38 | formatter._config = config; 39 | 40 | assert.isFalse(formatter.config('flags.quiet')); 41 | } 42 | ); 43 | } 44 | ); 45 | } 46 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/liferay_language_get.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var invalidTests = ['1', 'function(){}', '/f/', 'new Date()', 'foo'].map( 11 | function(item, index) { 12 | return { 13 | code: 'Liferay.Language.get(' + item + ')', 14 | errors: [ { message: 'You should only pass a single string literal to Liferay.Language.get()' } ] 15 | } 16 | } 17 | ); 18 | 19 | ruleTester.run( 20 | path.basename(__filename, '.js'), 21 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 22 | { 23 | valid: [ 24 | 'Liferay.Language.get("foo")', 25 | 'Liferay.Language.get(\'foo\')' 26 | ], 27 | 28 | invalid: invalidTests.concat([ 29 | { 30 | code: 'Liferay.Language.get()', 31 | errors: [ { message: 'Liferay.Language.get() only accepts a single string as an argument' } ] 32 | }, 33 | { 34 | code: 'Liferay.Language.get("foo", name)', 35 | errors: [ { message: 'Liferay.Language.get() only accepts a single string as an argument' } ] 36 | } 37 | ]) 38 | } 39 | ); -------------------------------------------------------------------------------- /test/fixture/result.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /test/lint_js_rules/no_unused_vars.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var options = [{'jsp': true}]; 11 | 12 | ruleTester.run( 13 | path.basename(__filename, '.js'), 14 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 15 | { 16 | valid: [ 17 | 'var _PN_xyz = 1;', 18 | { 19 | code: '(function(){ var _PN_xyz = function(){}; });', 20 | options: options 21 | }, 22 | { 23 | code: '(function(){ function _PN_xyz(){} })', 24 | options: options 25 | }, 26 | { 27 | code: '(function(){ function _SCRIPTLET_xyz(){} })', 28 | options: options 29 | }, 30 | ], 31 | 32 | invalid: [ 33 | { 34 | code: '(function(){ var _PN_xyz = 1; });', 35 | errors: [ { message: "'_PN_xyz' is assigned a value but never used." } ], 36 | options: options 37 | }, 38 | { 39 | code: 'var OSBForm_EL_EXPRESSION_13;', 40 | options: options, 41 | parserOptions: { 42 | sourceType: 'module' 43 | }, 44 | errors: [ { message: "'OSBForm_EL_EXPRESSION_13' is defined but never used." } ], 45 | } 46 | ] 47 | } 48 | ); -------------------------------------------------------------------------------- /lib/deprecation.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var colors = require('cli-color-keywords')(); 3 | var path = require('path'); 4 | 5 | var unindent = (strings, ...keys) => _.zipWith(strings, keys, _.add).join('').replace(/^\s+/gm, ' '); 6 | 7 | function deprecationCheck(params) { 8 | var config = params.config; 9 | var scriptName = params.scriptName; 10 | 11 | var checkInterval = params.interval || deprecationCheck.INTERVAL; 12 | 13 | var lastDeprecationCheck = config.get('lastDeprecationCheck'); 14 | 15 | var retVal = ''; 16 | 17 | if (path.basename(scriptName) === 'check_sf' && (Date.now() - lastDeprecationCheck >= checkInterval)) { 18 | var header = `Using the ${colors.inverse('check_sf')} form of this module is deprecated.`; 19 | 20 | var footer = `Please use the ${colors.inverse('csf')} command instead. It's easier to type too!`; 21 | 22 | var maxLineLength = Math.max(header.length, footer.length); 23 | 24 | var bar = new Array(maxLineLength).join('-'); 25 | 26 | config.set('lastDeprecationCheck', Date.now()); 27 | 28 | retVal = unindent`${bar} 29 | ${colors.error(header)} 30 | ${colors.green(footer)}`; 31 | } 32 | 33 | return retVal; 34 | } 35 | 36 | deprecationCheck.INTERVAL = 1000 * 60 * 60 * 24; 37 | 38 | module.exports = deprecationCheck; -------------------------------------------------------------------------------- /test/file.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var path = require('path'); 3 | 4 | var sub = require('string-sub'); 5 | var File = require('../lib/file'); 6 | 7 | chai.use(require('chai-string')); 8 | 9 | var assert = chai.assert; 10 | 11 | describe( 12 | 'File', 13 | function() { 14 | 'use strict'; 15 | 16 | var filePath = path.resolve('foo.txt'); 17 | 18 | it( 19 | 'should set handle a file read error', 20 | function() { 21 | var missingFileErr = { 22 | code: 'ENOENT' 23 | }; 24 | 25 | var permissionsFileErr = { 26 | code: 'EACCESS' 27 | }; 28 | 29 | assert.equal(File.handleFileReadError(missingFileErr, filePath), sub('File does not exist: {0}', filePath)); 30 | assert.equal(File.handleFileReadError(permissionsFileErr, filePath), sub('Could not open file: {0}', filePath)); 31 | } 32 | ); 33 | 34 | it( 35 | 'should set handle a file write error', 36 | function() { 37 | var permissionsFileErr = { 38 | code: 'EACCESS' 39 | }; 40 | 41 | assert.equal(File.handleFileWriteError(permissionsFileErr, filePath), sub('Could not write to file: {0}', filePath)); 42 | assert.equal(File.handleFileWriteError(permissionsFileErr, ''), 'Can\'t write to (no file name provided): '); 43 | } 44 | ); 45 | } 46 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/array_spacing_chars.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var STR_ERROR = 'Array items should be separated by exactly one space:'; 11 | 12 | ruleTester.run( 13 | path.basename(__filename, '.js'), 14 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 15 | { 16 | valid: [ 17 | '[1, 2, 3]', 18 | '[1,\n2]' 19 | ], 20 | 21 | invalid: [ 22 | { 23 | code: '[1,2,3]', 24 | errors: [ { message: STR_ERROR + '[1,2,3' } ] 25 | }, 26 | { 27 | code: '[1, 2, 3]', 28 | errors: [ { message: STR_ERROR + '[1, 2' } ] 29 | }, 30 | { 31 | code: '["1", "2", "3"]', 32 | errors: [ { message: STR_ERROR + '["1", "' } ] 33 | }, 34 | { 35 | code: '[\'1\', \'2\', \'3\']', 36 | errors: [ { message: STR_ERROR + '[\'1\', \'' } ] 37 | }, 38 | { 39 | code: '[1, 2, 3]', 40 | errors: [ { message: STR_ERROR + '[1,\\t' } ] 41 | }, 42 | { 43 | code: '["1", "2", "3"]', 44 | errors: [ { message: STR_ERROR + '["1",\\t' } ] 45 | }, 46 | { 47 | code: '[\'1\', \'2\', \'3\']', 48 | errors: [ { message: STR_ERROR + '[\'1\',\\t' } ] 49 | } 50 | ] 51 | } 52 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/array_spacing.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var REGEX = require('../regex'); 4 | 5 | var sub = require('string-sub'); 6 | 7 | var isSingleLine = node => node.loc.start.line === node.loc.end.line; 8 | 9 | module.exports = context => ({ 10 | ArrayExpression(node) { 11 | var source = context.getSource(node); 12 | 13 | if (isSingleLine(node) && REGEX.ARRAY_SURROUNDING_SPACE.test(source)) { 14 | var brackets = []; 15 | var surroundingSpaceTypes = []; 16 | 17 | source.replace( 18 | REGEX.ARRAY_SURROUNDING_SPACE, 19 | (item, index, str) => { 20 | var endIndex = str.length; 21 | var startIndex = 0; 22 | 23 | var leadingSpace = item.indexOf('[') > -1; 24 | 25 | if (leadingSpace) { 26 | endIndex = index + 1; 27 | surroundingSpaceTypes.push('leading'); 28 | } 29 | else { 30 | startIndex = index + 1; 31 | surroundingSpaceTypes.push('trailing'); 32 | brackets.push('...'); 33 | } 34 | 35 | brackets.push(str.substring(startIndex, endIndex)); 36 | 37 | if (leadingSpace) { 38 | brackets.push('...'); 39 | } 40 | } 41 | ); 42 | 43 | brackets = _.uniq(brackets); 44 | 45 | var message = sub('Remove {0} spaces: {1}', surroundingSpaceTypes.join(' and '), brackets.join(' ')); 46 | 47 | context.report(node, message); 48 | } 49 | } 50 | }); -------------------------------------------------------------------------------- /lib/lint_js_rules/no_use_before_define.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | module.exports = context => { 4 | var noUse = require('eslint/lib/rules/no-use-before-define').create; 5 | 6 | var collectedReport = []; 7 | 8 | var options = context.options; 9 | 10 | var sameScope = false; 11 | 12 | if (_.isPlainObject(options[0])) { 13 | sameScope = options[0].samescope; 14 | 15 | options[0] = _.omit(options[0], 'samescope'); 16 | } 17 | 18 | var mockContext = { 19 | report(obj) { 20 | var report = true; 21 | 22 | if (sameScope) { 23 | var scope = mockContext.getScope(); 24 | 25 | scope.references.forEach( 26 | reference => { 27 | if (reference.identifier !== obj.node) { 28 | return; 29 | } 30 | 31 | var variable = reference.resolved; 32 | 33 | report = reference.from.variableScope === variable.scope; 34 | } 35 | ); 36 | } 37 | 38 | if (report) { 39 | collectedReport.push(obj); 40 | } 41 | } 42 | }; 43 | 44 | _.defaults(mockContext, context); 45 | 46 | var defaultRule = noUse(mockContext); 47 | 48 | return _.defaults( 49 | { 50 | 'Program:exit': function(node) { 51 | defaultRule['Program:exit'](node); 52 | 53 | collectedReport.forEach( 54 | (item, index) => { 55 | context.report(item); 56 | } 57 | ); 58 | } 59 | }, 60 | defaultRule 61 | ); 62 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/sort_constants.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var utils = require('../rule_utils'); 3 | 4 | var sub = require('string-sub'); 5 | 6 | var REGEX_UNDERSCORE = /_/g; 7 | 8 | module.exports = context => { 9 | 10 | // Recursive function for collecting identifiers from node 11 | 12 | var getIdentifiers = utils.getIdentifiers; 13 | 14 | var checkSort = node => { 15 | var constants = utils.getConstants(node); 16 | 17 | var prevConstants = []; 18 | 19 | constants.forEach( 20 | (item, index, coll) => { 21 | var prev = coll[index - 1]; 22 | 23 | var itemName = item.id.name; 24 | 25 | if (prev) { 26 | var prevName = prev.id.name; 27 | 28 | var diff = utils.getLineDistance(prev, item); 29 | 30 | if (diff === 2 && prevName.replace(REGEX_UNDERSCORE, '') > itemName.replace(REGEX_UNDERSCORE, '')) { 31 | var identifiers = getIdentifiers(item.init); 32 | 33 | var hasReference = prevConstants.some( 34 | (item, index) => identifiers[item] 35 | ); 36 | 37 | if (!hasReference) { 38 | var message = sub('Sort constants: {0} {1}', prevName, itemName); 39 | 40 | context.report(prev, message); 41 | } 42 | } 43 | } 44 | 45 | prevConstants.push(itemName); 46 | } 47 | ); 48 | }; 49 | 50 | return { 51 | BlockStatement: checkSort, 52 | Program: checkSort 53 | }; 54 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/sort_requires.js: -------------------------------------------------------------------------------- 1 | module.exports = context => ({ 2 | Property(node) { 3 | var nodeValue = node.value; 4 | 5 | if (node.key.name == 'requires' && nodeValue.type == 'ArrayExpression') { 6 | var elements = nodeValue.elements; 7 | 8 | if (elements.length > 1) { 9 | 10 | // I really, really hate having two loops here, 11 | // but I can't think of any way to check only strings 12 | // in an array, allowing for the off chance of non-string values 13 | // ie. if the previous N items are not strings, you'll have to loop 14 | // backwards anyways, but I would like to find a way to do 15 | // this check all in one iteration 16 | 17 | var modules = []; 18 | 19 | elements.forEach( 20 | (item, index) => { 21 | if (item.type == 'Literal' && typeof item.value === 'string') { 22 | modules.push(item.value); 23 | } 24 | } 25 | ); 26 | 27 | var needsSort = []; 28 | 29 | modules.forEach( 30 | (item, index, collection) => { 31 | if (index > 0) { 32 | var prevValue = collection[index - 1]; 33 | 34 | if (item < prevValue) { 35 | needsSort.push(`${prevValue} > ${item}`); 36 | } 37 | } 38 | } 39 | ); 40 | 41 | if (needsSort.length) { 42 | context.report(node, `Sort modules in "requires" array: ${needsSort.join(', ')}`); 43 | } 44 | } 45 | } 46 | } 47 | }); -------------------------------------------------------------------------------- /test/config.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | 3 | var Config = require('../lib/config'); 4 | 5 | chai.use(require('chai-string')); 6 | 7 | var assert = chai.assert; 8 | 9 | describe( 10 | 'Config', 11 | function() { 12 | 'use strict'; 13 | 14 | it( 15 | 'should be a function', 16 | function() { 17 | assert.isTrue(typeof new Config() === 'function'); 18 | } 19 | ); 20 | 21 | it( 22 | 'should exclude _paths property when logging', 23 | function() { 24 | var config = new Config(); 25 | 26 | assert.isTrue(JSON.stringify(config) === '{}', 'Should be an empty object {}'); 27 | assert.isTrue(typeof config._paths === 'object', '_paths should still exists in config'); 28 | } 29 | ); 30 | 31 | it( 32 | 'should return property by key', 33 | function() { 34 | var config = new Config( 35 | { 36 | foo: 1, 37 | bar: function() { 38 | return 'hello world'; 39 | } 40 | } 41 | ); 42 | 43 | assert.isTrue(config('foo') === 1); 44 | assert.isTrue(config('bar') === 'hello world'); 45 | } 46 | ); 47 | 48 | it( 49 | 'should return entire config when called without a key', 50 | function() { 51 | var config = new Config( 52 | { 53 | foo: 1, 54 | bar: function() { 55 | return 'hello world'; 56 | } 57 | } 58 | ); 59 | 60 | assert.isTrue(config() === config); 61 | } 62 | ); 63 | } 64 | ); -------------------------------------------------------------------------------- /test/engine_rules/html_js.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | 3 | chai.use(require('chai-string')); 4 | 5 | var RE = require('../../lib/re'); 6 | 7 | var re = new RE(require('../../lib/rules')); 8 | 9 | var assert = chai.assert; 10 | 11 | describe( 12 | 'HTML JS Rule Engine Tests', 13 | function() { 14 | 'use strict'; 15 | 16 | it( 17 | 'should handle Liferay.Language.get()', 18 | function() { 19 | var rule = re.rules.htmlJS.liferayLanguage; 20 | 21 | var content = 'Liferay.Language.get(\'foo\');'; 22 | 23 | var context = { 24 | content: content 25 | }; 26 | 27 | var result = re.testContent(rule, context); 28 | var lineNum = 1; 29 | 30 | assert.isTrue(result); 31 | assert.startsWith(re.getMessage(result, rule, context), rule.message.split(':')[0]); 32 | assert.equal(content, re.replaceItem(result, rule, context)); 33 | } 34 | ); 35 | 36 | it( 37 | 'should handle Liferay.provide()', 38 | function() { 39 | var rule = re.rules.htmlJS.liferayProvide; 40 | 41 | var content = 'Liferay.provide(window, \'foo\', function() {}, []);'; 42 | 43 | var context = { 44 | asyncAUIScript: true, 45 | content: content 46 | }; 47 | 48 | var result = re.testContent(rule, context); 49 | var lineNum = 1; 50 | 51 | assert.isTrue(result); 52 | assert.startsWith(re.getMessage(result, rule, context), rule.message.split(':')[0]); 53 | assert.equal(content, re.replaceItem(result, rule, context)); 54 | } 55 | ); 56 | } 57 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/sort_constants.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var addES6 = require('../test_utils').addES6(); 11 | 12 | ruleTester.run( 13 | path.basename(__filename, '.js'), 14 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 15 | { 16 | valid: [ 17 | 'var ABC = 123;\n\nvar DEF = 456;', 18 | 'var DEF = 456;\n\nvar ABC = "FOO" + DEF;', 19 | 'var DEF = 456;\n\nvar GHI = 789;\n\nvar ABC = DEF;', 20 | 'var DEF = 456;\n\nvar ABC = some.method[DEF];', 21 | 'var DEF = 456;\n\nvar ABC = {key: DEF};', 22 | 'var DEF = 456;\n\nvar ABC = {key: {someOtherKey: DEF}};', 23 | 'var DEF = function(){};\n\nvar ABC = DEF();', 24 | 'var DEF = function(){};\n\nvar ABC = foo(DEF);' 25 | ].concat( 26 | [ 27 | {code: 'var DEF = "Hello";\n\nvar ABC = {[DEF]: 1};'}, 28 | ].map(addES6) 29 | ), 30 | 31 | invalid: [ 32 | { 33 | code: 'var DEF = 456;\n\nvar ABC = 123;', 34 | errors: [ { message: 'Sort constants: DEF ABC' } ] 35 | }, 36 | { 37 | code: 'var DEF = 456;\n\nvar DEF_XYZ = "FOO";\n\nvar ABC = 123;', 38 | errors: [ { message: 'Sort constants: DEF_XYZ ABC' } ] 39 | }, 40 | { 41 | code: 'var DEF = 456;\n\nvar ABC = "DEF";', 42 | errors: [ { message: 'Sort constants: DEF ABC' } ] 43 | }, 44 | { 45 | code: 'var DEF = 456;\n\nvar ABC;', 46 | errors: [ { message: 'Sort constants: DEF ABC' } ] 47 | } 48 | ] 49 | } 50 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/no_is_prefix.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var STR_ERROR = 'Variable/property names should not start with is*: '; 11 | 12 | ruleTester.run( 13 | path.basename(__filename, '.js'), 14 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 15 | { 16 | valid: [ 17 | 'var isString = A.Lang.isString;', 18 | 'var isString = Lang.isString;', 19 | 'var isString = function(){};', 20 | 'var o = {isString: function(){}}', 21 | 'var o = {isString: A.Lang.isString}', 22 | 'var o = {isString: Lang.isString}', 23 | 'var o = {isFoo: Lang.emptyFn}', 24 | 'var o = {isFoo: Lang.emptyFnTrue}', 25 | 'var o = {isFoo: Lang.emptyFnFalse}' 26 | ], 27 | 28 | invalid: [ 29 | { 30 | code: 'var isString;', 31 | errors: [ { message: STR_ERROR + 'isString' } ] 32 | }, 33 | { 34 | code: 'var isString = 1;', 35 | errors: [ { message: STR_ERROR + 'isString' } ] 36 | }, 37 | { 38 | code: 'var o = {isString: 1}', 39 | errors: [ { message: STR_ERROR + 'isString' } ] 40 | }, 41 | { 42 | code: 'var o = {isString: isFoo}', 43 | errors: [ { message: STR_ERROR + 'isString' } ] 44 | }, 45 | { 46 | code: 'function foo(isString){}', 47 | errors: [ { message: STR_ERROR + 'isString' } ] 48 | }, 49 | { 50 | code: 'var x = function(isString){}', 51 | errors: [ { message: STR_ERROR + 'isString' } ] 52 | } 53 | ] 54 | } 55 | ); -------------------------------------------------------------------------------- /lib/lint_css.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var glob = require('glob'); 3 | var path = require('path'); 4 | var stylelint = require('stylelint'); 5 | 6 | var STYLELINT_CONFIG = require('./config/stylelint'); 7 | 8 | var ruleUtils = require('./rule_utils'); 9 | 10 | var customRules = {}; 11 | 12 | var rootDir = path.resolve(__dirname, '..'); 13 | 14 | var runLinter = (contents, file, context) => { 15 | var customRules = context.customRules || {}; 16 | 17 | _.merge(stylelint.rules, customRules); 18 | 19 | // console.log(stylelint.rules); 20 | 21 | var config = context.lintConfig; 22 | 23 | var configs = [{}, STYLELINT_CONFIG]; 24 | 25 | if (_.isObject(config)) { 26 | configs.push(config); 27 | } 28 | 29 | config = _.merge(...configs); 30 | 31 | return stylelint.lint( 32 | { 33 | code: contents, 34 | codeFileName: file, 35 | config, 36 | configBasedir: rootDir, 37 | formatter: 'json', 38 | fix: context.fix, 39 | syntax: 'scss' 40 | } 41 | ); 42 | }; 43 | 44 | var globOptions = { 45 | cwd: __dirname 46 | }; 47 | 48 | module.exports = (contents, file, context) => { 49 | context.customRules = customRules; 50 | var plugins = STYLELINT_CONFIG.plugins; 51 | 52 | glob.sync( 53 | './lint_css_rules/*.js', 54 | globOptions 55 | ).forEach( 56 | (item, index) => { 57 | var id = ruleUtils.getRuleId(item); 58 | 59 | customRules[id] = require(item); 60 | 61 | plugins.push(path.resolve(rootDir, './lib', item)); 62 | } 63 | ); 64 | 65 | return runLinter(contents, file, context); 66 | }; 67 | 68 | module.exports.stylelint = stylelint; 69 | module.exports.linter = stylelint.linter; 70 | module.exports.runLinter = runLinter; -------------------------------------------------------------------------------- /test/deprecation.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | var chai = require('chai'); 4 | var _ = require('lodash'); 5 | 6 | var deprecationCheck = require('../lib/deprecation'); 7 | 8 | chai.use(require('chai-string')); 9 | 10 | var assert = chai.assert; 11 | 12 | var INTERVAL = deprecationCheck.INTERVAL; 13 | 14 | var configStore = { 15 | get(key) { 16 | return this[key]; 17 | }, 18 | 19 | set(key, value) { 20 | this[key] = value; 21 | } 22 | }; 23 | 24 | var expectedMessage = `----------------------------------------------------------- 25 | Using the check_sf form of this module is deprecated. 26 | Please use the csf command instead. It's easier to type too!`; 27 | 28 | describe( 29 | 'deprecation checking', 30 | function () { 31 | 'use strict'; 32 | 33 | it( 34 | 'should return a deprecation message', 35 | function() { 36 | var config = Object.create(configStore); 37 | 38 | config.set('lastDeprecationCheck', Date.now() - INTERVAL); 39 | 40 | var message = deprecationCheck( 41 | { 42 | config, 43 | scriptName: 'check_sf' 44 | } 45 | ); 46 | 47 | assert.isString(message); 48 | assert.isAbove(message.length, 0); 49 | assert.equal(message, expectedMessage); 50 | } 51 | ); 52 | 53 | it( 54 | 'should not return a deprecation message', 55 | function() { 56 | var config = Object.create(configStore); 57 | 58 | config.set('lastDeprecationCheck', Date.now()); 59 | 60 | var message = deprecationCheck( 61 | { 62 | config, 63 | scriptName: 'check_sf' 64 | } 65 | ); 66 | 67 | assert.isString(message); 68 | assert.equal(message.length, 0); 69 | } 70 | ); 71 | } 72 | ); -------------------------------------------------------------------------------- /test/lint_css_rules/at_rule_empty_line.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var ruleUtils = require('../../lib/rule_utils'); 3 | var testRule = require('../stylelint_rule_tester'); 4 | 5 | var stylelint = testRule.stylelint; 6 | 7 | var rule = require('../../lib/lint_css_rules/' + path.basename(__filename)); 8 | var sub = require('string-sub'); 9 | 10 | var stylelintRule = require('stylelint/lib/rules/at-rule-empty-line-before'); 11 | 12 | var path = require('path'); 13 | var fs = require('fs'); 14 | var chai = require('chai'); 15 | var _ = require('lodash'); 16 | 17 | chai.use(require('chai-string')); 18 | 19 | var assert = chai.assert; 20 | 21 | var ruleName = rule.ruleName; 22 | 23 | rule = rule.rule; 24 | 25 | testRule( 26 | rule, 27 | { 28 | ruleName: ruleName, 29 | config: ['always', {except: ['first-nested']}], 30 | accept: [ 31 | { 32 | code: `a { 33 | @include foo; 34 | color: pink; 35 | }`, 36 | description: 'always at rule empty line no first-nested' 37 | }, 38 | { 39 | code: ` 40 | @if $foo != null { 41 | div { 42 | color: blue; 43 | } 44 | } 45 | @else { 46 | div { 47 | color: red; 48 | } 49 | } 50 | `, 51 | description: 'ignore @else blocks' 52 | } 53 | ], 54 | reject: [ 55 | { 56 | code: `a { 57 | color: blue; 58 | @include foo; 59 | }`, 60 | message: stylelintRule.messages.expected 61 | } 62 | ], 63 | syntax: 'scss', 64 | } 65 | ); 66 | 67 | testRule( 68 | rule, 69 | { 70 | description: 'should handle invalid options', 71 | config: [], 72 | skipBasicChecks: true, 73 | reject: [ 74 | { 75 | code: 'a {\n}', 76 | message: 'Expected option value for rule "' + ruleName + '"' 77 | } 78 | ], 79 | } 80 | ); -------------------------------------------------------------------------------- /lib/formatter.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | 4 | require('./css'); 5 | require('./html'); 6 | require('./js'); 7 | 8 | var Formatter = module.exports = require('content-formatter'); 9 | 10 | var Config = require('./config'); 11 | var re = require('./re'); 12 | 13 | var RULES = require('./rules'); 14 | 15 | var minimatch = require('minimatch'); 16 | 17 | var configCache = {}; 18 | 19 | Formatter.prototype.config = function(key) { 20 | var abspath = this._abspath; 21 | 22 | if (!abspath) { 23 | abspath = path.resolve(this._config._paths.cwd, this.file); 24 | 25 | this._abspath = abspath; 26 | } 27 | 28 | var configObj = configCache[abspath]; 29 | 30 | if (!configObj) { 31 | var config = this._config; 32 | 33 | var paths = config._paths; 34 | 35 | var configs = paths.configs; 36 | 37 | var filteredConfigs = _.reduce( 38 | paths.keys, 39 | (prev, item, index) => { 40 | if (minimatch(abspath, item)) { 41 | prev.push(configs[index]); 42 | } 43 | 44 | return prev; 45 | }, 46 | [] 47 | ); 48 | 49 | if (filteredConfigs.length) { 50 | filteredConfigs.unshift(new Config(), config); 51 | 52 | configObj = _.merge(...filteredConfigs); 53 | 54 | delete configObj._paths; 55 | } 56 | else { 57 | configObj = config; 58 | } 59 | 60 | configCache[abspath] = configObj; 61 | } 62 | 63 | return configObj(key); 64 | }; 65 | 66 | Formatter.on( 67 | 'init', 68 | instance => { 69 | instance._config = new Config(); 70 | 71 | var ruleInstance = new re(RULES); 72 | 73 | instance._re = ruleInstance; 74 | 75 | instance.proxyEvent('message', ['re'], ruleInstance); 76 | 77 | instance.on( 78 | 're:message', 79 | data => { 80 | instance.log(data.context.lineNum, data.message); 81 | } 82 | ); 83 | } 84 | ); -------------------------------------------------------------------------------- /lib/argv.js: -------------------------------------------------------------------------------- 1 | var optimist = require('optimist') 2 | .usage('Usage: $0 -qo') 3 | .options( 4 | { 5 | config: { 6 | default: true, 7 | string: true 8 | }, 9 | 'display-raw': { 10 | boolean: true, 11 | default: false 12 | }, 13 | f: { 14 | alias: 'force', 15 | boolean: true, 16 | default: false 17 | }, 18 | filenames: { 19 | boolean: true, 20 | default: false 21 | }, 22 | h: { 23 | alias: 'help', 24 | boolean: true, 25 | default: false 26 | }, 27 | 'fail-on-errors': { 28 | boolean: true, 29 | default: false 30 | }, 31 | i: { 32 | alias: 'inline-edit', 33 | boolean: true, 34 | default: false 35 | }, 36 | l: { 37 | alias: 'lint', 38 | boolean: true, 39 | default: true 40 | }, 41 | 'lint-ids': { 42 | boolean: true, 43 | default: false 44 | }, 45 | m: { 46 | alias: 'check-metadata', 47 | boolean: true, 48 | default: false 49 | }, 50 | o: { 51 | alias: 'open', 52 | boolean: true, 53 | default: false 54 | }, 55 | q: { 56 | alias: 'quiet', 57 | boolean: true, 58 | default: false 59 | }, 60 | r: { 61 | alias: 'relative', 62 | boolean: true, 63 | default: false 64 | }, 65 | 'show-columns': { 66 | boolean: true, 67 | default: false 68 | }, 69 | v: { 70 | alias: 'verbose', 71 | boolean: true, 72 | default: false 73 | }, 74 | V: { 75 | alias: 'version', 76 | boolean: true, 77 | default: false 78 | } 79 | } 80 | ); 81 | 82 | var argv = optimist.argv; 83 | 84 | /* istanbul ignore next */ 85 | if (argv.h || argv.V) { 86 | /* eslint-disable */ 87 | 88 | if (argv.h) { 89 | optimist.showHelp(); 90 | } 91 | else if (argv.V) { 92 | console.log(require('../package.json').version); 93 | } 94 | 95 | process.exit(); 96 | } 97 | 98 | module.exports = argv; -------------------------------------------------------------------------------- /lib/lint_js_rules/liferay_provide_format.js: -------------------------------------------------------------------------------- 1 | module.exports = context => ({ 2 | CallExpression(node) { 3 | var callee = node.callee; 4 | 5 | if (callee.type === 'MemberExpression' && callee.object.name === 'Liferay' && callee.property.name === 'provide') { 6 | var args = node.arguments; 7 | 8 | if (args.length < 4) { 9 | context.report(node, 'Missing dependencies (don\'t use Liferay.provide to create regular functions).'); 10 | } 11 | else { 12 | var arg0 = args[0]; 13 | var arg1 = args[1]; 14 | var arg2 = args[2]; 15 | var arg3 = args[3]; 16 | 17 | var arg0Type = arg0.type; 18 | var arg1Type = arg1.type; 19 | var arg2Type = arg2.type; 20 | var arg3Type = arg3.type; 21 | 22 | if (arg0Type !== 'Identifier' && arg0Type !== 'MemberExpression') { 23 | context.report(arg0, 'Liferay.provide expects an object as the first argument.'); 24 | } 25 | 26 | if (arg1Type !== 'Identifier' && typeof arg1.value !== 'string') { 27 | context.report(arg1, 'Liferay.provide expects a string as the second argument.'); 28 | } 29 | 30 | if (arg2Type !== 'Identifier' && arg2Type !== 'FunctionExpression') { 31 | context.report(arg2, 'Liferay.provide expects a function as the third argument.'); 32 | } 33 | 34 | var arg3TypeExpression = arg3Type === 'CallExpression'; 35 | 36 | if (arg3Type !== 'Identifier' && arg3Type !== 'ArrayExpression' && !arg3TypeExpression || arg3TypeExpression && arg3.callee.object.type !== 'ArrayExpression') { 37 | context.report(arg3, 'Liferay.provide expects an array as the last argument.'); 38 | } 39 | else if (arg3Type === 'ArrayExpression' && !arg3.elements.length) { 40 | context.report(arg3, 'Liferay.provide dependencies should have at least one dependency.'); 41 | } 42 | } 43 | } 44 | } 45 | }); -------------------------------------------------------------------------------- /test/re.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var chai = require('chai'); 3 | var sinon = require('sinon'); 4 | 5 | chai.use(require('chai-string')); 6 | 7 | var assert = chai.assert; 8 | 9 | var RE = require('../lib/re'); 10 | 11 | var re = new RE(require('../lib/rules')); 12 | 13 | describe( 14 | 're.js', 15 | function() { 16 | 'use strict'; 17 | 18 | beforeEach( 19 | function() { 20 | sinon.createSandbox(); 21 | } 22 | ); 23 | 24 | afterEach( 25 | function() { 26 | sinon.restore(); 27 | } 28 | ); 29 | 30 | it( 31 | 'should find extra newlines at beginning', 32 | function() { 33 | var startingNewLine = ['', 'foo']; 34 | 35 | startingNewLine.forEach( 36 | function(item, index, collection) { 37 | var result = re.hasExtraNewLines(item, index, collection); 38 | var loggerResult = re.hasExtraNewLines(item, index, collection); 39 | 40 | if (index === 0) { 41 | assert.isTrue(result); 42 | assert.isTrue(loggerResult); 43 | } 44 | else { 45 | assert.isFalse(result); 46 | assert.isFalse(loggerResult); 47 | } 48 | } 49 | ); 50 | } 51 | ); 52 | 53 | it( 54 | 'should find extra newlines at end', 55 | function() { 56 | var endingNewLine = ['foo', '']; 57 | 58 | endingNewLine.forEach( 59 | function(item, index, collection) { 60 | var result = re.hasExtraNewLines(item, index, collection); 61 | var loggerResult = re.hasExtraNewLines(item, index, collection); 62 | 63 | if (index === 0) { 64 | assert.isFalse(result); 65 | assert.isFalse(loggerResult); 66 | } 67 | else if (index === collection.length - 1) { 68 | assert.isTrue(result); 69 | assert.isTrue(loggerResult); 70 | } 71 | } 72 | ); 73 | } 74 | ); 75 | } 76 | ); -------------------------------------------------------------------------------- /lib/engine_rules/common.js: -------------------------------------------------------------------------------- 1 | var colors = require('cli-color-keywords')(); 2 | 3 | var MAP_WHITESPACE = { 4 | '\x95': '', 5 | '\x99': '', 6 | '\xa0': ' ' 7 | }; 8 | 9 | var REGEX_WHITESPACE_CHARS = new RegExp(`(${Object.keys(MAP_WHITESPACE).join('|')})`, 'g'); 10 | 11 | module.exports = { 12 | extraneousSpaces: { 13 | message: 'Extraneous whitespace at the end of the line', 14 | regex: /\s+$/, 15 | replacer(result, rule, context) { 16 | return ''; 17 | }, 18 | test(content, regex) { 19 | var invalid = this.test(content, regex); 20 | 21 | return invalid; 22 | }, 23 | testProp: 'rawContent' 24 | }, 25 | 26 | mixedSpaces: { 27 | message: 'Mixed spaces and tabs: {1}', 28 | regex: /^.*( \t|\t ).*$/, 29 | replacer(result, rule, context) { 30 | var rawContent = context.rawContent; 31 | 32 | rawContent = rawContent.replace( 33 | /(.*)( +\t|\t +)(.*)/g, 34 | (str, prefix, problem, suffix) => { 35 | problem = problem.replace(/ {4}| {2}/g, '\t').replace(/ /g, ''); 36 | 37 | return prefix + problem + suffix; 38 | } 39 | ); 40 | 41 | return rawContent; 42 | }, 43 | testProp: 'rawContent' 44 | }, 45 | 46 | invalidWhiteSpace: { 47 | MSG: 'Invalid whitespace characters', 48 | message(result, rule, context) { 49 | var content = context.content; 50 | 51 | var displayedContent = content.replace( 52 | new RegExp(rule.regex.source, 'g'), 53 | colors.bgError('$1') 54 | ); 55 | 56 | context.content = displayedContent; 57 | 58 | return this.message(`${rule.MSG}: {1}`, result, rule, context); 59 | }, 60 | regex: REGEX_WHITESPACE_CHARS, 61 | replacer(result, rule, context) { 62 | var rawContent = context.rawContent; 63 | 64 | return rawContent.replace( 65 | rule.regex, 66 | (str, m) => MAP_WHITESPACE[m] 67 | ); 68 | }, 69 | testProp: 'rawContent' 70 | }, 71 | _MAP_WHITESPACE: MAP_WHITESPACE 72 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/no_unused_vars.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var base = require('../base'); 4 | var stubs = base.stubs; 5 | 6 | var STUB_RE = new RegExp(`^${Object.keys(stubs).join('|')}`); 7 | 8 | module.exports = { 9 | create(context) { 10 | function useInstance(node) { 11 | context.markVariableAsUsed('instance'); 12 | } 13 | 14 | var lintRules = { 15 | 'ReturnStatement': useInstance, 16 | 'VariableDeclaration': useInstance 17 | }; 18 | 19 | var options = context.options; 20 | 21 | if (options.length && options[0].jsp === true) { 22 | var noUnused = require('eslint/lib/rules/no-unused-vars').create; 23 | 24 | var collectedReport = []; 25 | 26 | var mockContext = { 27 | report(obj) { 28 | collectedReport.push(obj); 29 | }, 30 | options: [ 31 | { 32 | 'args': 'none', 33 | 'vars': 'local' 34 | } 35 | ] 36 | }; 37 | 38 | _.defaults(mockContext, context); 39 | 40 | mockContext.getSourceCode = context.getSourceCode; 41 | 42 | lintRules['Program:exit'] = node => { 43 | noUnused(mockContext)['Program:exit'](node); 44 | 45 | collectedReport.forEach( 46 | (item, index) => { 47 | var declaration = item.node; 48 | var name = declaration.name; 49 | 50 | var namespacedVar = STUB_RE.test(name); 51 | 52 | var namespacedFn = false; 53 | 54 | if (namespacedVar) { 55 | var parentType = declaration.parent.type; 56 | 57 | if (parentType === 'FunctionDeclaration' || 58 | (parentType === 'VariableDeclarator' && declaration.parent.init && declaration.parent.init.type === 'FunctionExpression') 59 | ) { 60 | namespacedFn = true; 61 | } 62 | } 63 | 64 | if (!stubs[name] && !namespacedVar || (namespacedVar && !namespacedFn)) { 65 | context.report(item); 66 | } 67 | } 68 | ); 69 | }; 70 | } 71 | 72 | return lintRules; 73 | } 74 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/liferay_provide_format.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | ruleTester.run( 11 | path.basename(__filename, '.js'), 12 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 13 | { 14 | valid: [ 15 | 'Liferay.provide(window, "foo", function(){}, ["dep"])', 16 | 'Liferay.provide(Liferay.Util, "foo", function(){}, ["dep"])', 17 | 'Liferay.provide(window, str, function(){}, ["dep"])', 18 | 'Liferay.provide(window, "foo", fooFn, ["dep"])', 19 | 'Liferay.provide(window, "foo", fooFn, ["dep"].concat("foo"))', 20 | ], 21 | 22 | invalid: [ 23 | { 24 | code: 'Liferay.provide(window, "foo", function(){})', 25 | errors: [ { message: 'Missing dependencies (don\'t use Liferay.provide to create regular functions).' } ] 26 | }, 27 | { 28 | code: 'Liferay.provide(null, "foo", function(){}, ["dep"])', 29 | errors: [ { message: 'Liferay.provide expects an object as the first argument.' } ] 30 | }, 31 | { 32 | code: 'Liferay.provide(window, null, function(){}, ["dep"])', 33 | errors: [ { message: 'Liferay.provide expects a string as the second argument.' } ] 34 | }, 35 | { 36 | code: 'Liferay.provide(window, "foo", {}, ["dep"])', 37 | errors: [ { message: 'Liferay.provide expects a function as the third argument.' } ] 38 | }, 39 | { 40 | code: 'Liferay.provide(window, "foo", function(){}, "foo")', 41 | errors: [ { message: 'Liferay.provide expects an array as the last argument.' } ] 42 | }, 43 | { 44 | code: 'Liferay.provide(window, "foo", function(){}, "foo".toLowerCase())', 45 | errors: [ { message: 'Liferay.provide expects an array as the last argument.' } ] 46 | }, 47 | { 48 | code: 'Liferay.provide(window, "foo", function(){}, [])', 49 | errors: [ { message: 'Liferay.provide dependencies should have at least one dependency.' } ] 50 | } 51 | ] 52 | } 53 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/no_is_prefix.js: -------------------------------------------------------------------------------- 1 | var REGEX = require('../regex'); 2 | 3 | var sub = require('string-sub'); 4 | 5 | module.exports = context => { 6 | var checkProcessVars = node => { 7 | var value = node.value; 8 | var valueType = value.type; 9 | 10 | var propValueIdentifier = (valueType === 'Identifier'); 11 | var propValueMemberExp = (valueType === 'MemberExpression'); 12 | 13 | var processVars = true; 14 | 15 | if (propValueMemberExp || propValueIdentifier) { 16 | var valName = value.name; 17 | 18 | if (propValueMemberExp) { 19 | valName = value.property.name; 20 | } 21 | 22 | processVars = (valName !== node.key.name) && !(REGEX.LANG_EMPTYFN.test(context.getSource(value))); 23 | } 24 | 25 | return processVars; 26 | }; 27 | 28 | var testVarNames = (varName, node) => { 29 | var pass = true; 30 | 31 | if (REGEX.VAR_IS.test(varName)) { 32 | context.report(node, sub('Variable/property names should not start with is*: {0}', varName)); 33 | pass = false; 34 | } 35 | 36 | return pass; 37 | }; 38 | 39 | var testFunctionParams = node => { 40 | var params = node.params; 41 | 42 | params.forEach( 43 | (item, index) => { 44 | testVarNames(item.name, node); 45 | } 46 | ); 47 | }; 48 | 49 | return { 50 | FunctionExpression: testFunctionParams, 51 | 52 | FunctionDeclaration: testFunctionParams, 53 | 54 | Property(node) { 55 | if (node.value.type !== 'FunctionExpression') { 56 | var processVars = checkProcessVars(node); 57 | 58 | if (processVars) { 59 | testVarNames(node.key.name, node); 60 | } 61 | } 62 | }, 63 | 64 | VariableDeclaration(node) { 65 | node.declarations.forEach( 66 | (item, index) => { 67 | var process = true; 68 | 69 | var varName = item.id.name; 70 | 71 | var init = item.init; 72 | 73 | if (init) { 74 | var itemType = init.type; 75 | 76 | if (itemType === 'FunctionExpression' || 77 | (itemType === 'MemberExpression' && init.property.name === varName)) { 78 | 79 | process = false; 80 | } 81 | } 82 | 83 | if (process) { 84 | testVarNames(varName, node); 85 | } 86 | } 87 | ); 88 | } 89 | }; 90 | }; -------------------------------------------------------------------------------- /lib/engine_rules/js.js: -------------------------------------------------------------------------------- 1 | var sub = require('string-sub'); 2 | 3 | var REGEX = require('../regex'); 4 | 5 | module.exports = { 6 | IGNORE: /^(\t| )*(\*|\/\/)/, 7 | 8 | elseFormat: { 9 | message: false, 10 | regex: /^(\s+)?\} ?(else|catch|finally)/, 11 | replacer: '$1}\n$1$2', 12 | test: 'match', 13 | testProp: 'rawContent' 14 | }, 15 | 16 | invalidArgumentFormat: { 17 | message: 'These arguments should each be on their own line: {1}', 18 | regex: /(\w+)\((?!(?:$|.*?\);?))/, 19 | test(content, regex) { 20 | var invalid = false; 21 | 22 | content.replace( 23 | regex, 24 | (str, fnName) => { 25 | if (fnName !== 'function') { 26 | invalid = true; 27 | } 28 | } 29 | ); 30 | 31 | return invalid; 32 | } 33 | }, 34 | 35 | invalidConditional: { 36 | message(result, rule, context) { 37 | var message = 'Needs a space between ")" and "{bracket}": {1}'; 38 | 39 | return sub(this.message(message, result, rule, context), rule._bracket); 40 | }, 41 | regex: /\)\{(?!\})/, 42 | replacer: ') {', 43 | test(content, regex) { 44 | content = content.replace(REGEX.REGEX, ''); 45 | 46 | return regex.test(content); 47 | }, 48 | _bracket: { 49 | bracket: '{' 50 | } 51 | }, 52 | 53 | invalidFunctionFormat: { 54 | message: 'Anonymous function expressions should be formatted as function(: {1}', 55 | regex: /function\s+\(/, 56 | replacer: 'function(' 57 | }, 58 | 59 | keywordFormat: { 60 | message: false, 61 | regex: /\s*(?=6" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/natecavanaugh/check-source-formatting" 22 | }, 23 | "preferGlobal": "true", 24 | "dependencies": { 25 | "babel-eslint": "^8.0.3", 26 | "bluebird": "^3.5.1", 27 | "cli": "^1.0.0", 28 | "cli-color-keywords": "0.0.1", 29 | "configstore": "^3.1.1", 30 | "content-formatter": "^2.0.2", 31 | "content-logger": "^2.0.0", 32 | "content-logger-handlebars-helpers": "0.0.3", 33 | "cosmiconfig": "^5.0.5", 34 | "drip": "^1.4.0", 35 | "eslint": "^4.13.1", 36 | "eslint-plugin-react": "^7.5.1", 37 | "eslint-plugin-sort-imports-es6-autofix": "^0.3.0", 38 | "falafel": "^1.2.0", 39 | "getobject": "^0.1.0", 40 | "glob": "^7.1.2", 41 | "lodash": "^4.15.0", 42 | "lodash-bindright": "^1.0.1", 43 | "lodash-namespace": "^1.0.0", 44 | "minimatch": "^3.0.4", 45 | "optimist": "^0.6.1", 46 | "roolz": "^1.0.2", 47 | "string-sub": "0.0.1", 48 | "stylelint": "^9.2.1", 49 | "stylelint-order": "^0.8.0", 50 | "update-notifier": "^2.3.0" 51 | }, 52 | "devDependencies": { 53 | "chai": "^4.1.2", 54 | "chai-string": "^1.4.0", 55 | "coveralls": "^3.0.0", 56 | "eslint-plugin-dollar-sign": "^1.0.1", 57 | "gulp": "^3.9.1", 58 | "gulp-complexity": "^0.3.2", 59 | "gulp-coveralls": "^0.1.4", 60 | "gulp-debug": "^4.0.0", 61 | "gulp-doctoc": "^0.1.4", 62 | "gulp-istanbul": "^1.1.2", 63 | "gulp-load-plugins": "^1.2.4", 64 | "gulp-mocha": "^6.0.0", 65 | "gulp-rename": "^1.2.2", 66 | "gulp-template": "^5.0.0", 67 | "harmonize": "^2.0.0", 68 | "inquirer": "^6.0.0", 69 | "istanbul": "^0.4.4", 70 | "mocha": "^5.2.0", 71 | "run-sequence": "^2.2.0", 72 | "sinon": "^6.0.0", 73 | "xsd-schema-validator": "^0.6.0" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/lint_js_rules/function_spacing.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var STR_END_ERROR = 'There should be exactly one line between the last statement and the end of the function, not '; 11 | 12 | var STR_START_ERROR = 'There should be exactly one line between the start of the function and the first statement, not '; 13 | 14 | ruleTester.run( 15 | path.basename(__filename, '.js'), 16 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 17 | { 18 | valid: [ 19 | 'function foo() {}', 20 | 'function foo() {\n}', 21 | 'function foo() {\nalert("test");\n}', 22 | 'function foo() {\n/*Test*/\nalert("test");\n}', 23 | 'function foo() {\nalert("test");\n/*Test*/\n}', 24 | 'function foo() {\n/*Test*/\nalert("test");\n/*Test*/\n}', 25 | 'function foo() {\n// Test\nalert("test");\n// Test\n}', 26 | ], 27 | 28 | invalid: [ 29 | { 30 | code: 'function foo() {\n\nalert("test");\n}', 31 | errors: [ { message: STR_START_ERROR + '2 lines' } ] 32 | }, 33 | { 34 | code: 'function foo() {\nalert("test");\n\n}', 35 | errors: [ { message: STR_END_ERROR + '2 lines' } ] 36 | }, 37 | { 38 | code: 'function foo() {alert("test");\n}', 39 | errors: [ { message: STR_START_ERROR + '0 lines' } ] 40 | }, 41 | { 42 | code: 'function foo() {\nalert("test");}', 43 | errors: [ { message: STR_END_ERROR + '0 lines' } ] 44 | }, 45 | { 46 | code: 'function foo() {\n/*Test*/\n\nalert("test");\n}', 47 | errors: [ { message: STR_START_ERROR + '2 lines' } ] 48 | }, 49 | { 50 | code: 'function foo() {\n\n/*Test*/\nalert("test");\n}', 51 | errors: [ { message: STR_START_ERROR + '2 lines' } ] 52 | }, 53 | { 54 | code: 'function foo() {\nalert("test");\n/*Test*/\n\n}', 55 | errors: [ { message: STR_END_ERROR + '2 lines' } ] 56 | }, 57 | // { 58 | // code: 'function foo() {\n/*Test*/\n\n/*Test*/\nalert("test");\n}', 59 | // errors: [ { message: STR_START_ERROR + '2 lines' } ] 60 | // }, 61 | // { 62 | // code: 'function foo() {\nalert("test");\n/*Test*/\n\n/*Test*/\n}', 63 | // errors: [ { message: STR_END_ERROR + '2 lines' } ] 64 | // } 65 | ] 66 | } 67 | ); -------------------------------------------------------------------------------- /test/lint_js_rules/no_multiple_return.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var addES6 = require('../test_utils').addES6(); 11 | 12 | ruleTester.run( 13 | path.basename(__filename, '.js'), 14 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 15 | { 16 | valid: [ 17 | 'function foo() {return 1;}', 18 | 'function foo() {}', 19 | 'function foo() {bar();}', 20 | 'function foo() {var f = function(){return 1;}; return f();}', 21 | 'function foo() {var f = function(){return 1;};}' 22 | ].concat( 23 | [ 24 | { code: 'var foo = () => {return 1;}' }, 25 | { code: 'var foo = () => {}' }, 26 | { code: 'var foo = () => {bar();}' }, 27 | { code: 'var foo = () => {var f = n => {return 1}; return f();}' }, 28 | { code: 'var foo = () => {var f = n => {return 1};}' }, 29 | 30 | ].map(addES6) 31 | ), 32 | 33 | invalid: [ 34 | { 35 | code: 'function foo() {if (x) {return x;} return;}', 36 | errors: [ { message: 'Functions should only have one return statement' } ] 37 | }, 38 | { 39 | code: 'function foo() {if (x) {return x;} else {return 1;}}', 40 | errors: [ { message: 'Functions should only have one return statement' } ] 41 | }, 42 | { 43 | code: 'function foo() {return 1; return 2;}', 44 | errors: [ { message: 'Functions should only have one return statement' } ] 45 | }, 46 | { 47 | code: 'function foo() {var f = function(){if (foo) {return foo;} return 1;};}', 48 | errors: [ { message: 'Functions should only have one return statement' } ] 49 | } 50 | ].concat( 51 | [ 52 | { 53 | code: 'var foo = () => {if (x) {return x;} return;}', 54 | errors: [ { message: 'Functions should only have one return statement' } ] 55 | }, 56 | { 57 | code: 'var foo = () => {if (x) {return x;} else {return 1;}}', 58 | errors: [ { message: 'Functions should only have one return statement' } ] 59 | }, 60 | { 61 | code: 'var foo = () => {return 1; return 2;}', 62 | errors: [ { message: 'Functions should only have one return statement' } ] 63 | }, 64 | { 65 | code: 'var foo = () => {var f = () => {if (foo) {return foo;} return 1;};}', 66 | errors: [ { message: 'Functions should only have one return statement' } ] 67 | } 68 | ] 69 | ).map(addES6) 70 | } 71 | ); -------------------------------------------------------------------------------- /test/fixture/test.jsp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Check Source Formatting 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 |
13 |
">
14 | " /> 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 29 | 30 | 31 | var testVar = true; 32 | 33 | 34 | 35 | var Liferay = true 36 | 37 | Liferay.Language.get('foo'); 38 | 39 | Liferay.provide( 40 | window, 41 | 'testFn', 42 | function() { 43 | var foo = false; 44 | } 45 | ); 46 | 47 | 48 | 49 | <% 50 | List foo = null; 51 | %> 52 | 53 | foo(); 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 67 | 68 | 69 | 70 | 71 |
72 | 73 | "> 74 | 75 | 79 | 80 | 81 | window.foo = 'foo'; 82 | 83 | 84 | var SOME_OBJ = { 85 | '${foo}': 'bar', 86 | '${bar}': 'baz' 87 | }; 88 | 89 | 90 | 91 | alert(fooBarBaz); 92 | alert(bazFoo_bar); 93 | alert(FooBar); 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /lib/junit.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var Handlebars = require('content-logger-handlebars-helpers')(); 5 | var path = require('path'); 6 | var Promise = require('bluebird'); 7 | 8 | var fs = Promise.promisifyAll(require('fs')); 9 | 10 | var Logger = require('./logger'); 11 | 12 | class JUnitReporter { 13 | constructor(config) { 14 | this.flags = config.flags || {}; 15 | this.logger = config.logger || Logger; 16 | this.read = config.read || fs.readFileAsync.bind(fs); 17 | this.write = config.write || fs.writeFileAsync.bind(fs); 18 | } 19 | 20 | generate() { 21 | return this.read(this.TPL_PATH, 'utf-8').then(this.onRead.bind(this)); 22 | } 23 | 24 | getContext() { 25 | const flags = this.flags; 26 | const logger = this.logger; 27 | 28 | const fileErrors = logger.getErrors(); 29 | const testStats = logger.testStats; 30 | 31 | const result = { 32 | files: [], 33 | showLintIds: flags['lint-ids'], 34 | stats: testStats 35 | }; 36 | 37 | _.forEach( 38 | fileErrors, 39 | (fileErrors, fileName) => { 40 | const errors = []; 41 | 42 | fileErrors = _.reject( 43 | fileErrors, 44 | { 45 | type: 'ignored' 46 | } 47 | ); 48 | 49 | _.forEach( 50 | _.groupBy(fileErrors, 'type'), 51 | (violations, violationType) => { 52 | errors.push( 53 | { 54 | failure: { 55 | msg: violationType, 56 | stack: violations 57 | }, 58 | testName: violationType 59 | } 60 | ); 61 | } 62 | ); 63 | 64 | const fileResult = { 65 | errors, 66 | file: fileName, 67 | stats: { 68 | failures: fileErrors.length 69 | } 70 | }; 71 | 72 | result.files.push(fileResult); 73 | } 74 | ); 75 | 76 | return result; 77 | } 78 | 79 | getOutputPath() { 80 | let outputPath = this.flags.junit; 81 | 82 | if (!_.isString(outputPath)) { 83 | outputPath = 'result.xml'; 84 | } 85 | 86 | return outputPath; 87 | } 88 | 89 | onRead(result) { 90 | const context = this.getContext(); 91 | const outputPath = this.getOutputPath(); 92 | 93 | const xml = this.renderTPL(result, context); 94 | 95 | return this.write(outputPath, xml); 96 | } 97 | 98 | renderTPL(tpl, context) { 99 | const xmlTpl = Handlebars.compile(tpl); 100 | 101 | return xmlTpl(context); 102 | } 103 | } 104 | 105 | JUnitReporter.prototype.TPL_PATH = path.join(__dirname, 'tpl', 'junit_report.tpl'); 106 | 107 | module.exports = JUnitReporter; -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var cosmiconfig = require('cosmiconfig'); 5 | 6 | var CONFIG_DEFAULT = { 7 | _paths: { 8 | configs: [], 9 | keys: [] 10 | } 11 | }; 12 | 13 | class Config { 14 | constructor(obj) { 15 | this._config = this._normalize(obj); 16 | 17 | return this._config; 18 | } 19 | 20 | toJSON() { 21 | return _.omit(this._config, '_paths'); 22 | } 23 | 24 | _get(key) { 25 | return key ? _.result(this._config, key) : this._config; 26 | } 27 | 28 | _normalize(obj) { 29 | const fn = this._get.bind(this); 30 | 31 | Object.defineProperty( 32 | fn, 33 | 'toJSON', 34 | { 35 | enumerable: false, 36 | value: _.bindKey(this, 'toJSON') 37 | } 38 | ); 39 | 40 | Object.defineProperty( 41 | fn, 42 | 'toString', 43 | { 44 | enumerable: false, 45 | value: JSON.stringify.bind(JSON, fn) 46 | } 47 | ); 48 | 49 | Object.defineProperty( 50 | fn, 51 | 'inspect', 52 | { 53 | enumerable: false, 54 | value: fn.toString 55 | } 56 | ); 57 | 58 | Object.defineProperty( 59 | fn, 60 | '_paths', 61 | { 62 | enumerable: false, 63 | value: {} 64 | } 65 | ); 66 | 67 | return _.merge(fn, CONFIG_DEFAULT, obj); 68 | } 69 | } 70 | 71 | class Loader { 72 | constructor(options) { 73 | options = _.defaults( 74 | options, 75 | { 76 | cwd: process.cwd(), 77 | packageProp: 'csfConfig' 78 | } 79 | ); 80 | 81 | this._config = cosmiconfig('csf', options); 82 | } 83 | 84 | load(cwd) { 85 | return this._config.search(cwd).then( 86 | obj => { 87 | const config = new Config(obj ? obj.config : {}); 88 | 89 | config._paths.cwd = cwd; 90 | 91 | if (obj) { 92 | const STR_PATH = 'path:'; 93 | 94 | const paths = Object.keys(config).reduce( 95 | (prev, item, index) => { 96 | if (item.indexOf(STR_PATH) === 0) { 97 | const pathKey = item.slice(STR_PATH.length); 98 | 99 | const pathConfig = config[item]; 100 | 101 | prev.configs.push(pathConfig); 102 | 103 | delete config[item]; 104 | 105 | prev.keys.push(pathKey); 106 | } 107 | 108 | return prev; 109 | }, 110 | config._paths 111 | ); 112 | 113 | paths.obj = obj; 114 | } 115 | 116 | return config; 117 | } 118 | ).catch( 119 | err => { 120 | const config = new Config(); 121 | 122 | config._paths.cwd = cwd; 123 | 124 | config._paths.err = err; 125 | 126 | return config; 127 | } 128 | ); 129 | } 130 | } 131 | 132 | Config.Loader = Loader; 133 | 134 | module.exports = Config; -------------------------------------------------------------------------------- /test/engine_rules/common.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var _ = require('lodash'); 3 | 4 | chai.use(require('chai-string')); 5 | 6 | var RE = require('../../lib/re'); 7 | 8 | var re = new RE(require('../../lib/rules')); 9 | 10 | var assert = chai.assert; 11 | 12 | describe( 13 | 'Common Rule Engine Tests', 14 | function() { 15 | 'use strict'; 16 | 17 | it( 18 | 'should detect and replace extraneous whitespace', 19 | function() { 20 | var rule = re.rules.common.extraneousSpaces; 21 | 22 | var tests = [ 23 | ' ', 24 | ' ', 25 | 'some code ' 26 | ]; 27 | 28 | _.forEach( 29 | tests, 30 | function(input) { 31 | var context = { 32 | content: input, 33 | rawContent: input 34 | }; 35 | 36 | var result = re.testContent(rule, context); 37 | var lineNum = 1; 38 | 39 | assert.isTrue(result); 40 | assert.startsWith(re.getMessage(result, rule, context), rule.message.split(':')[0]); 41 | assert.equal('', re.replaceItem(result, rule, context)); 42 | } 43 | ); 44 | } 45 | ); 46 | 47 | it( 48 | 'should detect and replace mixed spaces and tabs', 49 | function() { 50 | var rule = re.rules.common.mixedSpaces; 51 | 52 | var tests = { 53 | ' ': ' ', 54 | ' ': ' ', 55 | ' ': ' ', 56 | ' ': ' ' 57 | }; 58 | 59 | _.forEach( 60 | tests, 61 | function(output, input) { 62 | var context = { 63 | content: input, 64 | rawContent: input 65 | }; 66 | 67 | var result = re.testContent(rule, context); 68 | var lineNum = 1; 69 | 70 | assert.isTrue(result); 71 | assert.startsWith(re.getMessage(result, rule, context), rule.message.split(':')[0]); 72 | assert.equal(output, re.replaceItem(result, rule, context)); 73 | } 74 | ); 75 | } 76 | ); 77 | 78 | it( 79 | 'should detect and replace invalid whitespace', 80 | function() { 81 | var MAP_WHITESPACE = re.rules.common._MAP_WHITESPACE; 82 | 83 | var rule = re.rules.common.invalidWhiteSpace; 84 | 85 | var tests = {}; 86 | 87 | _.forEach( 88 | MAP_WHITESPACE, 89 | function(item, index) { 90 | tests['foo' + index + 'bar'] = 'foo' + item + 'bar'; 91 | } 92 | ); 93 | 94 | _.forEach( 95 | tests, 96 | function(output, input) { 97 | var context = { 98 | content: input, 99 | rawContent: input 100 | }; 101 | 102 | var result = re.testContent(rule, context); 103 | var lineNum = 1; 104 | 105 | assert.isTrue(result); 106 | assert.startsWith(re.getMessage(result, rule, context), rule.MSG); 107 | assert.equal(output, re.replaceItem(result, rule, context)); 108 | } 109 | ); 110 | } 111 | ); 112 | } 113 | ); -------------------------------------------------------------------------------- /lib/lint_css_rules/at_rule_empty_line.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var stylelint = require('stylelint'); 3 | 4 | var ruleUtils = require('../rule_utils'); 5 | 6 | var ruleName = ruleUtils.getPluginId(__filename); 7 | 8 | var slUtils = stylelint.utils; 9 | 10 | /* istanbul ignore next */ 11 | var jsonf = _.bindKeyRight( 12 | JSON, 13 | 'stringify', 14 | (key, value) => { 15 | var retVal; 16 | 17 | if (key === 'start' || key === 'end') { 18 | retVal = value.line; 19 | } 20 | else if (['parent', 'range'].indexOf(key) === -1) { 21 | retVal = value; 22 | } 23 | 24 | return retVal; 25 | }, 26 | 4 27 | ); 28 | 29 | var plugin = stylelint.createPlugin( 30 | ruleName, 31 | (options, secondaryOptions) => { 32 | // var atRuleFn = stylelint.rules['at-rule-empty-line-before'](expectation, options, context || {}); 33 | 34 | return (root, result) => { 35 | var validOptions = slUtils.validateOptions( 36 | result, 37 | ruleName, 38 | { 39 | actual: options, 40 | possible: ['always', 'never'] 41 | }, 42 | { 43 | actual: secondaryOptions, 44 | possible: { 45 | except: ['first-nested'], 46 | ignore: ['between-nested'] 47 | }, 48 | 49 | optional: true 50 | } 51 | ); 52 | 53 | var getLineDistance = (left, right) => right.source.start.line - left.source.end.line; 54 | 55 | var isSingleLineRule = node => node.source.start.line === node.source.end.line; 56 | 57 | var validLines = []; 58 | 59 | if (validOptions) { 60 | root.walkAtRules( 61 | node => { 62 | if (node !== root.first) { 63 | var startLine = node.source.start.line; 64 | 65 | if (node.name === 'include' || node.name === 'import') { 66 | var prev = node.prev(); 67 | 68 | if (prev && prev.type === 'atrule' && isSingleLineRule(node) && isSingleLineRule(prev) && getLineDistance(prev, node) === 1) { 69 | validLines.push(startLine); 70 | } 71 | } 72 | else if (node.name === 'else') { 73 | validLines.push(startLine); 74 | } 75 | } 76 | } 77 | ); 78 | 79 | stylelint.utils.checkAgainstRule( 80 | { 81 | ruleName: 'at-rule-empty-line-before', 82 | ruleSettings: [options, secondaryOptions], 83 | root: root 84 | }, 85 | (warning) => { 86 | if (!_.includes(validLines, warning.line)) { 87 | stylelint.utils.report( 88 | { 89 | message: warning.text, 90 | ruleName: ruleName, 91 | result: result, 92 | node: warning.node, 93 | line: warning.line, 94 | column: warning.column, 95 | } 96 | ); 97 | } 98 | } 99 | ); 100 | } 101 | 102 | return result; 103 | }; 104 | } 105 | ); 106 | 107 | plugin.ruleName = ruleName; 108 | 109 | module.exports = plugin; -------------------------------------------------------------------------------- /lib/lint_js_rules/function_spacing.js: -------------------------------------------------------------------------------- 1 | var utils = require('../rule_utils'); 2 | 3 | var sub = require('string-sub'); 4 | 5 | module.exports = { 6 | create(context) { 7 | var testFunctionSpacing = node => { 8 | var nodeBody = node.body; 9 | 10 | var fnBody = nodeBody.body; 11 | 12 | var range = []; 13 | 14 | var sourceCode = context.getSourceCode(); 15 | 16 | function fix(fixer) { 17 | return fixer.replaceTextRange(range, '\n'); 18 | } 19 | 20 | if (fnBody.length) { 21 | var firstStatement = fnBody[0]; 22 | var lastStatement = fnBody[fnBody.length - 1]; 23 | 24 | var leadingComments = sourceCode.getComments(firstStatement).leading; 25 | var trailingComments = sourceCode.getComments(lastStatement).trailing; 26 | 27 | var startLineDistance = utils.getLineDistance(nodeBody, firstStatement, 'start'); 28 | 29 | if (leadingComments.length) { 30 | var firstLeadingComment = leadingComments[0]; 31 | var lastLeadingComment = leadingComments[leadingComments.length - 1]; 32 | 33 | var startCommentAfterDistance = utils.getLineDistance(lastLeadingComment, firstStatement, 'start'); 34 | var startCommentBeforeDistance = utils.getLineDistance(nodeBody, firstLeadingComment, 'start'); 35 | 36 | startLineDistance = Math.max(startCommentBeforeDistance, startCommentAfterDistance); 37 | } 38 | 39 | if (startLineDistance !== 1) { 40 | range[0] = sourceCode.getFirstToken(nodeBody).range[1]; 41 | range[1] = sourceCode.getFirstToken(firstStatement).range[0] - 1; 42 | 43 | context.report( 44 | { 45 | fix, 46 | message: sub('There should be exactly one line between the start of the function and the first statement, not {0} lines', startLineDistance), 47 | node 48 | } 49 | ); 50 | } 51 | 52 | var endLineDistance = utils.getLineDistance(lastStatement, nodeBody, 'end', 'end'); 53 | 54 | if (trailingComments.length) { 55 | var firstTrailingComment = trailingComments[0]; 56 | var lastTrailingComment = trailingComments[trailingComments.length - 1]; 57 | 58 | var endCommentAfterDistance = utils.getLineDistance(lastTrailingComment, nodeBody, 'end', 'end'); 59 | var endCommentBeforeDistance = utils.getLineDistance(lastStatement, firstTrailingComment, 'end', 'end'); 60 | 61 | endLineDistance = Math.max(endCommentBeforeDistance, endCommentAfterDistance); 62 | } 63 | 64 | if (endLineDistance !== 1) { 65 | range[0] = sourceCode.getLastToken(lastStatement).range[1]; 66 | range[1] = sourceCode.getLastToken(nodeBody).range[0]; 67 | 68 | context.report( 69 | { 70 | fix, 71 | message: sub('There should be exactly one line between the last statement and the end of the function, not {0} lines', endLineDistance), 72 | node: lastStatement 73 | } 74 | ); 75 | } 76 | } 77 | }; 78 | 79 | return { 80 | FunctionDeclaration: testFunctionSpacing, 81 | FunctionExpression: testFunctionSpacing 82 | }; 83 | }, 84 | 85 | meta: { 86 | fixable: 'whitespace' 87 | } 88 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/sort_vars.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var addES6 = require('../test_utils').addES6( 11 | { 12 | ecmaFeatures: {}, 13 | sourceType: 'module' 14 | } 15 | ); 16 | 17 | ruleTester.run( 18 | path.basename(__filename, '.js'), 19 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 20 | { 21 | valid: [ 22 | 'var xyz = 123;', 23 | 'var abc = 123;\nvar def = 456;', 24 | 'var abc = 123; var def = 456;', 25 | 'var cde = 123;\nvar def = 123;\n\nvar abc = 456;', 26 | 'var cde = 123;\nvar def = foo();\n\nvar abc = bar(def);', 27 | 'var cde = 123;\nvar def = window.foo();\n\nvar abc = window.bar(def);', 28 | 'for (var i = 0; i < 10; i++) {\nvar current = 1;\n}', 29 | 'for (var i in obj) {\nvar current = 1;\n}' 30 | ].concat( 31 | [ 32 | { code: 'const {bar, foo} = refs;' }, 33 | { code: 'import Foo, {bar, baz} from "somefile";' }, 34 | { code: 'import Foo, {bar as doo, baz} from "somefile";' }, 35 | { code: 'import Foo from "somefile";' }, 36 | { code: 'import "somefile";' }, 37 | { code: 'const [foo, bar] = refs;' }/*, 38 | Need to add the below as soon as I figure out what to do about es7/babel-eslint, etc 39 | { code: 'const {foo, ...bar} = refs;' }*/ 40 | ].map(addES6) 41 | ), 42 | 43 | invalid: [ 44 | { 45 | code: 'var def = 456;\nvar abc = 123;', 46 | errors: [ { message: 'Sort variables: def abc' } ] 47 | }, 48 | { 49 | code: 'var def = 456;\n\nvar def_xyz = "FOO";\nvar abc = 123;', 50 | errors: [ { message: 'Sort variables: def_xyz abc' } ] 51 | }, 52 | { 53 | code: 'var def = 456;\n\nvar def_xyz = foo();\nvar abc = def_xyz.bar();', 54 | errors: [ { message: 'Sort variables: def_xyz abc. If you\'re using "def_xyz" in assigning a value to "abc", add a newline between them.' } ] 55 | }, 56 | { 57 | code: 'var def = 456;\n\nvar def_xyz = window.foo;\nvar abc = def_xyz.bar;', 58 | errors: [ { message: 'Sort variables: def_xyz abc. If you\'re using "def_xyz" in assigning a value to "abc", add a newline between them.' } ] 59 | }, 60 | { 61 | code: 'var def = 456;\nvar abc = def++;', 62 | errors: [ { message: 'Sort variables: def abc. If you\'re using "def" in assigning a value to "abc", add a newline between them.' } ] 63 | }, 64 | { 65 | code: 'var def = 456;\n\nvar def_xyz =[];\nvar abc = [];', 66 | errors: [ { message: 'Sort variables: def_xyz abc' } ] 67 | }, 68 | { 69 | code: 'var def = 456;\n\nvar def_xyz ={};\nvar abc = {};', 70 | errors: [ { message: 'Sort variables: def_xyz abc' } ] 71 | } 72 | ].concat( 73 | [ 74 | { 75 | code: 'const {foo, bar} = refs', 76 | errors: [ { message: 'Sort variables: foo bar' } ] 77 | }, 78 | { 79 | code: 'import Foo, {baz, bar} from "somefile";', 80 | errors: [ { message: 'Sort imported members: baz bar' } ] 81 | } 82 | ].map(addES6) 83 | ) 84 | } 85 | ); -------------------------------------------------------------------------------- /lib/config/eslint_es6.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'plugins': [ 3 | 'react', 4 | 'sort-imports-es6-autofix' 5 | ], 6 | 7 | 'rules': { 8 | 'jsx-quotes': [2, 'prefer-double'], 9 | 'jsx-uses-react': 2, 10 | 'jsx-uses-vars': 2, 11 | 'jsx-curly-spacing': [2, 'never'], 12 | 'jsx-indent': [2, 'tab'], 13 | 'jsx-indent-props': [2, 'tab'], 14 | 'jsx-key': 2, 15 | 'jsx-equals-spacing': [2, 'never'], 16 | 'jsx-no-undef': 2, 17 | 'jsx-no-duplicate-props': 2, 18 | 'jsx-no-literals': 2, 19 | 'jsx-closing-bracket-location': 2, 20 | 'jsx-pascal-case': 2, 21 | 'jsx-tag-spacing': [2], 22 | 'jsx-sort-props': [ 23 | 2, 24 | { 25 | 'ignoreCase': true 26 | } 27 | ], 28 | 'no-multi-comp': [ 29 | 0, 30 | { 31 | 'ignoreStateless': true 32 | } 33 | ], 34 | 'jsx-wrap-multilines': [2/* , {'ignoreStateless': true} */], 35 | 'no-unknown-property': 2, 36 | 'sort-comp': [ 37 | 0, 38 | { 39 | groups: { 40 | lifecycle: [ 41 | 'constructor', 42 | 'displayName', 43 | 'mixins', 44 | 'propTypes', 45 | 'statics', 46 | 'defaultProps', 47 | 'getDefaultProps', 48 | 'getInitialState', 49 | 'state', 50 | 'componentWillMount', 51 | 'componentDidMount', 52 | 'componentWillReceiveProps', 53 | 'componentWillUpdate', 54 | 'componentDidUpdate', 55 | 'componentWillUnmount', 56 | 'shouldComponentUpdate', 57 | 'getChildContext', 58 | 'childContextTypes', 59 | 'contextTypes' 60 | ] 61 | }, 62 | order: [ 63 | 'static-methods', 64 | 'lifecycle', 65 | 'everything-else', 66 | 'render' 67 | ] 68 | } 69 | ], 70 | 'sort-imports-es6-autofix/sort-imports-es6': [2, { 71 | 'ignoreCase': true, 72 | 'ignoreMemberSort': false, 73 | 'memberSyntaxSortOrder': ['none', 'all', 'single', 'multiple'] 74 | }], 75 | 'prop-types': 0, 76 | 'display-name': 0, 77 | 'no-danger': 0, 78 | 'no-set-state': 0, 79 | 'no-is-mounted': 0, 80 | 'no-deprecated': 0, 81 | 'no-did-mount-set-state': 0, 82 | 'no-did-update-set-state': 0, 83 | 'react-in-jsx-scope': 0, 84 | 'jsx-handler-names': 0, 85 | 'jsx-boolean-value': 0, 86 | 'require-extension': 0, 87 | 'jsx-max-props-per-line': 0, 88 | 'no-string-refs': 0, 89 | 'prefer-stateless-function': 0, 90 | 91 | 'require-render-return': 2, 92 | 'jsx-first-prop-new-line': [2, 'multiline'], 93 | 'self-closing-comp': 2, 94 | 'sort-prop-types': [ 95 | 2, 96 | { 97 | 'ignoreCase': true 98 | } 99 | ], 100 | 'no-direct-mutation-state': 2, 101 | 102 | 'forbid-prop-types': 0, 103 | 'jsx-no-bind': 2, 104 | 'prefer-es6-class': 0, 105 | 106 | 'arrow-body-style': [0, 'as-needed'], 107 | 'arrow-parens': [2, 'as-needed'], 108 | 'object-property-newline': 0, 109 | 'object-shorthand': [ 110 | 2, 111 | 'always', 112 | { 113 | 'ignoreConstructors': true 114 | } 115 | ], 116 | 'prefer-arrow-callback': [ 117 | 2, 118 | { 119 | 'allowNamedFunctions': true 120 | } 121 | ], 122 | 'no-new-symbol': 2, 123 | 'prefer-const': 2, 124 | 'prefer-rest-params': 2, 125 | 'prefer-spread': 2, 126 | 'prefer-template': 2 127 | } 128 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/sort_props.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var linter = lint.linter; 6 | var RuleTester = lint.eslint.RuleTester; 7 | 8 | var ruleTester = new RuleTester(); 9 | 10 | var addES6 = require('../test_utils').addES6( 11 | { 12 | ecmaFeatures: { 13 | experimentalObjectRestSpread: true 14 | } 15 | } 16 | ); 17 | 18 | ruleTester.run( 19 | path.basename(__filename, '.js'), 20 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 21 | { 22 | valid: [ 23 | '({init: function(){}, initializer: function(){}, renderUI: function(){}, bindUI: function(){}, syncUI: function(){}, destructor: function(){}})', 24 | '({init: function(){}, initializer: function(){}, renderUI: function(){}, bindUI: function(){}, syncUI: function(){}, destructor: function(){}, abc: function(){}})', 25 | '({ 0: 1, "$xyz": 1, abc: 2 })', 26 | '({ 0: 1, "@xyz": 1, abc: 2})', 27 | '({ a: 1,\nb: 2,\n\na:3})', 28 | '({ ATTRS: {a: 1,\n\nb: 2,}})', 29 | '({ ATTRS: {a: 1,\nb: 2,}})', 30 | { 31 | code: '({initString: 1, initsTriangle: 2})', 32 | options: [{'casesensitive': true}] 33 | }, 34 | { 35 | code: '({initsTriangle: 1, initString: 2})', 36 | options: [{'casesensitive': false}] 37 | } 38 | ].concat( 39 | [ 40 | { code: '({[bar]: 1, [foo]: 1})' }, 41 | { code: '({a: 1, [bar()]: 1, [foo]: 1, [obj.bar()]: 1, [obj.foo]: 1, [str + \'other\']: 1 })' }, 42 | { code: '({...baz, bar: 1, foo})' }, 43 | ].map(addES6) 44 | ), 45 | 46 | invalid: [ 47 | { 48 | code: '({abc: function(){}, init: function(){}, initializer: function(){}, renderUI: function(){}, bindUI: function(){}, syncUI: function(){}, destructor: function(){}})', 49 | errors: [ { message: 'Sort properties: abc init (Lifecycle methods should come first)' } ] 50 | }, 51 | { 52 | code: '({initString: 1, initsTriangle: 2})', 53 | errors: [ { message: 'Sort properties: initString initsTriangle' } ] 54 | }, 55 | { 56 | code: '({getFoo: 1, getAbc: 2})', 57 | errors: [ { message: 'Sort properties: getFoo getAbc' } ] 58 | }, 59 | { 60 | code: '({_getFoo: 1, _getAbc: 2})', 61 | errors: [ { message: 'Sort properties: _getFoo _getAbc' } ] 62 | }, 63 | { 64 | code: '({_getFoo: function(){},\n\n_getAbc: function(){}})', 65 | errors: [ { message: 'Sort properties: _getFoo _getAbc' } ] 66 | }, 67 | { 68 | code: '({_getFoo: 1, _getAbc: 2})', 69 | errors: [ { message: 'Sort properties: _getFoo _getAbc' } ] 70 | }, 71 | { 72 | code: '({_getFoo: 1, getAbc: 2})', 73 | errors: [ { message: 'Sort properties: _getFoo getAbc' } ] 74 | }, 75 | { 76 | code: '({_getFoo: function(){},\n\n getAbc: function(){}})', 77 | errors: [ { message: 'Sort properties: _getFoo getAbc' } ] 78 | }, 79 | { 80 | code: '({ ATTRS: {z: 1,\n\nb: {}}})', 81 | errors: [ { message: 'Sort properties: z b' } ] 82 | } 83 | ].concat( 84 | [ 85 | { 86 | code: '({[foo]: 1, [bar]: 1})', 87 | errors: [ { message: 'Sort properties: [foo] [bar]' } ] 88 | }, 89 | { 90 | code: '({a: 1, [str + \'other\']: 1, [bar()]: 1, [foo]: 1, [obj.bar()]: 1, [obj.foo]: 1 })', 91 | errors: [ { message: 'Sort properties: [str + \'other\'] [bar()]' } ] 92 | }, 93 | ].map(addES6) 94 | ) 95 | } 96 | ); -------------------------------------------------------------------------------- /lib/rule_utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var path = require('path'); 3 | 4 | var REGEX = require('./regex'); 5 | 6 | exports.getConstants = node => { 7 | var constants = []; 8 | 9 | node.body.forEach( 10 | (item, index) => { 11 | if (item.type == 'VariableDeclaration' && item.declarations) { 12 | item.declarations.forEach( 13 | (val, key) => { 14 | if (/^[A-Z0-9_]+$/.test(val.id.name)) { 15 | constants.push(val); 16 | } 17 | } 18 | ); 19 | } 20 | } 21 | ); 22 | 23 | return constants; 24 | }; 25 | 26 | var getIdentifiers = (node, obj) => { 27 | obj = obj || {}; 28 | 29 | if (node) { 30 | var type = node.type; 31 | 32 | if (type === 'Identifier') { 33 | obj[node.name] = true; 34 | } 35 | else if (type === 'Property') { 36 | obj = getIdentifiers(node.key, obj); 37 | obj = getIdentifiers(node.value, obj); 38 | } 39 | else if (type === 'BinaryExpression') { 40 | obj = getIdentifiers(node.left, obj); 41 | obj = getIdentifiers(node.right, obj); 42 | } 43 | else if (type === 'CallExpression' || type === 'ObjectExpression') { 44 | var prop = 'properties'; 45 | 46 | if (type === 'CallExpression') { 47 | obj = getIdentifiers(node.callee, obj); 48 | 49 | prop = 'arguments'; 50 | } 51 | 52 | node[prop].forEach(_.ary(_.bindRight(getIdentifiers, null, obj), 1)); 53 | } 54 | else if (type === 'MemberExpression') { 55 | obj = getIdentifiers(node.object, obj); 56 | obj = getIdentifiers(node.property, obj); 57 | } 58 | else if (type === 'UpdateExpression') { 59 | obj = getIdentifiers(node.argument); 60 | } 61 | } 62 | 63 | return obj; 64 | }; 65 | 66 | exports.getIdentifiers = getIdentifiers; 67 | 68 | // Gets distance between the end of "left" and the start of "right" 69 | // Or, pass in leftEnd or rightStart to define whether 70 | // it's the end or start of a line 71 | 72 | exports.getLineDistance = (left, right, leftEnd, rightStart) => right.loc[rightStart || 'start'].line - left.loc[leftEnd || 'end'].line; 73 | 74 | var compare = (a, b) => { 75 | var retVal = 0; 76 | 77 | if (a < b) { 78 | retVal = -1; 79 | } 80 | else if (a > b) { 81 | retVal = 1; 82 | } 83 | 84 | return retVal; 85 | }; 86 | 87 | var compareAlpha = (a, b, caseInsensitive, result) => { 88 | if (caseInsensitive === true) { 89 | a = a.toLowerCase(); 90 | b = b.toLowerCase(); 91 | } 92 | 93 | var aa = a.split(REGEX.DIGITS); 94 | var bb = b.split(REGEX.DIGITS); 95 | 96 | var length = Math.max(aa.length, bb.length); 97 | 98 | for (var i = 0; i < length; i++) { 99 | var itemA = aa[i]; 100 | var itemB = bb[i]; 101 | 102 | if (itemA != itemB) { 103 | var cmp1 = itemA; 104 | var cmp2 = itemB; 105 | 106 | result = compare(cmp1, cmp2); 107 | 108 | break; 109 | } 110 | } 111 | 112 | return result; 113 | }; 114 | 115 | var isFinite = _.isFinite; 116 | 117 | exports.naturalCompare = (a, b, caseInsensitive) => { 118 | var result = 0; 119 | 120 | if ((isFinite(a) && isFinite(b)) || (isFinite(+a) && isFinite(+b))) { 121 | result = compare(a, b); 122 | } 123 | else { 124 | result = compareAlpha(a, b, caseInsensitive, result); 125 | } 126 | 127 | return result; 128 | }; 129 | 130 | exports.getRuleId = filePath => { 131 | var baseName = path.basename(filePath, '.js'); 132 | 133 | return `csf-${_.kebabCase(baseName)}`; 134 | }; 135 | 136 | exports.getPluginId = filePath => { 137 | return exports.getRuleId(filePath).replace(/^csf-/, 'csf/'); 138 | }; -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('harmonize')(); 2 | 3 | var _ = require('lodash'); 4 | var gulp = require('gulp'); 5 | var plugins = require('gulp-load-plugins')(); 6 | var runSequence = require('run-sequence'); 7 | var inquirer = require('inquirer'); 8 | 9 | gulp.task('coveralls', () => { 10 | gulp.src('coverage/**/lcov.info') 11 | .pipe(plugins.coveralls()); 12 | }); 13 | 14 | gulp.task('test', done => runSequence('test-unit', done)); 15 | 16 | gulp.task('test-complexity', () => gulp.src(['lib/**/*.js']) 17 | .pipe(plugins.complexity({ 18 | cyclomatic: [4, 7, 12], 19 | halstead: [15, 15, 20] 20 | }))); 21 | 22 | gulp.task('test-file', () => { 23 | var file = process.argv.slice(3).pop(); 24 | 25 | if (file) { 26 | file = file.slice(2); 27 | file = !_.startsWith(file, 'test/') ? `test/${file}` : file; 28 | file = !_.endsWith(file, '.js') ? `${file}.js` : file; 29 | 30 | file = [file]; 31 | } 32 | else { 33 | file = ['test/**/*.js', '!test/fixture/*.js']; 34 | } 35 | 36 | return gulp.src(file) 37 | // .pipe(plugins.debug()) 38 | .pipe(plugins.mocha( 39 | { 40 | 'display-raw': true 41 | } 42 | ) 43 | ); 44 | }); 45 | 46 | gulp.task('test-unit', () => { 47 | return gulp.src(['test/**/*.js', '!test/fixture/*.js']) 48 | .pipe(plugins.mocha( 49 | { 50 | 'display-raw': true 51 | } 52 | ) 53 | ); 54 | }); 55 | 56 | gulp.task('test-cover', () => gulp.src(['lib/**/*.js']) 57 | .pipe(plugins.istanbul()) 58 | .pipe(plugins.istanbul.hookRequire())); 59 | 60 | gulp.task('test-coverage', ['test-cover'], () => { 61 | return gulp.src(['test/**/*.js', '!test/fixture/*.js']) 62 | .pipe(plugins.mocha( 63 | { 64 | 'display-raw': true 65 | } 66 | ) 67 | ) 68 | .pipe(plugins.istanbul.writeReports()); 69 | }); 70 | 71 | gulp.task('toc', done => { 72 | gulp.src('./README.md') 73 | .pipe( 74 | plugins.doctoc( 75 | { 76 | title: '### Jump to Section', 77 | depth: 2 78 | } 79 | ) 80 | ) 81 | .pipe(gulp.dest('./')); 82 | }); 83 | 84 | gulp.task( 85 | 'new-rule', 86 | done => { 87 | inquirer.prompt( 88 | [ 89 | { 90 | type: 'list', 91 | choices: ['ESLint'/*, 'Stylelint'*/], 92 | message: 'What kind of linting rule do you wish to create?', 93 | name: 'type' 94 | }, 95 | { 96 | type: 'input', 97 | default: 'my-new-rule', 98 | filter: _.snakeCase, 99 | message: 'What do you want to name it?', 100 | name: 'name' 101 | }, 102 | { 103 | type: 'input', 104 | default: 'Checks for this issue.', 105 | message: 'Type a short description', 106 | name: 'description' 107 | } 108 | ] 109 | ) 110 | .then( 111 | res => { 112 | var srcDir = 'js'; 113 | var destDir = 'lint_js_rules'; 114 | 115 | if (res.type !== 'ESLint') { 116 | srcDir = 'css'; 117 | destDir = 'lint_css_rules'; 118 | } 119 | 120 | gulp.src(`./lib/tpl/lint_rules/${srcDir}/*.js`) 121 | // .pipe(plugins.debug()) 122 | .pipe( 123 | plugins.rename( 124 | path => { 125 | var baseDir = path.basename === 'rule' ? 'lib' : 'test'; 126 | 127 | path.dirname = `./${baseDir}/lint_${srcDir}_rules`; 128 | 129 | path.basename = res.name; 130 | } 131 | ) 132 | ) 133 | .pipe( 134 | plugins.template( 135 | res, 136 | { 137 | interpolate: /<%=([\s\S]+?)%>/g 138 | } 139 | ) 140 | ) 141 | .pipe(gulp.dest('./')); 142 | 143 | done(); 144 | } 145 | ); 146 | } 147 | ); -------------------------------------------------------------------------------- /test/rule_utils.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var chai = require('chai'); 3 | var falafel = require('falafel'); 4 | var sinon = require('sinon'); 5 | 6 | var ruleUtils = require('../lib/rule_utils'); 7 | 8 | chai.use(require('chai-string')); 9 | 10 | var assert = chai.assert; 11 | 12 | var parse = function(contents, cb) { 13 | return falafel( 14 | contents, 15 | { 16 | locations: true, 17 | tolerant: true 18 | }, 19 | cb 20 | ); 21 | }; 22 | 23 | describe( 24 | 'Rule Utils', 25 | function() { 26 | 'use strict'; 27 | 28 | beforeEach( 29 | function() { 30 | sinon.createSandbox(); 31 | } 32 | ); 33 | 34 | afterEach( 35 | function() { 36 | sinon.restore(); 37 | } 38 | ); 39 | 40 | it( 41 | 'should get constants from node properly', 42 | function(done) { 43 | 44 | parse( 45 | 'var regVar = 1; var X = "X_FOO"; var Y = "Y_FOO"; var ZnotConstant = "NOT_CONSTANT"', 46 | function(node) { 47 | if (node.type === 'Program') { 48 | var constants = ruleUtils.getConstants(node); 49 | var constantNames = _.map(_.map(constants, 'id'), 'name'); 50 | 51 | assert.equal(constants.length, 2); 52 | assert.equal(constantNames.join(''), 'XY'); 53 | 54 | done(); 55 | } 56 | } 57 | ); 58 | } 59 | ); 60 | 61 | it( 62 | 'should calculate line distance', 63 | function(done) { 64 | 65 | var varStr = ['var a = 1;', 'var b = 2;', 'var c = 3;', 'var d = 4;'].join('\n'); 66 | 67 | parse( 68 | varStr, 69 | function(node) { 70 | if (node.type === 'Program') { 71 | var variables = _.filter( 72 | node.body, 73 | { 74 | type: 'VariableDeclaration' 75 | } 76 | ); 77 | 78 | var lastIndex = variables.length - 1; 79 | 80 | assert.equal(ruleUtils.getLineDistance(variables[0], variables[lastIndex]), lastIndex); 81 | 82 | done(); 83 | } 84 | } 85 | ); 86 | } 87 | ); 88 | 89 | it( 90 | 'should sort array naturally', 91 | function() { 92 | var naturalCompare = ruleUtils.naturalCompare; 93 | 94 | assert.equal(naturalCompare('a', 'a', false), 0, 'a should be the same as a'); 95 | assert.equal(naturalCompare('a', 'A', false), 1, 'a should come before A'); 96 | assert.equal(naturalCompare('A', 'a', false), -1, 'A should not come before a'); 97 | assert.equal(naturalCompare('a', 'b', false), -1, 'a should come before b'); 98 | assert.equal(naturalCompare('iStragedy', 'isTragedy', false), -1, 'iStragedy should come before isTragedy'); 99 | 100 | assert.equal(naturalCompare(1, 1), 0, '1 should be the same as 1'); 101 | assert.equal(naturalCompare(1, 2), -1, '1 should come before 2'); 102 | assert.equal(naturalCompare(2, 1), 1, '2 should not come before 1'); 103 | assert.equal(naturalCompare('1', '1'), 0, '"1" should be the same as "1"'); 104 | assert.equal(naturalCompare('1', '2'), -1, '"1" should come before "2"'); 105 | assert.equal(naturalCompare('2', '1'), 1, '"2" should not come before "1"'); 106 | 107 | assert.equal(naturalCompare('a', 'a', true), 0, 'a should be the same as a'); 108 | assert.equal(naturalCompare('a', 'A', true), 0, 'a should be the same as A'); 109 | assert.equal(naturalCompare('A', 'a', true), 0, 'A should be the same as a'); 110 | assert.equal(naturalCompare('a', 'b', true), -1, 'a should come before b'); 111 | assert.equal(naturalCompare('iStragedy', 'isTragedy', true), 0, 'iStragedy should be the same as isTragedy'); 112 | } 113 | ); 114 | } 115 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/sort_vars.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var utils = require('../rule_utils'); 3 | 4 | var getLineDistance = utils.getLineDistance; 5 | var naturalCompare = utils.naturalCompare; 6 | 7 | var sub = require('string-sub'); 8 | 9 | var REGEX_ASSIGNMENT_PATTERNS = /(Object|Array)Pattern/; 10 | 11 | var REGEX_FOR = /For.*Statement/; 12 | 13 | var getIdentifiers = utils.getIdentifiers; 14 | 15 | module.exports = context => { 16 | var configuration = context.options[0] || {}; 17 | 18 | var caseSensitive = configuration.casesensitive; 19 | 20 | var destructuredVars = []; 21 | var imports = []; 22 | var variables = []; 23 | 24 | var findVars = node => { 25 | if (!REGEX_FOR.test(node.parent.type)) { 26 | var declarations = node.declarations; 27 | var specifiers = node.specifiers; 28 | 29 | if (declarations) { 30 | declarations.forEach( 31 | (val, key) => { 32 | var varType = val.id.type; 33 | 34 | var destructuredVar = REGEX_ASSIGNMENT_PATTERNS.test(varType); 35 | 36 | if (val.init && !destructuredVar) { 37 | variables.push(val); 38 | } 39 | else if (destructuredVar && varType === 'ObjectPattern') { 40 | var props = val.id.properties.filter( 41 | (item, index) => item.key 42 | ); 43 | 44 | destructuredVars.push(props); 45 | } 46 | } 47 | ); 48 | } 49 | else { 50 | specifiers = specifiers.filter( 51 | (item, index) => item.type !== 'ImportDefaultSpecifier' 52 | ); 53 | 54 | if (specifiers.length > 1) { 55 | imports.push(specifiers); 56 | } 57 | } 58 | } 59 | }; 60 | 61 | return { 62 | 'Program:exit': function() { 63 | variables.reduce( 64 | (prev, item, index) => { 65 | var lineDistance = getLineDistance(prev, item); 66 | 67 | if (lineDistance === 1 || lineDistance === 0) { 68 | var curName = item.id.name; 69 | var prevName = prev.id.name; 70 | 71 | var result = naturalCompare(curName, prevName, !caseSensitive); 72 | 73 | if (result === -1) { 74 | var messageStr = 'Sort variables: {0} {1}'; 75 | 76 | var identifiers = getIdentifiers(item.init); 77 | 78 | if (identifiers[prevName]) { 79 | messageStr += `. If you're using "${prevName}" in assigning a value to "${curName}", add a newline between them.` 80 | } 81 | 82 | var message = sub(messageStr, prevName, curName); 83 | 84 | context.report(prev, message); 85 | } 86 | } 87 | 88 | return item; 89 | }, 90 | variables[0] 91 | ); 92 | 93 | destructuredVars.forEach( 94 | (item, index) => { 95 | item.reduce( 96 | (prev, item, index) => { 97 | var curName = item.key.name; 98 | var prevName = prev.key.name; 99 | 100 | var result = naturalCompare(curName, prevName, !caseSensitive); 101 | 102 | if (result === -1) { 103 | var message = sub('Sort variables: {0} {1}', prevName, curName); 104 | 105 | context.report(prev, message); 106 | } 107 | 108 | return item; 109 | }, 110 | item[0] 111 | ); 112 | } 113 | ); 114 | 115 | imports.forEach( 116 | (item, index) => { 117 | item.reduce( 118 | (prev, item, index) => { 119 | var curName = item.local.name; 120 | var prevName = prev.local.name; 121 | 122 | if (prevName === prev.imported.name && curName === item.imported.name) { 123 | var result = naturalCompare(curName, prevName, !caseSensitive); 124 | 125 | if (result === -1) { 126 | var message = sub('Sort imported members: {0} {1}', prevName, curName); 127 | 128 | context.report(prev, message); 129 | } 130 | } 131 | 132 | return item; 133 | }, 134 | item[0] 135 | ); 136 | } 137 | ); 138 | }, 139 | 140 | ImportDeclaration: findVars, 141 | 142 | VariableDeclaration: findVars 143 | }; 144 | }; -------------------------------------------------------------------------------- /lib/lint_js_rules/format_multiline_vars.js: -------------------------------------------------------------------------------- 1 | var findFirstofLastLine = (tokens, line) => { 2 | var firstOfLast = null; 3 | 4 | var len = tokens.length; 5 | 6 | for (var i = 0; i < len; i++) { 7 | var token = tokens[i]; 8 | 9 | if (token.loc.start.line === line) { 10 | firstOfLast = token; 11 | 12 | break; 13 | } 14 | } 15 | 16 | return firstOfLast; 17 | }; 18 | 19 | var isSameColumn = (a, b, requireKeyword) => { 20 | var inc = requireKeyword ? 3 : 0; 21 | 22 | return a.loc.start.column === (b.loc.start.column + inc); 23 | }; 24 | 25 | var isSameLine = (a, b) => a.loc.start.line === b.loc.start.line; 26 | 27 | module.exports = context => { 28 | var checkEndColumn = (declaration, init, node) => { 29 | var decLoc = declaration.loc; 30 | var decStart = decLoc.start; 31 | 32 | if (init.type === 'BinaryExpression' && 33 | init.operator === '+' && 34 | decLoc.end.line > decStart.line && 35 | !isSameColumn(declaration, init.right)) { 36 | 37 | var tokens = context.getTokens(node); 38 | 39 | var firstOfLast = findFirstofLastLine(tokens, decLoc.end.line); 40 | 41 | var assignmentExpression = (node.type === 'AssignmentExpression'); 42 | 43 | if (!isSameColumn(declaration, firstOfLast, !assignmentExpression)) { 44 | var id = assignmentExpression ? declaration.left : declaration.id; 45 | 46 | context.report( 47 | node, 48 | 'Multi-line strings should be aligned to the start of the variable name "{{identifier}}"', 49 | { 50 | identifier: id.name 51 | } 52 | ); 53 | } 54 | } 55 | }; 56 | 57 | var checkStartLine = (id, init, node) => { 58 | var allowedFormat = isSameLine(id, init); 59 | 60 | if (!allowedFormat) { 61 | var info = { 62 | identifier: id.name 63 | }; 64 | 65 | var message = 'Variable values should start on the same line as the variable name "{{identifier}}"'; 66 | 67 | if (init.type === 'LogicalExpression' || init.type === 'JSXElement') { 68 | var token = context.getTokenBefore(init); 69 | 70 | allowedFormat = (token.value === '(' && isSameLine(id, token)); 71 | } 72 | else if (id.type === 'ObjectPattern' || id.type === 'ArrayPattern') { 73 | var endToken = context.getLastToken(id); 74 | var startToken = context.getFirstToken(id); 75 | 76 | var keywordToken = context.getTokenBefore(id); 77 | 78 | var endOnSameLineAsInit = isSameLine(init, endToken); 79 | var startOnSameLineAsKeyword = isSameLine(keywordToken, startToken); 80 | 81 | allowedFormat = startOnSameLineAsKeyword && endOnSameLineAsInit; 82 | 83 | if (!allowedFormat) { 84 | info = { 85 | endToken: endToken.value, 86 | initName: init.name || context.getSourceCode().getText(init), 87 | keywordToken: keywordToken.value, 88 | startToken: startToken.value 89 | }; 90 | 91 | if (!startOnSameLineAsKeyword && !endOnSameLineAsInit) { 92 | message = 'Destructured assignments should have "{{startToken}}" on the same line as "{{keywordToken}}" and "{{endToken}}" should be on the same line as "{{initName}}"'; 93 | } 94 | else if (!startOnSameLineAsKeyword) { 95 | message = 'Destructured assignments should have "{{startToken}}" on the same line as "{{keywordToken}}"'; 96 | } 97 | else { 98 | message = 'Destructured assignments should have "{{endToken}}" on the same line as "{{initName}}"'; 99 | } 100 | } 101 | } 102 | 103 | if (!allowedFormat) { 104 | context.report( 105 | node, 106 | message, 107 | info 108 | ); 109 | } 110 | } 111 | }; 112 | 113 | return { 114 | AssignmentExpression(node) { 115 | checkStartLine(node.left, node.right, node); 116 | checkEndColumn(node, node.right, node); 117 | }, 118 | 119 | VariableDeclaration(node) { 120 | var declarations = node.declarations; 121 | 122 | var dec = declarations[0]; 123 | var decId = dec.id; 124 | var decInit = dec.init; 125 | 126 | if (decInit) { 127 | checkStartLine(decId, decInit, node); 128 | checkEndColumn(dec, decInit, node); 129 | } 130 | } 131 | }; 132 | }; -------------------------------------------------------------------------------- /lib/lint_js.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var eslint = require('eslint'); 3 | var glob = require('glob'); 4 | var path = require('path'); 5 | 6 | var SourceCodeFixer = require('eslint/lib/util/source-code-fixer'); 7 | 8 | var ruleUtils = require('./rule_utils'); 9 | 10 | var ESLINT_CONFIG = require('./config/eslint'); 11 | 12 | var customRules = {}; 13 | 14 | var reactRules = require('eslint-plugin-react').rules; 15 | 16 | _.defaults(customRules, reactRules); 17 | 18 | var loadPlugin = (pluginName, configPath) => { 19 | var rules; 20 | 21 | if (pluginName !== 'react') { 22 | if (pluginName.indexOf('eslint-plugin-') === -1) { 23 | pluginName = `eslint-plugin-${pluginName}`; 24 | } 25 | 26 | var baseName = pluginName.replace('eslint-plugin-', ''); 27 | 28 | if (pluginName.indexOf('/') !== -1) { 29 | baseName = path.basename(baseName); 30 | 31 | if (configPath && !path.isAbsolute(pluginName)) { 32 | pluginName = path.resolve(configPath, pluginName); 33 | } 34 | } 35 | 36 | try { 37 | rules = require(pluginName).rules; 38 | } 39 | catch (e) { 40 | } 41 | 42 | if (rules) { 43 | rules = _.mapKeys( 44 | rules, 45 | (item, index) => `${baseName}/${index}` 46 | ); 47 | 48 | eslint.linter.defineRules(rules); 49 | } 50 | } 51 | 52 | return rules; 53 | }; 54 | 55 | var runLinter = (contents, file, context) => { 56 | var customRules = context.customRules || {}; 57 | 58 | eslint.linter.defineRules(customRules); 59 | 60 | var config = context.lintConfig; 61 | 62 | // The ecmaVersion variable is undefined here if no config was found 63 | // But it's being used now for it's side effects 64 | // because we're defaulting the parser to es7 so it doesn't error out 65 | // but if it's specified in the config, we add the special 66 | // es6 lint rule set 67 | 68 | var ecmaVersion = _.get(config, 'parserOptions.ecmaVersion'); 69 | 70 | var configs = [{}, ESLINT_CONFIG]; 71 | 72 | if (ecmaVersion > 5) { 73 | var es6Config = require('./config/eslint_es6'); 74 | 75 | configs.push(es6Config); 76 | } 77 | 78 | if (_.isObject(config)) { 79 | configs.push(config); 80 | } 81 | 82 | configs.push( 83 | (objValue, srcValue, key) => { 84 | var retVal; 85 | 86 | if (key === 'plugins' && _.isArray(objValue) && _.isArray(srcValue)) { 87 | retVal = objValue.concat(srcValue); 88 | } 89 | 90 | return retVal; 91 | } 92 | ); 93 | 94 | config = _.mergeWith(...configs); 95 | 96 | if (config.plugins) { 97 | var configPath = _.get(context, 'fileConfig._paths.obj.filepath'); 98 | 99 | if (configPath) { 100 | configPath = path.dirname(configPath); 101 | } 102 | 103 | config.plugins.forEach( 104 | (item, index) => { 105 | loadPlugin(item, configPath); 106 | } 107 | ); 108 | } 109 | 110 | var originalGlobals = config.globals; 111 | 112 | var importedAliases = config.importedAliases; 113 | 114 | if (importedAliases) { 115 | var newGlobals = _.zipObject(importedAliases, _.times(importedAliases.length, _.stubTrue)); 116 | 117 | config.globals = _.assign({}, originalGlobals, newGlobals); 118 | } 119 | 120 | var results = eslint.linter.verify(contents, config, file); 121 | 122 | if (importedAliases) { 123 | config.globals = originalGlobals; 124 | } 125 | 126 | if (results.length) { 127 | var fixedContent = SourceCodeFixer.applyFixes(contents, results); 128 | 129 | if (fixedContent.fixed) { 130 | contents = fixedContent.output; 131 | } 132 | } 133 | 134 | return { 135 | contents, 136 | results 137 | }; 138 | }; 139 | 140 | var globOptions = { 141 | cwd: __dirname 142 | }; 143 | 144 | module.exports = (contents, file, context) => { 145 | context.customRules = customRules; 146 | 147 | glob.sync( 148 | './lint_js_rules/*.js', 149 | globOptions 150 | ).forEach( 151 | (item, index) => { 152 | var id = ruleUtils.getRuleId(item); 153 | 154 | customRules[id] = require(item); 155 | } 156 | ); 157 | 158 | // eslint.linter.defineRules(customRules); 159 | 160 | return runLinter(contents, file, context); 161 | }; 162 | 163 | module.exports.eslint = eslint; 164 | module.exports.linter = eslint.linter; 165 | module.exports.runLinter = runLinter; -------------------------------------------------------------------------------- /test/fixture/junit-4.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /test/junit.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var sinon = require('sinon'); 5 | var xsd = require('xsd-schema-validator'); 6 | 7 | var junit = require('../lib/junit'); 8 | var Logger = require('../lib/logger'); 9 | 10 | chai.use(require('chai-string')); 11 | 12 | var assert = chai.assert; 13 | 14 | describe( 15 | 'JUnit reporting', 16 | function() { 17 | 'use strict'; 18 | 19 | beforeEach( 20 | function() { 21 | sinon.createSandbox(); 22 | } 23 | ); 24 | 25 | afterEach( 26 | function() { 27 | sinon.restore(); 28 | } 29 | ); 30 | 31 | it( 32 | 'should generate a JUnit report', 33 | function() { 34 | var logger = new Logger.constructor(); 35 | 36 | logger.log(1, 'Content is not valid', 'foo.js'); 37 | logger.log(2, 'Content is not valid', 'foo.js', 'error'); 38 | logger.log(5, 'Something is not valid', 'foo.js', 'error'); 39 | logger.log(3, 'Content is not valid', 'foo.js', 'warning'); 40 | logger.log(10, 'Content is not valid', 'bar.html', 'warning'); 41 | logger.log(4, 'Content is not valid', 'foo.js'); 42 | logger.log(1, 'Content is not valid', 'baz.css', 'error'); 43 | logger.log(1, 'Content is not valid', 'baz.css', 'error'); 44 | logger.log('N/A', 'This file was ignored. Pass the "force" flag if you wish to have it included.', 'bar.min.js', 'ignored'); 45 | 46 | sinon.stub(fs, 'readFile').callsFake( 47 | function(path, encoding, callback) { 48 | if (path.indexOf('junit_report.tpl') > -1) { 49 | return callback(null, fs.readFileSync(path, encoding)); 50 | } 51 | 52 | callback(null, ''); 53 | } 54 | ); 55 | 56 | sinon.stub(fs, 'writeFile').callsFake( 57 | function(path, content, callback) { 58 | callback(null, content); 59 | } 60 | ); 61 | 62 | var reporter = new junit( 63 | { 64 | logger: logger 65 | } 66 | ); 67 | 68 | return reporter.generate().then( 69 | function(results) { 70 | assert.isTrue(fs.writeFile.called, 'writeFile should have been called'); 71 | 72 | assert.equal(results, fs.readFileSync(path.join(__dirname, 'fixture', 'result.xml'), 'utf-8'), 'The result should match what we expect'); 73 | } 74 | ); 75 | } 76 | ); 77 | 78 | it( 79 | 'should generate a valid JUnit report', 80 | function() { 81 | var logger = new Logger.constructor(); 82 | 83 | logger.log(1, 'Content is not valid', 'foo.js'); 84 | logger.log(38, 'Missing space between selector and bracket: &.no-title .asset-user-actions{', 'xmlentity.css', 'error'); 85 | logger.log(39, ' -1) { 91 | return callback(null, fs.readFileSync(path, encoding)); 92 | } 93 | 94 | callback(null, ''); 95 | } 96 | ); 97 | 98 | sinon.stub(fs, 'writeFile').callsFake( 99 | function(path, content, callback) { 100 | callback(null, content); 101 | } 102 | ); 103 | 104 | // Raise mocha's default timeout of 2s to 10s... 105 | // I'm hoping this keeps travis from failing so often :P 106 | 107 | this.timeout(5000); 108 | 109 | var reporter = new junit( 110 | { 111 | logger: logger 112 | } 113 | ); 114 | 115 | return reporter.generate().then( 116 | function(results) { 117 | xsd.validateXML( 118 | results, 119 | path.join(__dirname, 'fixture', 'junit-4.xsd'), 120 | function(err, result) { 121 | assert.isTrue(result.valid, err); 122 | } 123 | ); 124 | } 125 | ); 126 | } 127 | ); 128 | 129 | it( 130 | 'should generate a JUnit report to a custom path', 131 | function(done) { 132 | sinon.stub(Logger); 133 | 134 | Logger.log(1, 'Content is not valid', 'foo.js'); 135 | 136 | sinon.stub(fs, 'readFile').callsArgWith(2, null, ''); 137 | 138 | sinon.stub(fs, 'writeFile').callsFake( 139 | function(path, content, callback) { 140 | callback(null, content); 141 | 142 | assert.equal(path, 'custom_result.xml'); 143 | 144 | done(); 145 | } 146 | ); 147 | 148 | var reporter = new junit( 149 | { 150 | flags: { 151 | junit: 'custom_result.xml' 152 | } 153 | } 154 | ); 155 | 156 | reporter.generate(); 157 | } 158 | ); 159 | } 160 | ); -------------------------------------------------------------------------------- /lib/js.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var base = require('./base'); 4 | 5 | var REGEX = require('./regex'); 6 | 7 | var Formatter = require('content-formatter'); 8 | 9 | var iterateLines = base.iterateLines; 10 | var sub = require('string-sub'); 11 | 12 | /* istanbul ignore next */ 13 | var jsonf = _.bindKeyRight( 14 | JSON, 15 | 'stringify', 16 | (key, value) => { 17 | var retVal; 18 | 19 | if (key === 'start' || key === 'end') { 20 | retVal = value.line; 21 | } 22 | else if (['parent', 'range'].indexOf(key) === -1) { 23 | retVal = value; 24 | } 25 | 26 | return retVal; 27 | }, 28 | 4 29 | ); 30 | 31 | Formatter.JS = Formatter.create( 32 | { 33 | excludes: /[_.-](soy|min|nocsf)\.js$/, 34 | id: 'js', 35 | includes: /\.js$/, 36 | prototype: { 37 | init() { 38 | this.processor = {}; 39 | }, 40 | 41 | format(contents, lint) { 42 | var hasSheBang = this._hasSheBang(contents); 43 | 44 | if (hasSheBang) { 45 | contents = `//${contents}`; 46 | } 47 | 48 | var filePath = this.file; 49 | 50 | var re = this._re; 51 | 52 | var context = { 53 | customIgnore: re.rules.js.IGNORE, 54 | file: filePath, 55 | fileConfig: this._config, 56 | hasSheBang, 57 | lintConfig: this.flags.lint !== false && lint 58 | }; 59 | 60 | contents = this._lint(contents, context); 61 | 62 | if (this.flags.verbose) { 63 | this._processSyntax(contents); 64 | } 65 | 66 | if (hasSheBang) { 67 | contents = contents.substr(2, contents.length); 68 | } 69 | 70 | var newContents = iterateLines( 71 | contents, 72 | (content, index, collection) => { 73 | var rawContent = content; 74 | 75 | content = content.trim(); 76 | 77 | re.hasExtraNewLines(content, index, collection); 78 | 79 | var lineNum = index + 1; 80 | 81 | context.content = content; 82 | context.lineNum = lineNum; 83 | context.nextItem = collection[lineNum] && collection[lineNum].trim(); 84 | context.rawContent = rawContent; 85 | 86 | rawContent = re.iterateRules('common', context); 87 | 88 | content = context.content = rawContent.trim(); 89 | 90 | context.rawContent = rawContent; 91 | 92 | rawContent = re.iterateRules('js', context); 93 | 94 | content = context.content = rawContent.trim(); 95 | 96 | return rawContent; 97 | } 98 | ); 99 | 100 | return newContents; 101 | }, 102 | 103 | _hasSheBang(contents) { 104 | return contents && contents[0] === '#' && contents[1] === '!'; 105 | }, 106 | 107 | _lint(contents, context) { 108 | var lint = context.lintConfig; 109 | 110 | if (lint !== false) { 111 | var linter = require('./lint_js'); 112 | 113 | lint = _.isObjectLike(lint) ? lint : {}; 114 | 115 | context.lintConfig = _.merge(lint, this.config('js.lint')); 116 | 117 | var lintResults = linter(contents, this.file, context); 118 | 119 | var results = lintResults.results; 120 | 121 | if (results.length) { 122 | this._logLintResults(results); 123 | 124 | contents = lintResults.contents; 125 | } 126 | } 127 | 128 | return contents; 129 | }, 130 | 131 | _logLintResults(results) { 132 | var instance = this; 133 | 134 | var lintLogFilter = instance.lintLogFilter; 135 | 136 | if (!_.isFunction(lintLogFilter)) { 137 | lintLogFilter = false; 138 | } 139 | 140 | results.forEach( 141 | (item, index) => { 142 | if (lintLogFilter) { 143 | item = lintLogFilter(item); 144 | } 145 | 146 | instance.log( 147 | item.line, 148 | item.message, 149 | item.ruleId, 150 | { 151 | column: item.column, 152 | ruleId: item.ruleId 153 | } 154 | ); 155 | } 156 | ); 157 | }, 158 | 159 | _printAsSource(contents) { 160 | return contents.split(REGEX.NEWLINE).map( 161 | (item, index) => `${index + 1} ${item}` 162 | ).join('\n'); 163 | }, 164 | 165 | _processSyntax(contents) { 166 | var instance = this; 167 | 168 | try { 169 | var falafel = require('falafel'); 170 | 171 | contents = falafel( 172 | contents, 173 | { 174 | loc: true, 175 | tolerant: true 176 | }, 177 | node => { 178 | var parent = node.parent; 179 | var type = node.type; 180 | 181 | var processor = instance.processor; 182 | 183 | var processorFn = processor[type]; 184 | 185 | if (_.isFunction(processorFn)) { 186 | processorFn.call(instance, node, parent); 187 | } 188 | } 189 | ).toString(); 190 | } 191 | catch (e) { 192 | this.log(e.lineNumber || 'n/a', sub('Could not parse JavaScript: {0}', e.description || e.message)); 193 | 194 | this.logger.verboseDetails[this.file] = this._printAsSource(contents); 195 | } 196 | } 197 | } 198 | } 199 | ); 200 | 201 | module.exports = Formatter.JS; -------------------------------------------------------------------------------- /lib/css.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var Promise = require('bluebird'); 3 | 4 | var base = require('./base'); 5 | 6 | var REGEX = require('./regex'); 7 | 8 | var Formatter = require('content-formatter'); 9 | 10 | var iterateLines = base.iterateLines; 11 | 12 | var REGEX_STYLELINT_POSTFIX = / \(.+?\)$/; 13 | 14 | Formatter.CSS = Formatter.create( 15 | { 16 | excludes: /[_.-](soy|min|nocsf)\.[^.]+$/, 17 | id: 'css', 18 | includes: /\.s?css$/, 19 | prototype: { 20 | format(contents, lint) { 21 | var instance = this; 22 | 23 | var logger = this.log.bind(this); 24 | 25 | var newContents = iterateLines( 26 | contents, 27 | (item, index, collection) => instance.processFile(item, index, collection, logger) 28 | ); 29 | 30 | var fix = this.flags.inlineEdit; 31 | 32 | var context = { 33 | file: this.file, 34 | fix, 35 | lintConfig: this.flags.lint !== false && lint 36 | }; 37 | 38 | return this._lint(contents, context).then( 39 | results => { 40 | instance._logLintResults(results.results); 41 | 42 | return fix ? results.output : newContents; 43 | } 44 | ).catch( 45 | err => { 46 | instance.log( 47 | 'N/A', 48 | `Could not parse the file because of ${err}`, 49 | 'stylelint-error', 50 | { 51 | column: 0, 52 | ruleId: 'stylelint-error' 53 | } 54 | ); 55 | 56 | return newContents; 57 | } 58 | ); 59 | }, 60 | 61 | formatPropertyItem(content) { 62 | return content.replace(REGEX.LEADING_SPACE, '').replace(REGEX.LEADING_INCLUDE, ''); 63 | }, 64 | 65 | processFile(content, index, collection, logger) { 66 | var re = this._re; 67 | 68 | var rawContent = content; 69 | 70 | content = content.trim(); 71 | 72 | re.hasExtraNewLines(content, index, collection); 73 | 74 | var context = this._getContext(content, index, collection, logger); 75 | 76 | context.rawContent = rawContent; 77 | 78 | rawContent = re.iterateRules('common', context); 79 | 80 | content = context.content = rawContent.trim(); 81 | 82 | if (re.hasProperty(content)) { 83 | var processed = this.processProperty(rawContent, content, context.nextItem, context); 84 | 85 | content = processed.content; 86 | rawContent = processed.rawContent; 87 | } 88 | 89 | context.content = content; 90 | context.rawContent = rawContent; 91 | 92 | rawContent = re.iterateRules('css', context); 93 | 94 | content = rawContent.trim(); 95 | 96 | return rawContent; 97 | }, 98 | 99 | processProperty(rawContent, content, nextItem, context) { 100 | var re = this._re; 101 | 102 | content = this.formatPropertyItem(content); 103 | 104 | var propertyContext = _.assign( 105 | {}, 106 | context, 107 | { 108 | content 109 | } 110 | ); 111 | 112 | rawContent = re.iterateRules('css._properties', propertyContext); 113 | 114 | return { 115 | content, 116 | rawContent 117 | }; 118 | }, 119 | 120 | _getContext(content, index, collection, logger) { 121 | var context = { 122 | collection, 123 | file: this.file 124 | }; 125 | 126 | var lineNum = index + 1; 127 | var nextItem = collection[lineNum] && collection[lineNum].trim(); 128 | var previousItem = null; 129 | 130 | if (index > 0) { 131 | previousItem = collection[index - 1]; 132 | previousItem = previousItem && previousItem.trim(); 133 | } 134 | 135 | context.content = content; 136 | context.index = index; 137 | context.lineNum = lineNum; 138 | context.nextItem = nextItem; 139 | context.previousItem = previousItem; 140 | 141 | return context; 142 | }, 143 | 144 | _lint(contents, context) { 145 | var lint = context.lintConfig; 146 | 147 | var results = Promise.resolve( 148 | { 149 | results: [ 150 | { 151 | warnings: [] 152 | } 153 | ] 154 | } 155 | ); 156 | 157 | if (lint !== false) { 158 | var linter = require('./lint_css'); 159 | 160 | lint = _.isObjectLike(lint) ? lint : {}; 161 | 162 | context.lintConfig = _.merge(lint, this.config('css.lint')); 163 | 164 | results = linter(contents, this.file, context); 165 | } 166 | 167 | return results; 168 | }, 169 | 170 | _logLintResults(results) { 171 | var instance = this; 172 | 173 | var lintLogFilter = instance.lintLogFilter; 174 | 175 | if (!_.isFunction(lintLogFilter)) { 176 | lintLogFilter = false; 177 | } 178 | 179 | results[0].warnings.forEach( 180 | (item, index) => { 181 | item.text = item.text.replace(REGEX_STYLELINT_POSTFIX, ''); 182 | 183 | if (lintLogFilter) { 184 | item = lintLogFilter(item); 185 | } 186 | 187 | instance.log( 188 | item.line, 189 | item.text, 190 | item.rule, 191 | { 192 | column: item.column, 193 | ruleId: item.rule 194 | } 195 | ); 196 | } 197 | ); 198 | } 199 | } 200 | } 201 | ); 202 | 203 | module.exports = Formatter.CSS; -------------------------------------------------------------------------------- /test/lint_js_rules/format_multiline_vars.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var sub = require('string-sub'); 3 | 4 | var lint = require('../../lib/lint_js'); 5 | 6 | var linter = lint.linter; 7 | var RuleTester = lint.eslint.RuleTester; 8 | 9 | var ruleTester = new RuleTester(); 10 | 11 | var addES6 = require('../test_utils').addES6(); 12 | 13 | ruleTester.run( 14 | path.basename(__filename, '.js'), 15 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 16 | { 17 | valid: [ 18 | 'var FOO1;', 19 | 'var FOO1 = \'\';', 20 | 'var FOO1 = \'something\' +\n\t\'else\';', 21 | 'var FOO1 = \'something\' +\n\tLiferay.foo();', 22 | 'var FOO1 = \'something\' +\n\tLiferay.foo() + \'foo\';', 23 | 'FOO1 = \'\';', 24 | 'FOO1 = \'something\' +\n\'else\';', 25 | 'FOO1 = \'something\' +\nLiferay.foo();', 26 | 'FOO1 = \'something\' +\nLiferay.foo() + \'foo\';', 27 | 'FOO1 = (\nbar && baz\n);' 28 | ].concat( 29 | [ 30 | {code: 'var {\nFOO1,\nFOO2\n} = {FOO1: 1, FOO2: 2};'}, 31 | {code: 'var [\nFOO1,\nFOO2\n] = [1, 2];'}, 32 | ].map(addES6) 33 | ), 34 | 35 | invalid: [ 36 | { 37 | code: 'var FOO1 = \'something\' +\n\'else\';', 38 | errors: [ { message: 'Multi-line strings should be aligned to the start of the variable name "FOO1"' } ] 39 | }, 40 | { 41 | code: 'var FOO1 = \'something\' +\n\t\t\'else\';', 42 | errors: [ { message: 'Multi-line strings should be aligned to the start of the variable name "FOO1"' } ] 43 | }, 44 | { 45 | code: 'var FOO1 = \'something\' +\n\t\tLiferay.foo();', 46 | errors: [ { message: 'Multi-line strings should be aligned to the start of the variable name "FOO1"' } ] 47 | }, 48 | { 49 | code: 'var FOO1 =\n\'something\' +\n\tLiferay.foo();', 50 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 51 | }, 52 | { 53 | code: 'var FOO1 =\n\t\'something\' +\n\tLiferay.foo();', 54 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 55 | }, 56 | { 57 | code: 'var FOO1 =\n\'something\';', 58 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 59 | }, 60 | { 61 | code: 'FOO1 = \'something\' +\n\t\'else\';', 62 | errors: [ { message: 'Multi-line strings should be aligned to the start of the variable name "FOO1"' } ] 63 | }, 64 | { 65 | code: 'FOO1 = \'something\' +\n\t\t\'else\';', 66 | errors: [ { message: 'Multi-line strings should be aligned to the start of the variable name "FOO1"' } ] 67 | }, 68 | { 69 | code: 'FOO1 = \'something\' +\n\t\tLiferay.foo();', 70 | errors: [ { message: 'Multi-line strings should be aligned to the start of the variable name "FOO1"' } ] 71 | }, 72 | { 73 | code: 'FOO1 =\n\'something\' +\nLiferay.foo();', 74 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 75 | }, 76 | { 77 | code: '\tFOO1 =\n\t\'something\' +\n\tLiferay.foo();', 78 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 79 | }, 80 | { 81 | code: 'FOO1 =\n\'something\';', 82 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 83 | }, 84 | { 85 | code: 'FOO1 =\n(\nbar && baz\n);', 86 | errors: [ { message: 'Variable values should start on the same line as the variable name "FOO1"' } ] 87 | } 88 | ].concat( 89 | [ 90 | { 91 | code: 'var\n{\nFOO1,\nFOO2\n}\n= {FOO1: 1, FOO2: 2};', 92 | errors: [{ message: sub('Destructured assignments should have "{startToken}" on the same line as "{keywordToken}" and "{endToken}" should be on the same line as "{initName}"', {startToken: '{', keywordToken: 'var', endToken: '}', initName: '{FOO1: 1, FOO2: 2}'}) }] 93 | }, 94 | { 95 | code: 'var\n[\nFOO1,\nFOO2\n]\n= [1, 2];', 96 | errors: [{ message: sub('Destructured assignments should have "{startToken}" on the same line as "{keywordToken}" and "{endToken}" should be on the same line as "{initName}"', {startToken: '[', keywordToken: 'var', endToken: ']', initName: '[1, 2]'}) }] 97 | }, 98 | { 99 | code: 'var\n{\nFOO1,\nFOO2\n}\n= FOO;', 100 | errors: [{ message: sub('Destructured assignments should have "{startToken}" on the same line as "{keywordToken}" and "{endToken}" should be on the same line as "{initName}"', {startToken: '{', keywordToken: 'var', endToken: '}', initName: 'FOO'}) }] 101 | }, 102 | { 103 | code: 'var\n[\nFOO1,\nFOO2\n]\n= FOO;', 104 | errors: [{ message: sub('Destructured assignments should have "{startToken}" on the same line as "{keywordToken}" and "{endToken}" should be on the same line as "{initName}"', {startToken: '[', keywordToken: 'var', endToken: ']', initName: 'FOO'}) }] 105 | }, 106 | { 107 | code: 'var\n{\nFOO1,\nFOO2\n} = FOO;', 108 | errors: [{ message: 'Destructured assignments should have "{" on the same line as "var"' }] 109 | }, 110 | { 111 | code: 'var {\nFOO1,\nFOO2\n}\n= FOO;', 112 | errors: [{ message: 'Destructured assignments should have "}" on the same line as "FOO"' }] 113 | }, 114 | ].map(addES6) 115 | ) 116 | } 117 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/sort_props.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var REGEX = require('../regex'); 4 | 5 | var sub = require('string-sub'); 6 | 7 | var ruleUtils = require('../rule_utils'); 8 | 9 | module.exports = context => { 10 | var LIFECYCLE_METHODS = { 11 | init: -1100, 12 | initializer: -1000, 13 | renderUI: -900, 14 | bindUI: -800, 15 | syncUI: -700, 16 | destructor: -600 17 | }; 18 | 19 | var getCacheKey = (...args) => args.join('_'); 20 | 21 | var getPropName = obj => { 22 | var propName = ''; 23 | 24 | var type = obj.type; 25 | 26 | if (type === 'Literal' || type === 'Identifier') { 27 | propName = obj.name || obj.value; 28 | } 29 | else { 30 | propName = context.getSourceCode().getText(obj); 31 | } 32 | 33 | return propName; 34 | }; 35 | 36 | var sourceCode = context.getSourceCode(); 37 | 38 | var getComputedPropertyName = obj => sourceCode.getTokenBefore(obj).value + sourceCode.getText(obj) + sourceCode.getTokenAfter(obj).value; 39 | 40 | var inAttrs = parent => { 41 | var insideAttrs = false; 42 | 43 | var grandParent = parent && parent.parent; 44 | 45 | if (grandParent && grandParent.type === 'Property' && grandParent.key.name === 'ATTRS') { 46 | insideAttrs = true; 47 | } 48 | 49 | return insideAttrs; 50 | }; 51 | 52 | var isFunctionExpression = obj => obj.type === 'FunctionExpression' || obj.type === 'ArrowFunctionExpression'; 53 | 54 | var isLifecycle = _.memoize( 55 | (propName, prevPropName) => LIFECYCLE_METHODS.hasOwnProperty(propName) || LIFECYCLE_METHODS.hasOwnProperty(prevPropName), 56 | getCacheKey 57 | ); 58 | 59 | var isMatchingCase = _.memoize( 60 | (propName, prevPropName) => isUpper(propName) === isUpper(prevPropName), 61 | getCacheKey 62 | ); 63 | 64 | var isMatchingType = (item, prev) => isFunctionExpression(prev.value) === isFunctionExpression(item.value); 65 | 66 | var isPrivate = _.memoize( 67 | str => _.isString(str) && str.charAt(0) === '_' 68 | ); 69 | 70 | var RE_UPPER = /^[^a-z]+$/; 71 | 72 | var isUpper = _.memoize( 73 | str => RE_UPPER.test(str) 74 | ); 75 | 76 | var naturalCompare = ruleUtils.naturalCompare; 77 | 78 | var configuration = context.options[0] || {}; 79 | 80 | var caseSensitive = configuration.casesensitive; 81 | 82 | var checkLifecyleSort = (needsSort, propName, prevPropName, item, prev, parent) => { 83 | var customPrevPropName = LIFECYCLE_METHODS[prevPropName]; 84 | var customPropName = LIFECYCLE_METHODS[propName]; 85 | 86 | if (customPropName) { 87 | if (customPrevPropName) { 88 | needsSort = customPropName < customPrevPropName; 89 | } 90 | else { 91 | needsSort = (!isUpper(prevPropName) && isFunctionExpression(prev.value)); 92 | } 93 | } 94 | 95 | return needsSort; 96 | }; 97 | 98 | var checkRegPropSort = (needsSort, propName, prevPropName, item, prev, caseSensitive, parent) => { 99 | var privatePrevProp = isPrivate(prevPropName); 100 | var privateProp = isPrivate(propName); 101 | 102 | needsSort = (privateProp === privatePrevProp) && isMatchingCase(propName, prevPropName) && naturalCompare(propName, prevPropName, !caseSensitive) === -1 && isMatchingType(item, prev); 103 | 104 | if (privateProp !== privatePrevProp && privatePrevProp && !privateProp) { 105 | needsSort = true; 106 | } 107 | 108 | if (needsSort && !isFunctionExpression(item.value) && !inAttrs(parent)) { 109 | 110 | // Allow a set of properties to be grouped with an extra newline 111 | 112 | needsSort = (item.loc.start.line - prev.loc.end.line) < 2; 113 | } 114 | 115 | return needsSort; 116 | }; 117 | 118 | var checkSort = (propName, prevPropName, item, prev, caseSensitive, parent) => { 119 | var needsSort = false; 120 | 121 | if (isLifecycle(propName, prevPropName)) { 122 | needsSort = checkLifecyleSort(needsSort, propName, prevPropName, item, prev, parent); 123 | } 124 | else { 125 | needsSort = checkRegPropSort(needsSort, propName, prevPropName, item, prev, caseSensitive, parent); 126 | } 127 | 128 | if (REGEX.SERVICE_PROPS.test(propName) || REGEX.SERVICE_PROPS.test(prevPropName)) { 129 | needsSort = false; 130 | } 131 | 132 | return needsSort; 133 | }; 134 | 135 | return { 136 | ObjectExpression(node) { 137 | var prev = null; 138 | 139 | node.properties.forEach( 140 | (item, index, collection) => { 141 | if (index > 0 && item.type !== 'ExperimentalSpreadProperty' && prev.type !== 'ExperimentalSpreadProperty') { 142 | var key = item.key; 143 | var prevKey = prev.key; 144 | 145 | var prevPropName = getPropName(prevKey); 146 | var propName = getPropName(key); 147 | 148 | var needsSort = checkSort(propName, prevPropName, item, prev, caseSensitive, node); 149 | 150 | if (needsSort) { 151 | var note = isLifecycle(propName, prevPropName) ? ' (Lifecycle methods should come first)' : ''; 152 | 153 | var displayPrevPropName = prevPropName; 154 | var displayPropName = propName; 155 | 156 | if (item.computed) { 157 | displayPrevPropName = getComputedPropertyName(prevKey); 158 | displayPropName = getComputedPropertyName(key); 159 | } 160 | 161 | var message = sub('Sort properties: {0} {1}{2}', displayPrevPropName, displayPropName, note); 162 | 163 | context.report(item, message); 164 | } 165 | } 166 | 167 | prev = item; 168 | } 169 | ); 170 | } 171 | }; 172 | }; -------------------------------------------------------------------------------- /test/engine_rules/js.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var chai = require('chai'); 3 | 4 | chai.use(require('chai-string')); 5 | 6 | var RE = require('../../lib/re'); 7 | 8 | var re = new RE(require('../../lib/rules')); 9 | 10 | var assert = chai.assert; 11 | 12 | describe( 13 | 'JS Rule Engine Tests', 14 | function() { 15 | 'use strict'; 16 | 17 | it( 18 | 'should detect and replace improper else format', 19 | function() { 20 | var rule = re.rules.js.elseFormat; 21 | 22 | var input = '} else {'; 23 | var output = '}\nelse {'; 24 | var expectedResult = ['} else', undefined, 'else']; 25 | 26 | var context = { 27 | content: input 28 | }; 29 | 30 | var result = re.testContent(rule, context); 31 | var lineNum = 1; 32 | 33 | assert.deepEqual(expectedResult, _.toArray(result)); 34 | 35 | assert.equal(output, re.replaceItem(result, rule, context)); 36 | } 37 | ); 38 | 39 | it( 40 | 'should detect invalid argument format', 41 | function() { 42 | var rule = re.rules.js.invalidArgumentFormat; 43 | 44 | var shouldMatch = ['fire("foo",', 'setTimeout(function(', 'alert({']; 45 | 46 | shouldMatch.forEach( 47 | function(content, index) { 48 | var context = { 49 | content: content 50 | }; 51 | 52 | var result = re.testContent(rule, context); 53 | var lineNum = 1; 54 | 55 | assert.isTrue(result); 56 | assert.startsWith(re.getMessage(result, rule, context), 'These arguments should each be on their own line'); 57 | assert.equal(content, re.replaceItem(result, rule, context)); 58 | } 59 | ); 60 | 61 | var shouldNotMatch = ['fire(', 'setTimeout(function(){})', 'alert({})', 'function("foo"']; 62 | 63 | shouldNotMatch.forEach( 64 | function(content, index) { 65 | var context = { 66 | content: content 67 | }; 68 | 69 | assert.isFalse(re.testContent(rule, context)); 70 | } 71 | ); 72 | } 73 | ); 74 | 75 | it( 76 | 'should detect invalid conditional format', 77 | function() { 78 | var rule = re.rules.js.invalidConditional; 79 | 80 | var input = 'if (){'; 81 | var output = 'if () {'; 82 | var expectedWarning = 'Needs a space between ")" and "{"'; 83 | 84 | var context = { 85 | content: input 86 | }; 87 | 88 | var result = re.testContent(rule, context); 89 | var lineNum = 1; 90 | 91 | assert.isTrue(result); 92 | assert.startsWith(re.getMessage(result, rule, context), expectedWarning); 93 | assert.equal(output, re.replaceItem(result, rule, context)); 94 | } 95 | ); 96 | 97 | it( 98 | 'should detect invalid function format', 99 | function() { 100 | var rule = re.rules.js.invalidFunctionFormat; 101 | 102 | var input = 'function () {'; 103 | var output = 'function() {'; 104 | var expectedWarning = 'Anonymous function expressions should be formatted as function('; 105 | 106 | var context = { 107 | content: input 108 | }; 109 | 110 | var result = re.testContent(rule, context); 111 | var lineNum = 1; 112 | 113 | assert.isTrue(result); 114 | assert.startsWith(re.getMessage(result, rule, context), expectedWarning); 115 | assert.equal(output, re.replaceItem(result, rule, context)); 116 | } 117 | ); 118 | 119 | it( 120 | 'should detect invalid keyword format', 121 | function() { 122 | var rule = re.rules.js.keywordFormat; 123 | 124 | var input = ['while() {', ' try {', 'catch() { ', '.catch(', 'instance._updateDataSetEntry(key)']; 125 | var output = ['while () {', ' try {', 'catch () { ', '.catch(', 'instance._updateDataSetEntry(key)']; 126 | var expectedResult = [ 127 | ['while(', 'while', '('], 128 | null, 129 | ['catch(', 'catch', '('], 130 | null, 131 | null 132 | ]; 133 | 134 | input.forEach( 135 | function(item, index) { 136 | var context = { 137 | content: item 138 | }; 139 | 140 | var result = re.testContent(rule, context); 141 | var lineNum = 1; 142 | 143 | assert.deepEqual(expectedResult[index], result ? _.toArray(result) : null, 'expectedResult not the same'); 144 | 145 | assert.equal(output[index], re.replaceItem(result, rule, context), 'output not equal'); 146 | 147 | } 148 | ); 149 | } 150 | ); 151 | 152 | it( 153 | 'should detect invalid logging statements', 154 | function() { 155 | var rule = re.rules.js.logging; 156 | 157 | var shouldMatch = ['console.trace()', 'console.log()', 'console.dir()']; 158 | 159 | shouldMatch.forEach( 160 | function(content, index) { 161 | var context = { 162 | content: content 163 | }; 164 | 165 | var result = re.testContent(rule, context); 166 | var lineNum = 1; 167 | 168 | assert.isTrue(result); 169 | assert.isUndefined(re.getMessage(result, rule, context)); 170 | assert.equal(content, re.replaceItem(result, rule, context)); 171 | } 172 | ); 173 | } 174 | ); 175 | 176 | it( 177 | 'should detect invalid variable line spacing', 178 | function() { 179 | var rule = re.rules.js.varLineSpacing; 180 | 181 | var input = 'var foo = 1;'; 182 | var output = input; 183 | var expectedWarning = 'Variable declaration needs a new line after it'; 184 | 185 | var context = { 186 | content: input, 187 | nextItem: 'instance.foo()' 188 | }; 189 | 190 | var result = re.testContent(rule, context); 191 | var lineNum = 1; 192 | 193 | assert.isTrue(result); 194 | assert.startsWith(re.getMessage(result, rule, context), expectedWarning); 195 | assert.equal(output, re.replaceItem(result, rule, context)); 196 | 197 | context.nextItem = ''; 198 | assert.isFalse(re.testContent(rule, context)); 199 | context.nextItem = input; 200 | assert.isFalse(re.testContent(rule, context)); 201 | } 202 | ); 203 | 204 | } 205 | ); -------------------------------------------------------------------------------- /lib/lint_js_rules/format_args.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | 3 | var sub = require('string-sub'); 4 | 5 | module.exports = context => { 6 | var isFunctionExpression = type => _.endsWith(type, 'FunctionExpression'); 7 | 8 | var isCallable = type => isFunctionExpression(type) || type === 'CallExpression'; 9 | 10 | var getAnonymousFnName = fnName => { 11 | if (!fnName) { 12 | fnName = ''; 13 | } 14 | 15 | return fnName; 16 | }; 17 | 18 | var getFnExpLines = (lineBounds, node) => { 19 | var end = lineBounds.end; 20 | var start = lineBounds.start; 21 | 22 | if (node.callee && isFunctionExpression(node.callee.type)) { 23 | start = end; 24 | } 25 | 26 | if (node.arguments && node.arguments.length) { 27 | node.arguments.forEach( 28 | (item, index) => { 29 | var type = item.type; 30 | 31 | if (isCallable(type)) { 32 | var argLineBounds = getFnLines(item); 33 | 34 | start = Math.min(argLineBounds.start, start); 35 | end = Math.max(argLineBounds.end, end); 36 | } 37 | else { 38 | start = item.loc.start.line; 39 | end = item.loc.end.line; 40 | } 41 | } 42 | ); 43 | } 44 | 45 | lineBounds.end = end; 46 | lineBounds.start = start; 47 | 48 | return lineBounds; 49 | }; 50 | 51 | var getFnLines = node => { 52 | var lineBounds = getLineBounds(node); 53 | 54 | var callee = node.callee; 55 | 56 | if (callee) { 57 | var type = callee.type; 58 | 59 | if (isCallable(type)) { 60 | lineBounds = getFnExpLines(lineBounds, node); 61 | } 62 | else if (type === 'MemberExpression') { 63 | lineBounds.end = getLineBounds(node, 'end'); 64 | lineBounds.start = getLineBounds(callee, 'end'); 65 | } 66 | } 67 | 68 | lineBounds.multi = (lineBounds.end > lineBounds.start); 69 | 70 | return lineBounds; 71 | }; 72 | 73 | var getFnName = callee => { 74 | var fnName = callee.name; 75 | 76 | if (!fnName && callee.id) { 77 | fnName = callee.id.name; 78 | } 79 | 80 | var type = callee.type; 81 | 82 | if (isFunctionExpression(type)) { 83 | fnName = getAnonymousFnName(fnName); 84 | } 85 | else if (type === 'MemberExpression') { 86 | fnName = getMethodName(callee); 87 | } 88 | else if (type === 'CallExpression') { 89 | fnName = callee.callee.name; 90 | } 91 | 92 | return fnName; 93 | }; 94 | 95 | var getLineBounds = (loc, prop) => { 96 | var val = {}; 97 | 98 | loc = loc.loc || loc; 99 | 100 | val.start = loc.start.line; 101 | val.end = loc.end.line; 102 | 103 | if (prop) { 104 | val = val[prop]; 105 | } 106 | 107 | return val; 108 | }; 109 | 110 | var getMethodName = callee => { 111 | var fnName = callee.property.name; 112 | 113 | if (!fnName && callee.object.callee) { 114 | fnName = callee.object.callee.name; 115 | } 116 | 117 | return fnName; 118 | }; 119 | 120 | var getMultiLineError = (error, loc, options) => { 121 | var argEnd = loc.end; 122 | var argStart = loc.start; 123 | var fnEnd = options.end; 124 | var fnStart = options.start; 125 | 126 | if (options.multi) { 127 | if (argStart === fnStart) { 128 | error = 'Args should each be on their own line (args on start line)'; 129 | } 130 | else if (argStart === fnStart || argEnd === fnEnd) { 131 | error = 'Args should each be on their own line (args on end line)'; 132 | } 133 | } 134 | 135 | return error; 136 | }; 137 | 138 | var logArgError = (fnLines, fnName, node) => { 139 | var message; 140 | 141 | var obj = processArgs(node.arguments, fnLines, node); 142 | 143 | var error = obj.error; 144 | 145 | if (error) { 146 | message = sub('{0}: {1}(...)', error, fnName); 147 | 148 | context.report(node, message); 149 | } 150 | }; 151 | 152 | var processArgs = (args, options, node) => { 153 | var obj = {}; 154 | 155 | var multiLineFn = options.multi; 156 | 157 | var lastArgEndLine = 0; 158 | var lastArgStartLine = 0; 159 | 160 | var hasNonEmptyFunctionArg = false; 161 | var hasNonEmptyObjectArg = false; 162 | 163 | var argLineEnds = []; 164 | var argLineStarts = []; 165 | 166 | var testLineEndings = false; 167 | 168 | var error = ''; 169 | 170 | args.forEach( 171 | (item, index) => { 172 | var type = item.type; 173 | 174 | var loc = getLineBounds(item.loc); 175 | 176 | var argStart = loc.start; 177 | var argEnd = loc.end; 178 | 179 | argLineEnds.push(argEnd); 180 | argLineStarts.push(argStart); 181 | 182 | if (type === 'FunctionExpression' && item.body.body.length) { 183 | hasNonEmptyFunctionArg = true; 184 | } 185 | else if (type === 'ObjectExpression' && item.properties.length && _.findIndex(item.properties, ['shorthand', true]) === -1) { 186 | hasNonEmptyObjectArg = true; 187 | } 188 | 189 | error = getMultiLineError(error, loc, options); 190 | 191 | if (argStart === lastArgStartLine || argStart === lastArgEndLine) { 192 | testLineEndings = true; 193 | } 194 | 195 | lastArgEndLine = argEnd; 196 | lastArgStartLine = argStart; 197 | } 198 | ); 199 | 200 | if (testLineEndings) { 201 | var argLines = argLineStarts.concat(argLineEnds); 202 | 203 | if (hasNonEmptyFunctionArg || hasNonEmptyObjectArg || _.uniq(argLines).length > 1) { 204 | error = 'Args should each be on their own line (args on same line)'; 205 | } 206 | else if (multiLineFn) { 207 | error = 'Function call can be all on one line'; 208 | } 209 | } 210 | 211 | obj.error = error; 212 | 213 | return obj; 214 | }; 215 | 216 | return { 217 | CallExpression(node) { 218 | var callee = node.callee; 219 | 220 | var fnLines = getFnLines(node); 221 | var fnName = getFnName(callee); 222 | 223 | if (node.arguments.length) { 224 | logArgError(fnLines, fnName, node); 225 | } 226 | else if (fnLines.multi) { 227 | var message = sub('Function call without arguments should be on one line: {0}()', fnName); 228 | 229 | context.report(node, message); 230 | } 231 | } 232 | }; 233 | }; -------------------------------------------------------------------------------- /test/lint_js_rules/format_args.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | var lint = require('../../lib/lint_js'); 4 | 5 | var testUtils = require('../test_utils'); 6 | 7 | var nl = testUtils.nl; 8 | 9 | var addES6 = testUtils.addES6(); 10 | 11 | var linter = lint.linter; 12 | var RuleTester = lint.eslint.RuleTester; 13 | 14 | var ruleTester = new RuleTester(); 15 | /* 16 | alert({}, 1); 17 | alert(function() {}, 1); 18 | alert( 19 | function() { 20 | }, 21 | 1 22 | ); 23 | 24 | alert(function() { 25 | }, 1, 2); 26 | 27 | alert( 28 | 1, 2, 3 29 | ); 30 | 31 | alert( 32 | ); 33 | 34 | alert(function() { 35 | }, 36 | 1); 37 | 38 | alert(function() { 39 | }, 40 | 1 41 | ); 42 | 43 | alert(function() { 44 | }); 45 | 46 | alert(function() {alert('foo');}, 1); 47 | alert({x: 1}, 1); 48 | firstFn(arg1)( 49 | secondFn(arg2)( 50 | thirdFn() 51 | ) 52 | ) 53 | */ 54 | 55 | ruleTester.run( 56 | path.basename(__filename, '.js'), 57 | require('../../lib/lint_js_rules/' + path.basename(__filename)), 58 | { 59 | valid: [ 60 | 'alert();', 61 | 'alert({}, 1);', 62 | 'alert(function() {}, 1);', 63 | 64 | nl('alert(', 65 | 'function() {', 66 | '},', 67 | '1', 68 | ');'), 69 | 70 | nl('(function foo(){', 71 | '}());'), 72 | 73 | nl('(function(){', 74 | '}(', 75 | '));'), 76 | 77 | nl('alert(', 78 | '{', 79 | 'x: 1', 80 | '}', 81 | ')(foo);'), 82 | nl('firstFn(arg1)(', 83 | 'secondFn(arg2)(', 84 | 'thirdFn(', 85 | 'fourthFn(arg3)(', 86 | 'fifthFn()', 87 | ')', 88 | ')', 89 | ')', 90 | ')'), 91 | nl('firstFn(arg1)(', 92 | 'secondFn(', 93 | '{', 94 | 'x: 1', 95 | '}', 96 | ')(', 97 | 'thirdFn()', 98 | ')', 99 | ')'), 100 | // I need to come back to this test... 101 | // It passes if the identifier is the last argument, 102 | // but fails when it's the first. 103 | // I also wonder about variations of this, like (var, fn, var) or (fn, var, obj, var), etc 104 | // UPDATE: (var, obj), (fn, var, fn), and (fn, var, obj) fail as well 105 | // nl('(function(arg1) {', 106 | // 'return function(){', 107 | // '};', 108 | // '})(', 109 | // 'obj,', 110 | // 'function(exp){', 111 | // 'return "";', 112 | // '}', 113 | // ');') 114 | ].concat( 115 | [ 116 | { code: nl( 117 | 'doSomethingPromisable()', 118 | '.then((foo) => doSomethingMorePromisable(foo))', 119 | '.then((bar) => finish(bar))' 120 | ) 121 | } 122 | ].map(addES6) 123 | ), 124 | 125 | invalid: [ 126 | { 127 | code: nl('alert(function() {', 128 | '}, 1, 2);'), 129 | errors: [ { message: 'Args should each be on their own line (args on same line): alert(...)' } ] 130 | }, 131 | { 132 | code: nl('alert(', 133 | '1, 2, 3', 134 | ');'), 135 | errors: [ { message: 'Function call can be all on one line: alert(...)' } ] 136 | }, 137 | { 138 | code: nl('alert(', 139 | ');'), 140 | errors: [ { message: 'Function call without arguments should be on one line: alert()' } ] 141 | }, 142 | { 143 | code: nl('alert(function() {', 144 | '},', 145 | '1);'), 146 | errors: [ { message: 'Args should each be on their own line (args on end line): alert(...)' } ] 147 | }, 148 | { 149 | code: nl('alert(function() {', 150 | '},', 151 | '1', 152 | ');'), 153 | errors: [ { message: 'Args should each be on their own line (args on start line): alert(...)' } ] 154 | }, 155 | { 156 | code: nl('alert(function() {', 157 | '});'), 158 | errors: [ { message: 'Args should each be on their own line (args on start line): alert(...)' } ] 159 | }, 160 | { 161 | code: nl('alert(function() {alert(\'foo\');}, 1);'), 162 | errors: [ { message: 'Args should each be on their own line (args on same line): alert(...)' } ] 163 | }, 164 | { 165 | code: 'alert({x: 1}, 1);', 166 | errors: [ { message: 'Args should each be on their own line (args on same line): alert(...)' } ] 167 | }, 168 | { 169 | code: nl('AUI()[\'add\'](function(){', 170 | '});'), 171 | errors: [ { message: 'Args should each be on their own line (args on start line): AUI(...)' } ] 172 | }, 173 | { 174 | code: nl('(function(){', 175 | '}(', 176 | '{x: 1},2,3', 177 | '));'), 178 | errors: [ { message: 'Args should each be on their own line (args on same line): (...)' } ] 179 | }, 180 | { 181 | code: nl('alert({', 182 | 'x: 1', 183 | '})(foo);'), 184 | errors: [ { message: 'Args should each be on their own line (args on start line): alert(...)' } ] 185 | }, 186 | { 187 | code: nl('alert()(foo, {', 188 | 'x: 1', 189 | '});'), 190 | errors: [ { message: 'Args should each be on their own line (args on same line): alert(...)' } ] 191 | }, 192 | { 193 | code: nl('firstFn(arg1)(secondFn(arg2)(', 194 | 'thirdFn(', 195 | 'fourthFn(arg3)(', 196 | 'fifthFn()', 197 | ')', 198 | ')', 199 | ')', 200 | ')'), 201 | errors: [ { message: 'Args should each be on their own line (args on start line): firstFn(...)' } ] 202 | }, 203 | { 204 | code: nl('firstFn(arg1)(', 205 | 'secondFn(arg2)(', 206 | 'thirdFn(', 207 | 'fourthFn(arg3)(', 208 | 'fifthFn()', 209 | ')', 210 | ')', 211 | '))'), 212 | errors: [ { message: 'Args should each be on their own line (args on end line): firstFn(...)' } ] 213 | }, 214 | { 215 | code: nl('firstFn(arg1)(', 216 | 'secondFn(arg2)(', 217 | 'thirdFn(', 218 | 'fourthFn(', 219 | '{', 220 | 'x: 1', 221 | '})(', 222 | 'fifthFn()', 223 | ')', 224 | ')', 225 | ')', 226 | ')'), 227 | errors: [ { message: 'Args should each be on their own line (args on end line): fourthFn(...)' } ] 228 | }, 229 | { 230 | code: nl('(function(arg1) {', 231 | 'return function(){};', 232 | '})(obj, function(exp){', 233 | '});'), 234 | errors: [ { message: 'Args should each be on their own line (args on same line): (...)' } ] 235 | } 236 | ] 237 | } 238 | ); -------------------------------------------------------------------------------- /lib/engine_rules/css.js: -------------------------------------------------------------------------------- 1 | var REGEX = require('../regex'); 2 | 3 | var REGEX_WHITESPACE = /\s/; 4 | 5 | module.exports = { 6 | hexLowerCase: { 7 | message: false, 8 | regex: /[a-f]/, 9 | replacer(result, rule, context) { 10 | var rawContent = context.rawContent; 11 | 12 | var hexMatch = this.hasHex(rawContent); 13 | 14 | rawContent = rawContent.replace(hexMatch, hexMatch.toUpperCase()); 15 | 16 | return rawContent; 17 | }, 18 | test(content, regex) { 19 | var match = this.hasHex(content); 20 | 21 | return match && this.test(match, regex); 22 | } 23 | }, 24 | 25 | hexRedundant: { 26 | message: false, 27 | regex: /#([0-9A-Fa-f])\1([0-9A-Fa-f])\2([0-9A-Fa-f])\3/, 28 | replacer(result, rule, context) { 29 | var rawContent = context.rawContent; 30 | 31 | var hexMatch = this.hasHex(rawContent); 32 | 33 | rawContent = rawContent.replace(hexMatch, rule._reduceHex.call(this, hexMatch)); 34 | 35 | return rawContent; 36 | }, 37 | test(content, regex) { 38 | var match = this.hasHex(content); 39 | 40 | return match && this.test(match, regex); 41 | }, 42 | _reduceHex(hex) { 43 | return hex.replace(REGEX.HEX_REDUNDANT, REGEX.REPLACE_HEX_REDUNDANT); 44 | } 45 | }, 46 | 47 | missingInteger: { 48 | message: false, 49 | regex: /([^0-9])(\.\d+)/g, 50 | replacer: '$10$2' 51 | }, 52 | 53 | missingListValuesSpace: { 54 | message: false, 55 | regex: /,(?=[^\s])/g, 56 | replacer: ', ', 57 | test(content, regex) { 58 | var needsSpace = this.test(content, regex); 59 | 60 | if (this.hasProperty(content)) { 61 | var value = content.match(REGEX.PROP_KEY); 62 | 63 | needsSpace = needsSpace && value[1].trim() !== 'content'; 64 | } 65 | 66 | return needsSpace; 67 | } 68 | }, 69 | 70 | missingNewlines: { 71 | message: false, 72 | regex: /.*?(\}|\{)/, 73 | replacer(result, rule, context) { 74 | var rawContent = context.rawContent; 75 | 76 | return rawContent.replace( 77 | rule.regex, 78 | (m, bracket) => { 79 | if (bracket == '{') { 80 | m = `\n${m}`; 81 | } 82 | 83 | return m; 84 | } 85 | ); 86 | }, 87 | test(content, regex, rule, context) { 88 | var missingNewlines = false; 89 | 90 | var hasCloser = REGEX.BRACE_CLOSING.test(content); 91 | var hasOpener = REGEX.BRACE_OPENING.test(content); 92 | 93 | var nextItem = (context.nextItem || '').trim(); 94 | var previousItem = (context.previousItem || '').trim(); 95 | 96 | if ((hasCloser && rule._isNextLineInvalid.call(this, nextItem)) || 97 | (hasOpener && rule._isPrevLineInvalid.call(this, previousItem) && content.indexOf('@else') !== 0)) { 98 | missingNewlines = true; 99 | } 100 | 101 | return missingNewlines; 102 | }, 103 | 104 | _isNextLineInvalid(content) { 105 | return (content !== '' && !REGEX.BRACE_CLOSING.test(content) && content.indexOf('@') !== 0); 106 | }, 107 | 108 | _isPrevLineInvalid(content) { 109 | return (content !== '' && !REGEX.BRACE_OPENING.test(content) && !REGEX.CSS_COMMA_END.test(content)); 110 | } 111 | }, 112 | 113 | missingSelectorSpace: { 114 | message: false, 115 | regex: /([^ ])\{\s*$/ 116 | }, 117 | 118 | missingInternalSelectorSpace: { 119 | message: false, 120 | regex: /(.)?([>~+])(.)?/g, 121 | replacer(result, rule, context) { 122 | var item = context.rawContent; 123 | 124 | if (!this.hasProperty(item)) { 125 | item = context.rawContent.replace( 126 | rule.regex, 127 | (m, before, combinator, after) => { 128 | if (rule._hasCombinator(before, after)) { 129 | if (before && !REGEX_WHITESPACE.test(before)) { 130 | before += ' '; 131 | } 132 | 133 | if (!REGEX_WHITESPACE.test(after)) { 134 | after = ` ${after}`; 135 | } 136 | } 137 | 138 | return (before || '') + combinator + after; 139 | } 140 | ); 141 | } 142 | 143 | return item; 144 | }, 145 | test(item, regex, rule, context) { 146 | var hasCombinator = false; 147 | 148 | if (!this.hasProperty(item) && regex.test(item)) { 149 | item.replace( 150 | regex, 151 | (m, before, combinator, after) => { 152 | if (!hasCombinator) { 153 | hasCombinator = rule._hasCombinator(before, after); 154 | } 155 | } 156 | ); 157 | } 158 | 159 | return hasCombinator; 160 | }, 161 | _hasCombinator(before, after) { 162 | return after !== '=' && ((before && !REGEX_WHITESPACE.test(before)) || !REGEX_WHITESPACE.test(after)); 163 | } 164 | }, 165 | 166 | needlessQuotes: { 167 | message: false, 168 | regex: /url\((["'])(.*?)\1\)/, 169 | replacer: 'url($2)' 170 | }, 171 | 172 | needlessUnit: { 173 | message: false, 174 | regex: /(#?)(\b0(?!s\b)[a-zA-Z]{1,}\b)/, 175 | replacer: '0', 176 | test(content, regex) { 177 | var m = content.match(regex); 178 | 179 | return m && !m[1]; 180 | } 181 | }, 182 | 183 | trailingNewlines: { 184 | PRECEDING: 1, 185 | TRAILING: 2, 186 | message(result, rule, context) { 187 | var offset = 1; 188 | 189 | if (result == rule.PRECEDING) { 190 | offset = -1; 191 | } 192 | 193 | context.lineNum += offset; 194 | 195 | return this.message('Needless new line', result, rule, context); 196 | }, 197 | test(content, regex, rule, context) { 198 | var hasCloser = REGEX.BRACE_CLOSING.test(content); 199 | var hasOpener = REGEX.BRACE_OPENING.test(content); 200 | 201 | var nextItem = context.nextItem; 202 | var previousItem = context.previousItem; 203 | 204 | var trailingNewlines = false; 205 | 206 | if (hasCloser && (previousItem === '')) { 207 | trailingNewlines = rule.PRECEDING; 208 | } 209 | else if (hasOpener && (nextItem === '')) { 210 | var afterNextLine = context.collection[context.index + 2]; 211 | 212 | if (!afterNextLine || (afterNextLine && afterNextLine.trim().indexOf('/*') !== 0)) { 213 | trailingNewlines = rule.TRAILING; 214 | } 215 | } 216 | 217 | return trailingNewlines; 218 | } 219 | }, 220 | 221 | trailingComma: { 222 | message: 'Trailing comma in selector', 223 | regex: /,(\s*\{)\s*$/, 224 | replacer: '$1' 225 | }, 226 | 227 | _properties: { 228 | invalidBorderReset: { 229 | message: false, 230 | regex: /(border(-(?:right|left|top|bottom))?):\s?(none|0)(\s?(none|0))?;/, 231 | replacer(result, rule, context) { 232 | var rawContent = context.rawContent; 233 | 234 | return rawContent.replace(result[0], rule._getValidReplacement(result)); 235 | }, 236 | test: 'match', 237 | _getValidReplacement(result) { 238 | return `${result[1]}-width: 0;`; 239 | } 240 | }, 241 | 242 | invalidFormat: { 243 | message: false, 244 | regex: /^\t*([^:]+:(?:(?! )|(?= {2,})))[^;]+;$/, 245 | replacer(result, rule, context) { 246 | var rawContent = context.rawContent; 247 | 248 | return rawContent.replace(/:\s*/, ': '); 249 | }, 250 | test(content, regex) { 251 | return content.indexOf(':') > -1 && regex.test(content); 252 | } 253 | } 254 | } 255 | }; -------------------------------------------------------------------------------- /lib/config/eslint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'amd': false, 4 | 'browser': true, 5 | 'es6': true, 6 | 'mocha': true, 7 | 'node': true 8 | }, 9 | 10 | 'parser': 'babel-eslint', 11 | 12 | 'parserOptions': { 13 | 'ecmaFeatures': { 14 | 'experimentalObjectRestSpread': true, 15 | 'jsx': true 16 | }, 17 | 'ecmaVersion': 7, 18 | 'sourceType': 'module' 19 | }, 20 | 21 | 'globals': { 22 | 'alert': true, 23 | 'AUI': true, 24 | 'CKEDITOR': true, 25 | 'confirm': true, 26 | 'Liferay': true, 27 | 'submitForm': true, 28 | 'themeDisplay': true, 29 | 'tinyMCE': true, 30 | 'YUI': true 31 | }, 32 | 33 | 'rules': { 34 | 'array-bracket-spacing': [2, 'never'], 35 | 'block-scoped-var': 2, 36 | 'brace-style': [2, 'stroustrup'], 37 | 'camelcase': 0, 38 | 'comma-dangle': [2, 'never'], 39 | 'comma-spacing': 2, 40 | 'comma-style': [2, 'last'], 41 | 'complexity': [0, 11], 42 | 'consistent-return': 2, 43 | 'consistent-this': [1, 'instance'], 44 | 45 | // 'csf-no-lonely-if': 2, 46 | 47 | 'csf-array-spacing': 2, 48 | 'csf-array-spacing-chars': 2, 49 | 'csf-catch-arg-name': 2, 50 | 'csf-catch-format': 2, 51 | 'csf-format-args': 2, 52 | 'csf-format-constants': 2, 53 | 54 | 'csf-function-spacing': 2, 55 | 'csf-format-multiline-vars': 2, 56 | 'csf-liferay-language-get': 2, 57 | 'csf-liferay-provide-format': 2, 58 | 'csf-multiple-vars': 2, 59 | 'csf-no-extra-semi': 2, 60 | 'csf-no-is-prefix': 2, 61 | 'csf-no-multiple-return': 2, 62 | 'csf-no-unused-vars': 2, 63 | 'csf-no-use-before-define': [ 64 | 2, 65 | { 66 | 'classes': true, 67 | 'functions': true, 68 | 'samescope': true 69 | } 70 | ], 71 | 'csf-sort-constants': 2, 72 | 'csf-sort-props': 2, 73 | 'csf-sort-requires': 2, 74 | 'csf-sort-vars': 2, 75 | 'curly': [2, 'all'], 76 | 'default-case': 0, 77 | 'dot-location': [2, 'property'], 78 | 'dot-notation': 2, 79 | 'eol-last': 0, 80 | 'eqeqeq': 0, 81 | 'func-names': 0, 82 | 'func-style': [0, 'declaration'], 83 | 'generator-star-spacing': 0, 84 | 'guard-for-in': 0, 85 | 'handle-callback-err': 0, 86 | 'indent': [2, 'tab'], 87 | 'key-spacing': 2, 88 | 'keyword-spacing': [ 89 | 2, 90 | { 91 | after: true, 92 | before: true 93 | } 94 | ], 95 | 'max-depth': [0, 4], 96 | 'max-len': [0, 80, 4], 97 | 'max-nested-callbacks': [0, 2], 98 | 'max-params': [0, 3], 99 | 'max-statements': [0, 10], 100 | 'new-cap': 0, 101 | 'new-parens': 2, 102 | 'newline-after-var': 0, 103 | 'no-alert': 0, 104 | 'no-array-constructor': 2, 105 | 'no-bitwise': 0, 106 | 'no-caller': 2, 107 | 'no-catch-shadow': 2, 108 | 'no-cond-assign': 2, 109 | 'no-console': 2, 110 | 'no-constant-condition': 2, 111 | 'no-control-regex': 2, 112 | 'no-debugger': 2, 113 | 'no-delete-var': 2, 114 | 'no-div-regex': 0, 115 | 'no-dupe-args': 2, 116 | 'no-dupe-keys': 2, 117 | 'no-duplicate-case': 2, 118 | 'no-else-return': 2, 119 | 'no-empty': 0, 120 | 'no-empty-character-class': 2, 121 | 'no-eq-null': 0, 122 | 'no-eval': 2, 123 | 'no-ex-assign': 2, 124 | 'no-extend-native': 2, 125 | 'no-extra-bind': 2, 126 | 'no-extra-boolean-cast': 2, 127 | 'no-extra-parens': 0, 128 | 'no-extra-semi': 0, 129 | 'no-fallthrough': 2, 130 | 'no-floating-decimal': 0, 131 | 'no-func-assign': 2, 132 | 'no-implied-eval': 2, 133 | 'no-inline-comments': 2, 134 | 'no-inner-declarations': [2, 'functions'], 135 | 'no-invalid-regexp': 2, 136 | 'no-irregular-whitespace': 2, 137 | 'no-iterator': 2, 138 | 'no-label-var': 2, 139 | 'no-labels': 2, 140 | 'no-lone-blocks': 2, 141 | 'no-lonely-if': 2, 142 | 'no-loop-func': 2, 143 | 'no-mixed-requires': [0, false], 144 | 'no-mixed-spaces-and-tabs': [0, false], 145 | 'no-multi-spaces': 2, 146 | 'no-multi-str': 2, 147 | 'no-multiple-empty-lines': [ 148 | 2, 149 | { 150 | max: 2 151 | } 152 | ], 153 | 'no-native-reassign': 2, 154 | 'no-negated-in-lhs': 2, 155 | 'no-nested-ternary': 2, 156 | 'no-new': 0, 157 | 'no-new-func': 2, 158 | 'no-new-object': 2, 159 | 'no-new-require': 0, 160 | 'no-new-wrappers': 2, 161 | 'no-obj-calls': 2, 162 | 'no-octal': 2, 163 | 'no-octal-escape': 2, 164 | 'no-param-reassign': 0, 165 | 'no-path-concat': 0, 166 | 'no-plusplus': 0, 167 | 'no-process-env': 0, 168 | 'no-process-exit': 2, 169 | 'no-proto': 2, 170 | 'no-redeclare': 2, 171 | 'no-regex-spaces': 2, 172 | 'no-restricted-modules': 0, 173 | 'no-return-assign': 2, 174 | 'no-script-url': 0, 175 | 'no-self-compare': 0, 176 | 'no-sequences': 2, 177 | 'no-shadow': 0, 178 | 'no-shadow-restricted-names': 2, 179 | 'no-spaced-func': 2, 180 | 'no-sparse-arrays': 2, 181 | 'no-sync': 0, 182 | 'no-ternary': 0, 183 | 'no-throw-literal': 0, 184 | 'no-trailing-spaces': 2, 185 | 'no-undef': 2, 186 | 'no-undef-init': 2, 187 | 'no-undefined': 0, 188 | 'no-underscore-dangle': 0, 189 | 'no-unreachable': 2, 190 | 'no-unused-expressions': 2, 191 | 'no-unused-vars': [ 192 | 2, 193 | { 194 | 'args': 'none', 195 | 'vars': 'local' 196 | } 197 | ], 198 | 'no-use-before-define': [0], 199 | 'no-var': 0, 200 | 'no-void': 0, 201 | 'no-warning-comments': [ 202 | 0, 203 | { 204 | 'location': 'start', 205 | 'terms': ['todo', 'fixme', 'xxx'] 206 | } 207 | ], 208 | 'no-with': 2, 209 | 210 | // 'no-extra-parens': 2, 211 | 212 | 'one-var': 0, 213 | 'operator-assignment': [2, 'always'], 214 | 'padded-blocks': 0, 215 | 'quote-props': 0, 216 | 'quotes': [2, 'single'], 217 | 'radix': 2, 218 | 'semi': [2, 'always'], 219 | 'semi-spacing': [ 220 | 2, 221 | { 222 | after: true, 223 | before: false 224 | } 225 | ], 226 | 'sort-vars': [ 227 | 2, 228 | { 229 | ignoreCase: true 230 | } 231 | ], 232 | 'space-before-blocks': [2, 'always'], 233 | 'space-before-function-paren': [2, 'never'], 234 | 'space-in-parens': [2, 'never'], 235 | 'space-infix-ops': 2, 236 | 'space-unary-ops': [ 237 | 1, 238 | { 239 | 'nonwords': false, 240 | 'words': true 241 | } 242 | ], 243 | 'spaced-comment': [2, 'always'], 244 | 'template-curly-spacing': [2, 'never'], 245 | 'no-self-assign': 2, 246 | 'strict': 0, 247 | 'use-isnan': 2, 248 | 'valid-jsdoc': 0, 249 | 'valid-typeof': 2, 250 | 'vars-on-top': 0, 251 | 'wrap-iife': 0, 252 | 'wrap-regex': 0, 253 | 'yoda': 2, 254 | 255 | // Investigate 256 | 257 | 'jsx-quotes': 0, 258 | 'no-continue': 0, 259 | 'no-invalid-this': 0, 260 | 'no-magic-numbers': 0, 261 | 'no-implicit-globals': 0, 262 | 'no-negated-condition': 0, 263 | 'no-restricted-syntax': 0, 264 | 265 | 'accessor-pairs': 0, 266 | 'array-callback-return': 2, 267 | 'arrow-spacing': [ 268 | 2, 269 | { 270 | after: true, 271 | before: true 272 | } 273 | ], 274 | 'block-spacing': 0, 275 | 'callback-return': 0, 276 | 'computed-property-spacing': [2, 'never'], 277 | 'constructor-super': 2, 278 | 'global-require': 2, 279 | 'id-blacklist': 0, 280 | 'id-length': 0, 281 | 'id-match': 0, 282 | 'init-declarations': 0, 283 | 'linebreak-style': [2, 'unix'], 284 | 'lines-around-comment': [ 285 | 2, 286 | { 287 | 'afterBlockComment': false, 288 | 'afterLineComment': true, 289 | 'beforeBlockComment': true, 290 | 'beforeLineComment': true 291 | } 292 | ], 293 | 'newline-per-chained-call': 0, 294 | 'no-case-declarations': 2, 295 | 'no-class-assign': 2, 296 | 'no-confusing-arrow': 2, 297 | 'no-const-assign': 2, 298 | 'no-dupe-class-members': 2, 299 | 'no-empty-function': 0, 300 | 'no-empty-pattern': 2, 301 | 'no-extra-label': 2, 302 | 'no-implicit-coercion': [ 303 | 2, 304 | { 305 | allow: ['!!', '*', '+'] 306 | } 307 | ], 308 | 'no-restricted-imports': 0, 309 | 'no-this-before-super': 2, 310 | 'no-unexpected-multiline': 2, 311 | 'no-unmodified-loop-condition': 2, 312 | 'no-unneeded-ternary': 2, 313 | 'no-unused-labels': 0, 314 | 'no-useless-call': 2, 315 | 'no-useless-concat': 2, 316 | 'no-useless-constructor': 2, 317 | 'no-whitespace-before-property': 2, 318 | 'object-curly-spacing': [2, 'never'], 319 | 'object-property-newline': 2, 320 | 'one-var-declaration-per-line': 0, 321 | 'operator-linebreak': [2, 'after'], 322 | 'prefer-reflect': 0, 323 | 'require-jsdoc': 0, 324 | 'require-yield': 0, 325 | 'sort-imports': [ 326 | 0, 327 | { 328 | ignoreCase: true, 329 | memberSyntaxSortOrder: ['none', 'all', 'single', 'multiple'] 330 | } 331 | ], 332 | 'yield-star-spacing': 0 333 | } 334 | }; -------------------------------------------------------------------------------- /lib/config/stylelint.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | 'plugins': [ 4 | 'stylelint-order' 5 | ], 6 | 7 | 'globals': { 8 | 9 | }, 10 | 11 | 'rules': { 12 | // 'at-rule-blacklist': 'string|[]', 13 | // 'at-rule-empty-line-before': 'alwaysd|never', 14 | 'at-rule-name-case': 'lower', 15 | // 'at-rule-name-newline-after': 'alwaysd|always-multi-line', 16 | 'at-rule-name-space-after': 'always', 17 | // 'at-rule-no-unknown': true, 18 | 'at-rule-semicolon-newline-after': 'always', 19 | 'at-rule-semicolon-space-before': 'never', 20 | // 'at-rule-whitelist': 'string|[]', 21 | 'block-closing-brace-empty-line-before': 'never', 22 | 'comment-no-empty': true, 23 | // 'comment-word-blacklist': 'string|[]', 24 | // 'custom-property-empty-line-before': 'alwaysd|never', 25 | 'declaration-block-no-redundant-longhand-properties': true, 26 | // 'declaration-empty-line-before': 'alwaysd|never', 27 | // 'declaration-property-unit-blacklist': {}, 28 | // 'declaration-property-unit-whitelist': {}, 29 | // 'declaration-property-value-blacklist': {}, 30 | // 'declaration-property-value-whitelist': {}, 31 | 'font-family-no-duplicate-names': true, 32 | 'function-max-empty-lines': 0, 33 | 'function-name-case': 'lower', 34 | // 'function-url-data-uris': 'alwaysd|never', 35 | // 'function-url-no-scheme-relative': true, 36 | // 'function-url-scheme-whitelist': 'string|[]', 37 | // 'keyframe-declaration-no-important': true, 38 | // 'media-feature-name-blacklist': 'string|[]', 39 | 'media-feature-name-case': 'lower', 40 | // 'media-feature-name-no-unknown': true, 41 | // 'media-feature-name-whitelist': 'string|[]', 42 | 'no-empty-source': false, 43 | 'no-extra-semicolons': true, 44 | // 'no-missing-end-of-source-newline': true, 45 | 'property-case': 'lower', 46 | // 'property-no-unknown': true, 47 | 'selector-attribute-brackets-space-inside': 'never', 48 | // 'selector-attribute-operator-blacklist': 'string|[]', 49 | 'selector-attribute-operator-space-after': 'never', 50 | 'selector-attribute-operator-space-before': 'never', 51 | // 'selector-attribute-operator-whitelist': 'string|[]', 52 | // 'selector-attribute-quotes': 'alwaysd|never', 53 | 'selector-descendant-combinator-no-non-space': true, 54 | 'selector-max-empty-lines': 0, 55 | // 'selector-max-compound-selectors': 'int', 56 | // 'selector-nested-pattern': 'string', 57 | // 'selector-no-qualifying-type': true, 58 | // 'selector-pseudo-class-blacklist': 'string|[]', 59 | 'selector-pseudo-class-case': 'lower', 60 | 'selector-pseudo-class-no-unknown': true, 61 | 'selector-pseudo-class-parentheses-space-inside': 'never', 62 | // 'selector-pseudo-class-whitelist': 'string|[]', 63 | 'selector-pseudo-element-case': 'lower', 64 | 'selector-pseudo-element-no-unknown': true, 65 | // 'selector-type-no-unknown': true, 66 | 'shorthand-property-no-redundant-values': true, 67 | // 'time-min-milliseconds': 'int', 68 | 'unit-case': 'lower', 69 | 'unit-no-unknown': true, 70 | 'value-keyword-case': 'lower', 71 | 'value-list-max-empty-lines': 0, 72 | 73 | 'csf/at-rule-empty-line': [ 74 | 'always', 75 | { 76 | except: ['first-nested'] 77 | } 78 | ], 79 | 'order/properties-alphabetical-order': true, 80 | 'at-rule-no-vendor-prefix': true, 81 | 'block-closing-brace-newline-after': 'always', 82 | 'block-closing-brace-newline-before': 'always', 83 | // 'block-closing-brace-space-after': 'never', 84 | // 'block-closing-brace-space-before': 'never', 85 | // 'block-no-empty': true, 86 | // 'block-no-single-line': true, 87 | 'block-opening-brace-newline-after': 'always', 88 | // 'block-opening-brace-newline-before': 'always', 89 | // 'block-opening-brace-space-after': 'never', 90 | 'block-opening-brace-space-before': 'always', 91 | 'color-hex-case': 'upper', 92 | 'color-hex-length': 'short', 93 | 'color-named': 'never', 94 | // 'color-no-hex': true, 95 | 'color-no-invalid-hex': true, 96 | 'comment-empty-line-before': [ 97 | 'always', 98 | { 99 | except: ['first-nested'] 100 | } 101 | ], 102 | 'comment-whitespace-inside': 'always', 103 | // 'custom-media-pattern': string, 104 | // 'custom-property-pattern': string, 105 | 'declaration-bang-space-after': 'never', 106 | 'declaration-bang-space-before': 'always', 107 | 'declaration-block-no-duplicate-properties': true, 108 | 'declaration-block-no-shorthand-property-overrides': true, 109 | 'declaration-block-semicolon-newline-after': 'always', 110 | 'declaration-block-semicolon-newline-before': 'never', 111 | // 'declaration-block-semicolon-space-after': 'always'|'never'|'always-single-line'|'never-single-line', 112 | 'declaration-block-semicolon-space-before': 'never', 113 | 'declaration-block-single-line-max-declarations': 1, 114 | 'declaration-block-trailing-semicolon': 'always', 115 | // 'declaration-colon-newline-after': 'always'|'always-multi-line', 116 | 'declaration-colon-space-after': 'always', 117 | 'declaration-colon-space-before': 'never', 118 | // 'declaration-no-important': true, 119 | 'font-family-name-quotes': 'single-where-recommended', 120 | 'font-weight-notation': 'named-where-possible', 121 | // 'function-blacklist': 'rgba', 122 | 'function-calc-no-unspaced-operator': true, 123 | 'function-comma-newline-after': 'never', 124 | 'function-comma-newline-before': 'never', 125 | 'function-comma-space-after': 'always', 126 | 'function-comma-space-before': 'never', 127 | // 'function-linear-gradient-no-nonstandard-direction': true, 128 | // 'function-parentheses-newline-inside': 'always'|'always-multi-line'|'never-multi-line', 129 | 'function-parentheses-space-inside': 'never', 130 | 131 | // Will probably need to reimplement this one to ignore strings with quotes 132 | // 'function-url-quotes': 'never', 133 | 134 | // 'function-whitelist': '', 135 | 'function-whitespace-after': 'always', 136 | 'indentation': 'tab', 137 | 'max-empty-lines': 1, 138 | // 'max-line-length': int, 139 | // 'max-nesting-depth': int, 140 | 'media-feature-colon-space-after': 'always', 141 | 'media-feature-colon-space-before': 'never', 142 | // 'media-feature-name-no-vendor-prefix': true, 143 | // 'media-feature-no-missing-punctuation': true, 144 | 'media-feature-range-operator-space-after': 'always', 145 | 'media-feature-range-operator-space-before': 'always', 146 | // 'media-query-list-comma-newline-after': 'always'|'always-multi-line'|'never-multi-line', 147 | // 'media-query-list-comma-newline-before': 'always'|'always-multi-line'|'never-multi-line', 148 | // 'media-query-list-comma-space-after': 'always'|'never'|'always-single-line'|'never-single-line', 149 | // 'media-query-list-comma-space-before': 'always'|'never'|'always-single-line'|'never-single-line', 150 | 'media-feature-parentheses-space-inside': 'never', 151 | // 'no-descending-specificity': true, // Maybe 152 | // 'no-duplicate-selectors': true, // Maybe 153 | 'no-eol-whitespace': true, 154 | // 'no-invalid-double-slash-comments': true, 155 | // 'no-missing-end-of-source-newline': true, 156 | // 'no-unknown-animations': true, 157 | 'number-leading-zero': 'always', 158 | 'number-max-precision': 3, 159 | 'number-no-trailing-zeros': true, 160 | 'length-zero-no-unit': true, 161 | // 'property-blacklist': string|[], 162 | 'property-no-vendor-prefix': true, 163 | // 'property-unit-blacklist': {}, 164 | // 'property-unit-whitelist': {}, 165 | // 'property-value-blacklist': {}, 166 | // 'property-whitelist': string|[], 167 | // 'root-no-standard-properties': true, 168 | 'rule-empty-line-before': [ 169 | 'always', 170 | { 171 | except: ['first-nested'] 172 | } 173 | ], 174 | // 'rule-non-nested-empty-line-before': 'always', 175 | 'selector-class-pattern': [ 176 | /(#\{\$.*?\}|[a-z0-9]+)(-#\{\$.*?\}|-[a-z0-9]+)*$/, 177 | { 178 | resolveNestedSelectors: true 179 | } 180 | ], // Maybe 181 | 'selector-combinator-space-after': 'always', 182 | 'selector-combinator-space-before': 'always', 183 | // 'selector-id-pattern': string, 184 | 'selector-list-comma-newline-after': 'always-multi-line', // Maybe 185 | 'selector-list-comma-newline-before': 'never-multi-line', 186 | 'selector-list-comma-space-after': 'always-single-line', 187 | 'selector-list-comma-space-before': 'never', 188 | // 'selector-max-specificity': string, 189 | // 'selector-no-attribute': true, 190 | // 'selector-no-combinator': true, 191 | // 'selector-no-id': true, 192 | // 'selector-no-type': true, 193 | // 'selector-no-universal': true, 194 | // 'selector-no-vendor-prefix': true, 195 | 'selector-pseudo-element-colon-notation': 'single', 196 | 'selector-type-case': 'lower', 197 | 'string-no-newline': true, 198 | // 'string-quotes': 'single', 199 | // 'time-no-imperceptible': true, 200 | // 'unit-blacklist': string|[], 201 | // 'unit-whitelist': string|[], 202 | 'value-list-comma-newline-after': false, 203 | 'value-list-comma-newline-before': 'never-multi-line', 204 | 'value-list-comma-space-after': 'always-single-line', 205 | 'value-list-comma-space-before': 'never' 206 | // 'value-no-vendor-prefix': true 207 | } 208 | }; -------------------------------------------------------------------------------- /lib/meta.js: -------------------------------------------------------------------------------- 1 | var _ = require('lodash'); 2 | var falafel = require('falafel'); 3 | var path = require('path'); 4 | var Promise = require('bluebird'); 5 | var fs = Promise.promisifyAll(require('fs')); 6 | var argv = require('./argv'); 7 | var base = require('./base'); 8 | var colors = require('cli-color-keywords')(); 9 | 10 | var INDENT = base.INDENT; 11 | 12 | var VERBOSE = argv.v; 13 | 14 | var extractRequires = node => { 15 | var requires = []; 16 | 17 | if (node && node.type == 'Property' && node.key.name == 'requires' && node.value && node.value.elements) { 18 | node.value.elements.forEach( 19 | (item, index) => { 20 | requires.push(item.value); 21 | } 22 | ); 23 | } 24 | 25 | return requires; 26 | }; 27 | 28 | var extractModuleMetaData = (node, metaDataObj) => { 29 | if (node.type == 'Property' && node.key.name == 'modules' 30 | && node.parent.parent.type == 'Property' && node.parent.parent.key.name == 'liferay' 31 | && node.value.type == 'ObjectExpression' 32 | ) { 33 | var objSource = node.value.source(); 34 | 35 | node.value.properties.forEach( 36 | (item, index) => { 37 | var val = item.value; 38 | var metaDataItem = {}; 39 | 40 | if (val.type == 'ObjectExpression') { 41 | val.properties.forEach( 42 | (valItem, valIndex) => { 43 | var propName = valItem.key.name; 44 | var propValue = valItem.value; 45 | 46 | var metaDataItemValue = null; 47 | 48 | if (propName == 'path') { 49 | metaDataItemValue = propValue.value; 50 | 51 | metaDataObj.files.push(metaDataItemValue); 52 | } 53 | else if (propName == 'requires' && propValue.type == 'ArrayExpression') { 54 | // console.log(propName, propValue.elements); 55 | 56 | metaDataItemValue = extractRequires(valItem); 57 | } 58 | 59 | if (metaDataItemValue) { 60 | metaDataItem[propName] = metaDataItemValue; 61 | } 62 | } 63 | ); 64 | } 65 | 66 | metaDataObj.meta[item.key.value] = metaDataItem; 67 | } 68 | ); 69 | } 70 | 71 | return metaDataObj; 72 | }; 73 | 74 | var extractFileMetaData = (node, moduleInfo, fileName) => { 75 | if (node.type == 'Property' && node.key.name == 'requires') { 76 | if (node.parent.parent.type == 'CallExpression') { 77 | var moduleDef = node.parent.parent; 78 | var moduleDefArgs = moduleDef.arguments; 79 | 80 | if (moduleDefArgs && moduleDefArgs.length && moduleDefArgs[0].type == 'Literal') { 81 | var moduleName = moduleDefArgs[0].value; 82 | 83 | if (moduleName && !moduleInfo.fileMeta.hasOwnProperty(moduleName)) { 84 | var fileModuleMetaData = extractRequires(node); 85 | 86 | moduleInfo.fileMeta[moduleName] = { 87 | path: fileName, 88 | requires: fileModuleMetaData 89 | }; 90 | } 91 | } 92 | } 93 | } 94 | 95 | return moduleInfo; 96 | }; 97 | 98 | var readFile = (filePath, fileName, moduleInfo) => fs.readFileAsync(filePath).then( 99 | contents => { 100 | contents = falafel( 101 | contents.toString(), 102 | node => { 103 | moduleInfo = extractFileMetaData(node, moduleInfo, fileName); 104 | } 105 | ); 106 | } 107 | ).catch(_.noop); 108 | 109 | var checkMissingModuleInfo = (files, metaDataObj) => { 110 | var missingModules = metaDataObj.missing; 111 | var moduleFiles = metaDataObj.files; 112 | 113 | if (files.length != moduleFiles.length) { 114 | var largest = files; 115 | var smallest = moduleFiles; 116 | 117 | if (moduleFiles.length > files.length) { 118 | largest = moduleFiles; 119 | smallest = files; 120 | } 121 | 122 | missingModules = largest.reduce( 123 | (prev, item, index) => { 124 | if (smallest.indexOf(item) === -1) { 125 | prev.push(item); 126 | } 127 | 128 | return prev; 129 | }, 130 | missingModules 131 | ); 132 | } 133 | 134 | return metaDataObj; 135 | }; 136 | 137 | var diffArray = (array, values) => array.filter( 138 | (item, index) => values.indexOf(item) == -1 139 | ); 140 | 141 | var checkMetaData = config => { 142 | var done = config.done; 143 | 144 | var liferayModuleDir = config.liferayModuleDir; 145 | 146 | var moduleContents = fs.readFileSync(path.join(liferayModuleDir, 'modules.js')); 147 | 148 | var moduleInfo = { 149 | fileMeta: {}, 150 | files: [], 151 | meta: {}, 152 | missing: [] 153 | }; 154 | 155 | var moduleMetaData = {}; 156 | var moduleFiles = []; 157 | 158 | moduleContents = falafel( 159 | moduleContents.toString(), 160 | { 161 | loc: true, 162 | tolerant: true 163 | }, 164 | node => { 165 | moduleInfo = extractModuleMetaData(node, moduleInfo); 166 | } 167 | ); 168 | 169 | var fileSeries = []; 170 | 171 | fs.readdirAsync(liferayModuleDir).then( 172 | files => { 173 | files = files.filter( 174 | (item, index) => path.extname(item) == '.js' && item !== 'modules.js' 175 | ); 176 | 177 | checkMissingModuleInfo(files, moduleInfo); 178 | 179 | var updateModules = []; 180 | 181 | files.forEach( 182 | (item, index) => { 183 | fileSeries.push(readFile(path.join(liferayModuleDir, item), item, moduleInfo)); 184 | } 185 | ); 186 | 187 | return Promsie.all(fileSeries).then( 188 | results => { 189 | var moduleKeys = Object.keys(moduleInfo.meta); 190 | var fileModuleKeys = Object.keys(moduleInfo.fileMeta); 191 | 192 | var largest = moduleInfo.meta; 193 | var smallest = moduleInfo.fileMeta; 194 | 195 | if (moduleKeys.length < fileModuleKeys.length) { 196 | largest = moduleInfo.fileMeta; 197 | smallest = moduleInfo.meta; 198 | } 199 | 200 | var combined = _.uniq(moduleKeys.concat(fileModuleKeys)); 201 | 202 | var needsMetaSync = []; 203 | var needsModuleData = []; 204 | var needsFileData = []; 205 | 206 | combined.forEach( 207 | (item, index) => { 208 | var moduleMeta = moduleInfo.meta[item]; 209 | var fileMeta = moduleInfo.fileMeta[item]; 210 | 211 | var hasModuleMeta = !!moduleMeta; 212 | var hasFileMeta = !!fileMeta; 213 | 214 | var modFileIdentifier = `${item}: ${hasFileMeta ? fileMeta.path : moduleMeta.path}`; 215 | 216 | if (!hasModuleMeta && hasFileMeta) { 217 | needsModuleData.push(modFileIdentifier); 218 | } 219 | else if (hasModuleMeta && !hasFileMeta) { 220 | needsFileData.push(modFileIdentifier); 221 | } 222 | else { 223 | if (!moduleMeta.requires && fileMeta.requires) { 224 | needsMetaSync.push(modFileIdentifier); 225 | 226 | if (VERBOSE) { 227 | needsMetaSync.push(`${INDENT}modules.js: ${moduleMeta.requires}`); 228 | needsMetaSync.push(`${INDENT + fileMeta.path}: ${fileMeta.requires.join(', ')}`); 229 | } 230 | } 231 | else if (moduleMeta.requires && !fileMeta.requires) { 232 | needsMetaSync.push(modFileIdentifier); 233 | 234 | if (VERBOSE) { 235 | needsMetaSync.push(`${INDENT}modules.js: ${moduleMeta.requires.join(', ')}`); 236 | needsMetaSync.push(`${INDENT + fileMeta.path}: ${fileMeta.requires}`); 237 | } 238 | } 239 | else { 240 | var largeReq = moduleMeta.requires; 241 | var smallReq = fileMeta.requires; 242 | 243 | if (moduleMeta.requires.length < fileMeta.requires.length) { 244 | largeReq = fileMeta.requires; 245 | smallReq = moduleMeta.requires; 246 | } 247 | 248 | largeReq.sort(); 249 | smallReq.sort(); 250 | 251 | if (largeReq.join() !== smallReq.join()) { 252 | needsMetaSync.push(modFileIdentifier); 253 | 254 | if (VERBOSE) { 255 | needsMetaSync.push(`${INDENT}modules.js: ${moduleMeta.requires.join(', ')}`); 256 | needsMetaSync.push(`${INDENT + fileMeta.path}: ${fileMeta.requires.join(', ')}`); 257 | 258 | var merged = fileMeta.requires.concat(moduleMeta.requires); 259 | 260 | merged = _.uniq(merged).sort(); 261 | 262 | needsMetaSync.push(`${INDENT}merged: '${merged.join('\', \'')}'`); 263 | needsMetaSync.push('---'); 264 | } 265 | } 266 | } 267 | } 268 | } 269 | ); 270 | 271 | if (needsMetaSync.length) { 272 | console.log('The following modules/files need their requires arrays synced with modules.js:'); 273 | 274 | console.log(colors.warn(INDENT + needsMetaSync.join('\n' + INDENT))); 275 | 276 | console.log(colors.subtle('----')); 277 | } 278 | 279 | if (needsModuleData.length) { 280 | console.log('The following modules/files need their metadata added to modules.js:'); 281 | 282 | console.log(colors.warn(INDENT + needsModuleData.join('\n' + INDENT))); 283 | 284 | console.log(colors.subtle('----')); 285 | } 286 | 287 | if (needsFileData.length) { 288 | console.log('The following modules/files need their meta data added to their files:'); 289 | 290 | console.log(colors.warn(INDENT + needsFileData.join('\n' + INDENT))); 291 | 292 | console.log(colors.subtle('----')); 293 | } 294 | 295 | if (VERBOSE && moduleInfo.missing.length) { 296 | console.log('The following files are missing any metadata:'); 297 | 298 | console.log(colors.warn(INDENT + moduleInfo.missing.join('\n' + INDENT))); 299 | 300 | console.log(colors.subtle('----')); 301 | } 302 | } 303 | ); 304 | } 305 | ); 306 | }; 307 | 308 | module.exports = { 309 | check(config) { 310 | return checkMetaData(config); 311 | } 312 | }; --------------------------------------------------------------------------------