/
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 |
--------------------------------------------------------------------------------
/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 == '
(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 | };
--------------------------------------------------------------------------------