Skip to content
This repository has been archived by the owner on Mar 23, 2024. It is now read-only.

requireDotNotation: Require dots for es3 keywords when not in es3 mode. #825

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 45 additions & 3 deletions README.md
Expand Up @@ -100,6 +100,9 @@ But you also can specify your own reporter, since this flag accepts relative or
jscs path[ path[...]] --reporter=./some-dir/my-reporter.js
```

### `--es3`
Validates your code as targetted for an ES3 environment. This effects the behavior of certain rules, e.g., requireDotNotation.

### `--esnext`
Attempts to parse your code as ES6 using the harmony version of the esprima parser. Please note that this is currently experimental, and will improve over time.

Expand Down Expand Up @@ -226,6 +229,20 @@ Default: Infinity
"maxErrors": 10
```

### es3

Validates your code as targetted for an ES3 environment. This effects the behavior of certain rules, e.g., requireDotNotation.

Type: `Boolean`

Value: `true`

#### Example

```js
"es3": true
```

### esnext

Attempts to parse your code as ES6 using the harmony version of the esprima parser.
Expand Down Expand Up @@ -2896,18 +2913,19 @@ var d = new e();

### requireDotNotation

Requires member expressions to use dot notation when possible
Requires member expressions to use dot notation when possible. Note, if you specify the --es3 option to JSCS, ES3 keywords and future reserved words MUST remain quoted.

Type: `Boolean`

Values: `true`

