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

Commit

Permalink
Improve JsFile constructor for better encapsulation
Browse files Browse the repository at this point in the history
  • Loading branch information
mdevils committed Jun 6, 2015
1 parent f963cd4 commit 184bbc6
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 100 deletions.
40 changes: 31 additions & 9 deletions lib/js-file.js
Expand Up @@ -12,24 +12,37 @@ var KEYWORD_OPERATORS = {
* File representation for JSCS.
*
* @name JsFile
* @param {Object} options
* @param {String} options.filename
* @param {String} options.source
* @param {Object} options.esprima
* @param {Object} [options.esprimaOptions]
* @param {Boolean} [options.es3]
* @param {Boolean} [options.es6]
*/
var JsFile = function(filename, source, tree, options) {
var JsFile = function(options) {
options = options || {};

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

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

this._lineBreaks = null;
this._lines = source.split(/\r\n|\r|\n/);
this._lines = this._source.split(/\r\n|\r|\n/);

try {
this._tree = parseJavaScriptSource(this._source, options.esprima, options.esprimaOptions);
} catch (e) {
this._parseErrors.push(e);
}

this._tree.tokens = this._fixEs6Tokens(this._tree.tokens);
this._tokens = this._buildTokenList(this._tree.tokens, this._tree.comments);
this._addEOFToken();
this._applyWhitespaceData(this._tokens, source);
this._applyWhitespaceData(this._tokens, this._source);

var tokenIndexes = this._buildTokenIndex(this._tokens);
this._tokenRangeStartIndex = tokenIndexes.tokenRangeStartIndex;
Expand Down Expand Up @@ -650,6 +663,15 @@ JsFile.prototype = {
return result;
},

/**
* Returns list of parse errors.
*
* @returns {Error[]}
*/
getParseErrors: function() {
return this._parseErrors;
},

/**
* Builds token list using both code tokens and comment-tokens.
*
Expand Down Expand Up @@ -805,7 +827,7 @@ JsFile.prototype = {
* @param {Object} [esprimaOptions]
* @returns {Object}
*/
JsFile.parse = function(source, esprima, esprimaOptions) {
function parseJavaScriptSource(source, esprima, esprimaOptions) {
var finalEsprimaOptions = {
tolerant: true
};
Expand Down Expand Up @@ -859,6 +881,6 @@ JsFile.parse = function(source, esprima, esprimaOptions) {
}

return tree;
};
}

module.exports = JsFile;
75 changes: 38 additions & 37 deletions lib/string-checker.js
Expand Up @@ -93,27 +93,22 @@ StringChecker.prototype = {
checkString: function(source, filename) {
filename = filename || 'input';

var sourceTree;
var parseError;

try {
sourceTree = JsFile.parse(source, this._esprima, this._configuration.getEsprimaOptions());
} catch (e) {
parseError = e;
}

var file = this._createJsFileInstance(filename, source, sourceTree);
var file = new JsFile({
filename: filename,
source: source,
esprima: this._esprima,
esprimaOptions: this._configuration.getEsprimaOptions(),
es3: this._configuration.isES3Enabled(),
es6: this._configuration.isESNextEnabled()
});

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

if (this._maxErrorsExceeded) {
return errors;
}

if (parseError) {
this._addParseError(errors, parseError);
return errors;
}
file.getParseErrors().forEach(function(parseError) {
if (!this._maxErrorsExceeded) {
this._addParseError(errors, parseError);
}
}, this);

// Do not check empty strings
if (file.getFirstToken().type === 'EOF') {
Expand Down Expand Up @@ -225,28 +220,34 @@ StringChecker.prototype = {

filename = filename || 'input';

var sourceTree;
var parseError;

try {
sourceTree = JsFile.parse(source, this._esprima, this._configuration.getEsprimaOptions());
} catch (e) {
parseError = e;
var _this = this;

function createFileInstance(source) {
return new JsFile({
filename: filename,
source: source,
esprima: _this._esprima,
esprimaOptions: _this._configuration.getEsprimaOptions(),
es3: _this._configuration.isES3Enabled(),
es6: _this._configuration.isESNextEnabled()
});
}

if (parseError) {
var parseErrors = new Errors(this._createJsFileInstance(filename, source, sourceTree), this._verbose);
this._addParseError(parseErrors, parseError);
return {output: source, errors: parseErrors};
var file = createFileInstance(source);
var errors = new Errors(file, this._verbose);

var parseErrors = file.getParseErrors();

if (parseErrors.length > 0) {
parseErrors.forEach(function(parseError) {
this._addParseError(errors, parseError);
}, this);

return {output: source, errors: errors};
} else {
var attempt = 0;
var errors;
var file;

do {
file = this._createJsFileInstance(filename, source, sourceTree);
errors = new Errors(file, this._verbose);

// Changes to current sources are made in rules through assertions.
this._checkJsFile(file, errors);

Expand All @@ -258,13 +259,13 @@ StringChecker.prototype = {
break;
}

source = file.render();
sourceTree = JsFile.parse(source, this._esprima, this._configuration.getEsprimaOptions());
file = createFileInstance(file.render());
errors = new Errors(file, this._verbose);

attempt++;
} while (attempt < MAX_FIX_ATTEMPTS);

return {output: source, errors: errors};
return {output: file.getSource(), errors: errors};
}
},

Expand Down
101 changes: 58 additions & 43 deletions test/specs/js-file.js
Expand Up @@ -8,27 +8,43 @@ var fs = require('fs');
describe('modules/js-file', function() {

function createJsFile(sources, options) {
return new JsFile(
'example.js',
sources,
JsFile.parse(sources, esprima),
options || {}
);
var params = {
filename: 'example.js',
source: sources,
esprima: esprima
};

for (var i in options) {
params[i] = options[i];
}

return new JsFile(params);
}

function createHarmonyJsFile(sources) {
return new JsFile(
'example.js',
sources,
JsFile.parse(sources, harmonyEsprima),
{es6: true}
);
return new JsFile({
filename: 'example.js',
source: sources,
esprima: harmonyEsprima,
es6: true
});
}

describe('constructor', function() {

it('empty file should have one token EOF', function() {
var file = new JsFile('example.js', '', null);
var file = new JsFile({filename: 'example.js', source: '', esprima: esprima});
assert(Array.isArray(file.getTokens()));
assert.equal(file.getTokens().length, 1);
assert.equal(file.getTokens()[0].type, 'EOF');
});

it('should accept broken JS file', function() {
var file = new JsFile({
filename: 'example.js',
source: '/1',
esprima: esprima
});
assert(Array.isArray(file.getTokens()));
assert.equal(file.getTokens().length, 1);
assert.equal(file.getTokens()[0].type, 'EOF');
Expand Down Expand Up @@ -67,6 +83,20 @@ describe('modules/js-file', function() {
});
});
});

it('should handle parse errors', function() {
var file = new JsFile({
filename: 'input',
source: '\n2++;',
esprima: esprima,
esprimaOptions: {tolerant: false}
});
assert.equal(file.getParseErrors().length, 1);
var parseError = file.getParseErrors()[0];
assert.equal(parseError.description, 'Invalid left-hand side in assignment');
assert.equal(parseError.lineNumber, 2);
assert.equal(parseError.column, 2);
});
});

describe('isEnabledRule', function() {
Expand Down Expand Up @@ -854,15 +884,8 @@ describe('modules/js-file', function() {
});

describe('getTree', function() {
it('should return specified esprima-tree', function() {
var sources = 'var x;';
var tree = esprima.parse(sources, {loc: true, range: true, comment: true, tokens: true});
var file = new JsFile('example.js', sources, tree);
assert.equal(file.getTree(), tree);
});

it('should return empty token tree for non-existing esprima-tree', function() {
var file = new JsFile('example.js', 'Hello\nWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\nWorld', esprima: esprima});
assert.equal(typeof file.getTree(), 'object');
assert(file.getTree() !== null);
});
Expand Down Expand Up @@ -1001,7 +1024,11 @@ describe('modules/js-file', function() {

describe('getFilename', function() {
it('should return given filename', function() {
var file = new JsFile('example.js', 'Hello\nWorld', null);
var file = new JsFile({
filename: 'example.js',
source: 'Hello\nWorld',
esprima: esprima
});
assert.equal(file.getFilename(), 'example.js');
});
});
Expand All @@ -1020,49 +1047,49 @@ describe('modules/js-file', function() {

describe('getLineBreaks', function() {
it('should return \\n', function() {
var file = new JsFile('example.js', 'Hello\nWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\nWorld', esprima: esprima});
assert.deepEqual(file.getLineBreaks(), ['\n']);
});

it('should return empty array for single line file', function() {
var file = new JsFile('example.js', 'Hello', null);
var file = new JsFile({filename: 'example.js', source: 'Hello', esprima: esprima});
assert.deepEqual(file.getLineBreaks(), []);
});

it('should return \\r', function() {
var file = new JsFile('example.js', 'Hello\rWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\rWorld', esprima: esprima});
assert.deepEqual(file.getLineBreaks(), ['\r']);
});

it('should return \\r\\n', function() {
var file = new JsFile('example.js', 'Hello\r\nWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\r\nWorld', esprima: esprima});
assert.deepEqual(file.getLineBreaks(), ['\r\n']);
});
});

describe('getLineBreakStyle', function() {
it('should return \\n', function() {
var file = new JsFile('example.js', 'Hello\nWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\nWorld', esprima: esprima});
assert.equal(file.getLineBreakStyle(), '\n');
});

it('should return \\r', function() {
var file = new JsFile('example.js', 'Hello\rWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\rWorld', esprima: esprima});
assert.equal(file.getLineBreakStyle(), '\r');
});

it('should return \\r\\n', function() {
var file = new JsFile('example.js', 'Hello\r\nWorld', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\r\nWorld', esprima: esprima});
assert.equal(file.getLineBreakStyle(), '\r\n');
});

it('should return \\n for single line file', function() {
var file = new JsFile('example.js', 'Hello', null);
var file = new JsFile({filename: 'example.js', source: 'Hello', esprima: esprima});
assert.equal(file.getLineBreakStyle(), '\n');
});

it('should return first line break for mixed file', function() {
var file = new JsFile('example.js', 'Hello\nWorld\r\n!', null);
var file = new JsFile({filename: 'example.js', source: 'Hello\nWorld\r\n!', esprima: esprima});
assert.equal(file.getLineBreakStyle(), file.getLineBreaks()[0]);
});
});
Expand Down Expand Up @@ -1189,16 +1216,4 @@ describe('modules/js-file', function() {
assert.equal(spy.getCall(1).args[0].value, '2');
});
});

describe('parse', function() {
it('should accept esprima options', function() {
assert.doesNotThrow(function() {
JsFile.parse('2++;', esprima, {tolerant: true});
});

assert.throws(function() {
JsFile.parse('2++;', esprima, {tolerant: false});
});
});
});
});
4 changes: 3 additions & 1 deletion test/specs/string-checker.js
Expand Up @@ -274,7 +274,9 @@ describe('modules/string-checker', function() {
describe('esprima options', function() {
var code = 'import { foo } from "bar";';
var customEsprima = {
parse: function() {}
parse: function() {
return {tokens: [], comments: []};
}
};

beforeEach(function() {
Expand Down
11 changes: 6 additions & 5 deletions test/specs/token-assert.js
Expand Up @@ -7,11 +7,12 @@ var TokenAssert = require('../../lib/token-assert');
describe('modules/token-assert', function() {

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

describe('whitespaceBetween', function() {
Expand Down

0 comments on commit 184bbc6

Please sign in to comment.