JSHint: [`sub`](http://www.jshint.com/docs/options/#sub)

#### Example
#### Example for `"es3": true`

```js
"requireDotNotation": true
"requireDotNotation": true,
"es3": true
```

##### Valid
Expand All @@ -2926,6 +2944,30 @@ var a = b['while']; //reserved word
var a = b['c'];
```

#### Example for `"es3": false` or `"es3": null`

```js
"requireDotNotation": true,
"es3": false
```

##### Valid

```js
var a = b[c];
var a = b.c;
var a = b[c.d];
var a = b[1];
var a = b.while;
```

##### Invalid

```js
var a = b['c'];
var a = b['while']; //reserved words can be property names in ES5
```

### requireYodaConditions

Requires the variable to be the right hand operator when doing a boolean comparison
Expand Down
1 change: 1 addition & 0 deletions bin/jscs
Expand Up @@ -15,6 +15,7 @@ program
.usage('[options] <file ...>')
.option('-c, --config [path]', 'configuration file path')
.option('-e, --esnext', 'attempts to parse esnext code (currently es6)')
.option('--es3', 'validates code as es3')
.option('-s, --esprima <path>', 'attempts to use a custom version of Esprima')
.option('-n, --no-colors', 'clean output without colors')
.option('-p, --preset <preset>', 'preset config')
Expand Down
20 changes: 20 additions & 0 deletions lib/config/configuration.js
Expand Up @@ -11,6 +11,7 @@ var BUILTIN_OPTIONS = {
maxErrors: true,
configPath: true,
esnext: true,
es3: true,
esprima: true,
errorFilter: true
};
Expand All @@ -35,6 +36,7 @@ function Configuration() {
this._overrides = {};
this._presetName = null;
this._esnextEnabled = false;
this._es3Enabled = true;
this._esprima = null;
this._errorFilter = null;
}
Expand Down Expand Up @@ -91,6 +93,7 @@ Configuration.prototype.getProcessedConfig = function() {
result.maxErrors = this._maxErrors;
result.preset = this._presetName;
result.esnext = this._esnextEnabled;
result.es3 = this._es3Enabled;
result.esprima = this._esprima;
result.errorFilter = this._errorFilter;
return result;
Expand Down Expand Up @@ -163,6 +166,15 @@ Configuration.prototype.isESNextEnabled = function() {
return this._esnextEnabled;
};

/**
* Returns `true` if `es3` option is enabled.
*
* @returns {Boolean}
*/
Configuration.prototype.isES3Enabled = function() {
return this._es3Enabled;
};

/**
* Returns `true` if `esprima` option is not null.
*
Expand Down Expand Up @@ -292,6 +304,14 @@ Configuration.prototype._processConfig = function(config) {
this._esnextEnabled = Boolean(config.esnext);
}

if (config.hasOwnProperty('es3')) {
assert(
typeof config.es3 === 'boolean' || config.es3 === null,
'`es3` option requires boolean or null value'
);
this._es3Enabled = Boolean(config.es3);
}

if (config.hasOwnProperty('esprima')) {
this._loadEsprima(config.esprima);
}
Expand Down
3 changes: 2 additions & 1 deletion lib/config/node-configuration.js
Expand Up @@ -8,7 +8,8 @@ var OVERRIDE_OPTIONS = [
'preset',
'maxErrors',
'errorFilter',
'esprima'
'esprima',
'es3'
];

/**
Expand Down
24 changes: 23 additions & 1 deletion lib/js-file.js
Expand Up @@ -13,10 +13,16 @@ var KEYWORD_OPERATORS = {
*
* @name JsFile
*/
var JsFile = function(filename, source, tree) {
var JsFile = function(filename, source, tree, options) {
options = options || {};

this._filename = filename;
this._source = source;
this._tree = tree || {tokens: []};

this._es3 = options.es3 || false;
this._es6 = options.es6 || false;

this._lines = source.split(/\r\n|\r|\n/);
this._tokenRangeStartIndex = null;
this._tokenRangeEndIndex = null;
Expand Down Expand Up @@ -361,6 +367,22 @@ JsFile.prototype = {
}
});
},
/**
* Returns which dialect of JS this file supports.
*
* @returns {String}
*/
getDialect: function() {
if (this._es6) {
return 'es6';
}

if (this._es3) {
return 'es3';
}

return 'es5';
},
/**
* Returns string representing contents of the file.
*
Expand Down
7 changes: 5 additions & 2 deletions lib/rules/require-dot-notation.js
Expand Up @@ -21,6 +21,10 @@ module.exports.prototype = {
},

check: function(file, errors) {
function isES3Allowed(value) {
return file.getDialect() === 'es3' && (utils.isEs3Keyword(value) || utils.isEs3FutureReservedWord(value));
}

file.iterateNodesByType('MemberExpression', function(node) {
if (!node.computed || node.property.type !== 'Literal') {
return;
Expand All @@ -36,8 +40,7 @@ module.exports.prototype = {
value === 'null' ||
value === 'true' ||
value === 'false' ||
utils.isEs3Keyword(value) ||
utils.isEs3FutureReservedWord(value)
isES3Allowed(value)
) {
return;
}
Expand Down
7 changes: 6 additions & 1 deletion lib/string-checker.js
Expand Up @@ -93,7 +93,12 @@ StringChecker.prototype = {
} catch (e) {
parseError = e;
}
var file = new JsFile(filename, str, tree);

var file = new JsFile(filename, str, tree, {
es3: this._configuration.isES3Enabled(),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made this an options hash because I plan on also making tabSize a file level option and didn't want to go adding more and more formal parameters.

es6: this._configuration.isESNextEnabled()
});

var errors = new Errors(file, this._verbose);
var errorFilter = this._configuration.getErrorFilter();

Expand Down
22 changes: 22 additions & 0 deletions test/config/configuration.js
Expand Up @@ -101,6 +101,28 @@ describe('modules/config/configuration', function() {
});
});

describe('isES3Enabled', function() {
it('should return false when null is specified', function() {
configuration.load({es3: null});
assert.equal(configuration.isES3Enabled(), false);
});

it('should return false when false is specified', function() {
configuration.load({es3: false});
assert.equal(configuration.isES3Enabled(), false);
});

it('should return true when true is specified', function() {
configuration.load({es3: true});
assert.equal(configuration.isES3Enabled(), true);
});

it('should return true when unspecified', function() {
configuration.load({});
assert.equal(configuration.isES3Enabled(), true);
});
});

describe('getRegisteredPresets', function() {
it('should return registered presets object', function() {
var preset = {maxErrors: 5};
Expand Down
4 changes: 3 additions & 1 deletion test/config/node-configuration.js
Expand Up @@ -24,14 +24,16 @@ describe('modules/config/node-configuration', function() {
preset: 'jquery',
maxErrors: '2',
errorFilter: path.resolve(__dirname, '../data/error-filter.js'),
esprima: 'esprima-harmony-jscs'
esprima: 'esprima-harmony-jscs',
es3: true
});

configuration.registerPreset('jquery', {});
configuration.load({});

assert.equal(configuration.getProcessedConfig().preset, 'jquery');
assert.equal(configuration.getMaxErrors(), 2);
assert.equal(configuration.isES3Enabled(), true);
assert.equal(typeof configuration.getErrorFilter, 'function');
assert.equal(configuration.hasCustomEsprima(), true);
});
Expand Down
43 changes: 41 additions & 2 deletions test/js-file.js
Expand Up @@ -6,11 +6,12 @@ var sinon = require('sinon');

describe('modules/js-file', function() {

function createJsFile(sources) {
function createJsFile(sources, options) {
return new JsFile(
'example.js',
sources,
esprima.parse(sources, {loc: true, range: true, comment: true, tokens: true})
esprima.parse(sources, {loc: true, range: true, comment: true, tokens: true}),
options || {}
);
}

Expand Down Expand Up @@ -677,6 +678,44 @@ describe('modules/js-file', function() {
});
});

describe('getDialect', function() {
it('should return es5 with no options specified', function() {
var sources = 'var x = 1;\nvar y = 2;';
var file = createJsFile(sources);
assert.equal(file.getDialect(), 'es5');
});

it('should return es6 when es6 is specified as true', function() {
var sources = 'var x = 1;\nvar y = 2;';
var file = createJsFile(sources, {es6: true});
assert.equal(file.getDialect(), 'es6');
});

it('should return es5 when es6 is specified as false', function() {
var sources = 'var x = 1;\nvar y = 2;';
var file = createJsFile(sources, {es6: false});
assert.equal(file.getDialect(), 'es5');
});

it('should return es3 when es3 is specified as true', function() {
var sources = 'var x = 1;\nvar y = 2;';
var file = createJsFile(sources, {es3: true});
assert.equal(file.getDialect(), 'es3');
});

it('should return es5 when es3 is specified as false', function() {
var sources = 'var x = 1;\nvar y = 2;';
var file = createJsFile(sources, {es3: false});
assert.equal(file.getDialect(), 'es5');
});

it('should return es6 when es3 and es6 are both specified as true', function() {
var sources = 'var x = 1;\nvar y = 2;';
var file = createJsFile(sources, {es3: true, es6: true});
assert.equal(file.getDialect(), 'es6');
});
});

describe('getLines', function() {
it('should return specified source code lines', function() {
var sources = ['var x = 1;', 'var y = 2;'];
Expand Down