supchik
Version:
Supchik like Borschik but with source maps support and one more thing... things.
554 lines (452 loc) • 18.8 kB
JavaScript
var assert = require('chai').assert;
supchik = require('../supchik'),
file = require('../src/file');
error = require('../src/error'),
wrench = require('wrench'),
techs = {
'js': require('../src/techs/js'),
'custom.js': {},
'complex.custom.js': {},
'json': require('../src/techs/json'),
'txt': require('../src/techs/txt')
},
estraverse = require('estraverse'),
fs = require('fs');
var assertAst = function(ast) {
assert.isObject(ast);
assert.propertyVal(ast, 'type', 'Program');
assert.isArray(ast.body);
}
describe('file', function() {
describe('#file.read', function() {
it('should read file and return string', function() {
var source = file.read('./test/sources/include.js');
assert.isString(source);
});
it('should throw error with not exists file', function() {
assert.throws(function() {
file.read('./test/sources/not-exists.js');
}, error.ReadError);
});
});
describe('#file.write', function() {
it('should write file', function() {
var artifactsPath = './test/.artifacts';
if(fs.existsSync(artifactsPath)) {
wrench.rmdirSyncRecursive(artifactsPath, true);
}
wrench.mkdirSyncRecursive(artifactsPath);
assert.doesNotThrow(function() {
file.write(artifactsPath + '/output.js', 'var a = 100;');
});
});
});
describe('#file.tech', function() {
var options = {
techs: techs,
defaultTech: 'txt'
};
it('should return tech by extension', function() {
var tech = file.tech('file.js', options);
assert.strictEqual(tech, techs['js']);
});
it('should return default tech for unknown extension', function() {
var tech = file.tech('file.unknown', options);
assert.strictEqual(tech, techs['txt']);
});
it('should return custom tech for extension', function() {
var tech = file.tech('file.custom.js', options);
assert.strictEqual(tech, techs['custom.js']);
});
it('should return custom tech for extension (more complex)', function() {
var tech = file.tech('file.complex.custom.js', options);
assert.strictEqual(tech, techs['complex.custom.js']);
});
it('should return null with unknown extension and without default tech', function() {
var tech = file.tech('file.js', { techs: {} });
assert.isNull(tech, null);
});
});
});
describe('techs', function() {
describe('#techs.js', function() {
it('should return valid ast', function() {
var source = 'var a = 100, b = 50; var c = a + b;',
ast = techs.js.parse(source, {
source: 'file.js'
});
assertAst(ast);
});
it('should throw error with not valid js', function() {
var source = 'var a = 100 b = 50; var c = a + b';
assert.throws(function() {
techs.js.parse(source, {
source: 'file.js'
});
}, error.ParseError);
try {
techs.js.parse(source, {
source: 'file.js'
});
} catch(e) {
assert.property(e, 'index');
assert.isNumber(e.index);
assert.property(e, 'lineNumber');
assert.isNumber(e.lineNumber);
assert.property(e, 'column');
assert.isNumber(e.column);
assert.property(e, 'description');
assert.isString(e.description);
}
});
});
describe('#techs.json', function() {
it('should return valid ast', function() {
var source = '{ "a": 100, b: "text" }',
ast = techs.json.parse(source, {
source: 'file.json'
});
assertAst(ast);
assert.propertyVal(ast.body[0], 'type', 'ObjectExpression');
});
it('should throw error with not valid json', function() {
var source = '{ "a": 100 b: "text" }';
assert.throws(function() {
techs.json.parse(source, {
source: 'file.json'
});
}, error.ParseError);
try {
techs.json.parse(source, {
source: 'file.json'
});
} catch(e) {
assert.property(e, 'index');
assert.isNumber(e.index);
assert.property(e, 'lineNumber');
assert.isNumber(e.lineNumber);
assert.property(e, 'column');
assert.isNumber(e.column);
assert.property(e, 'description');
assert.isString(e.description);
}
});
});
describe('#techs.txt', function() {
it('should return valid ast', function() {
var line1 = 'Some text file content.',
line2 = 'Another file line.',
source = line1 + '\n' + line2,
fileName = 'file.txt',
ast = techs.txt.parse(source, {
source: fileName
});
assertAst(ast);
assert.propertyVal(ast.body[0], 'type', 'Literal');
assert.propertyVal(ast.body[0], 'value', source);
assert.isObject(ast.body[0].loc);
assert.isObject(ast.body[0].loc.start);
assert.isObject(ast.body[0].loc.end);
assert.isArray(ast.body[0].loc.range);
assert.deepEqual(ast.body[0].loc.start, { line: 1, column: 0 });
assert.deepEqual(ast.body[0].loc.end, { line: 2, column: line2.length });
assert.deepEqual(ast.body[0].loc.range, [ 0, source.length ]);
assert.propertyVal(ast.body[0].loc, 'source', fileName);
});
it('should return valid ast with empty string', function() {
var source = '',
fileName = 'file.txt',
ast = techs.txt.parse(source, {
source: fileName
});
assertAst(ast);
});
});
});
describe('transforms', function() {
describe('#transforms', function() {
it('should transform with custom transform', function() {
var CustomTransform = {
transform: function(ast, options) {
estraverse.traverse(ast, {
enter: function(node, parent) {
if(node.type === 'Identifier') {
node.name = 'TRANSFORM';
}
}
});
return ast;
}
};
var ast = supchik.compile('./test/sources/a.js', null, {
inputFormat: supchik.Format.FILE_CODE,
outputFormat: supchik.Format.AST,
transforms: [
CustomTransform
]
});
assert.propertyVal(ast.body[0].declarations[0].id, 'name', 'TRANSFORM');
});
it('should transform with custom transform provided as string', function() {
var ast = supchik.compile('./test/sources/a.js', null, {
inputFormat: supchik.Format.FILE_CODE,
outputFormat: supchik.Format.AST,
transforms: [
'./test/transform.js'
]
});
assert.propertyVal(ast.body[0].declarations[0].id, 'name', 'TRANSFORM');
});
it('should throw error with unknown transform', function() {
assert.throws(function() {
supchik.compile('var a = 100;', null, {
transforms: [ 'unknown-transform' ]
});
}, error.SupchikError);
});
});
describe('#transforms.borschik', function() {
it('should include a.js, b.js and c.js', function() {
var source = './test/sources/include.js',
ast = supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE,
outputFormat: supchik.Format.AST
});
assertAst(ast);
assert.propertyVal(ast.body[0].loc, 'source', 'a.js');
assert.propertyVal(ast.body[3].loc, 'source', source);
assert.propertyVal(ast.body[5].loc, 'source', 'b.js');
assert.propertyVal(ast.body[7].loc, 'source', source);
assert.propertyVal(ast.body[10].loc, 'source', 'c.js');
});
it('should include a.json, b.json, c.json and a.txt', function() {
var source = './test/sources/include-escape.js',
ast = supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE,
outputFormat: supchik.Format.AST
});
assertAst(ast);
assert.propertyVal(ast.body[0].declarations[0].init.loc, 'source', 'a.json');
assert.propertyVal(ast.body[0].declarations[0].init, 'type', 'ObjectExpression');
assert.propertyVal(ast.body[1].declarations[0].init.loc, 'source', 'b.json');
assert.propertyVal(ast.body[1].declarations[0].init, 'type', 'ArrayExpression');
assert.propertyVal(ast.body[2].declarations[0].init.loc, 'source', 'c.json');
assert.propertyVal(ast.body[2].declarations[0].init, 'type', 'ArrayExpression');
assert.propertyVal(ast.body[3].declarations[0].init.loc, 'source', 'a.txt');
assert.propertyVal(ast.body[3].declarations[0].init, 'type', 'Literal');
});
it('should include a.txt, b.txt, c.txt and a.json', function() {
var source = './test/sources/include-string-literal.js',
ast = supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE,
outputFormat: supchik.Format.AST
});
assertAst(ast);
assert.propertyVal(ast.body[0].declarations[0].init.loc, 'source', 'a.txt');
assert.propertyVal(ast.body[0].declarations[0].init, 'type', 'Literal');
assert.propertyVal(ast.body[1].declarations[0].init.loc, 'source', 'b.txt');
assert.propertyVal(ast.body[1].declarations[0].init, 'type', 'Literal');
assert.propertyVal(ast.body[2].declarations[0].init.loc, 'source', 'c.txt');
assert.propertyVal(ast.body[2].declarations[0].init, 'type', 'Literal');
assert.propertyVal(ast.body[3].declarations[0].init.loc, 'source', 'a.json');
assert.propertyVal(ast.body[3].declarations[0].init, 'type', 'Literal');
});
it('should include mixed and nested stuff', function() {
var source = './test/sources/include-mixed.js',
ast = supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE,
outputFormat: supchik.Format.AST
});
assertAst(ast);
assert.propertyVal(ast.body[0].declarations[0].init.loc, 'source', 'mixed/a.js');
assert.propertyVal(
ast.body[1].body.body[0].body.body[0].declarations[0].init.loc,
'source', '../b.js'
);
assert.propertyVal(ast.body[2].declarations[0].init.loc, 'source', 'mixed/a.json');
assert.propertyVal(ast.body[3].body.body[0].declarations[0].init.loc, 'source', 'mixed/b.js');
assert.propertyVal(
ast.body[3].body.body[1].body.body[0].declarations[0].init.loc,
'source', '../b.js'
);
});
it('should throw error with not valid source', function() {
var source = './test/sources/include-not-valid.js';
assert.throws(function() {
supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE
});
}, error.ParseError);
});
it('should throw error with not valid source (2)', function() {
var source = './test/sources/include-not-valid2.js';
assert.throws(function() {
supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE
});
}, error.ParseError);
});
it('should throw error with not valid source (3)', function() {
var source = './test/sources/include-not-valid3.js';
assert.throws(function() {
supchik.compile(source, null, {
inputFormat: supchik.Format.FILE_CODE
});
}, error.ParseError);
});
});
});
describe('sourcemap', function() {
it('should generate valid sourcemap and attach sourcemap\'s url', function() {
var source = './test/sources/include.js',
sourceMapUrl = 'source-map.js.map',
output = {};
var generatedSourced = supchik.compile(source, output, {
inputFormat: supchik.Format.FILE_CODE,
sourceMap: sourceMapUrl
});
assert.property(output, 'sourceMap');
assert.isString(output.sourceMap);
if(output.sourceMap) {
var sourceMap;
assert.doesNotThrow(function() {
sourceMap = JSON.parse(output.sourceMap);
});
assert.property(sourceMap, 'version');
assert.isNumber(sourceMap.version);
assert.property(sourceMap, 'names');
assert.isArray(sourceMap.names);
assert.property(sourceMap, 'sources');
assert.isArray(sourceMap.sources);
assert.property(sourceMap, 'mappings');
assert.isString(sourceMap.mappings);
assert.includeMembers(
sourceMap.sources,
[ 'a.js', 'b.js', 'c.js', './test/sources/include.js' ]
);
}
assert.isString(generatedSourced);
var lines = generatedSourced.match(/[^\r\n]+/g),
lastLine = lines[lines.length-1];
assert.match(
lastLine,
new RegExp('\/\/\# sourceMappingURL\=' + sourceMapUrl.replace('.', '\.'))
);
});
it('should not attach sourcemap\'s url if source map file name is not specified', function() {
var source = './test/sources/include.js',
output = {};
var generatedSourced = supchik.compile(source, output, {
inputFormat: supchik.Format.FILE_CODE,
sourceMap: true
});
var lines = generatedSourced.match(/[^\r\n]+/g),
lastLine = lines[lines.length-1];
assert.ok(lastLine.indexOf('//#sourceMappingURL') === -1);
});
});
describe('api', function() {
it('should output all required stuff', function() {
var output = {};
supchik.compile('./test/sources/include.js', output, {
inputFormat: supchik.Format.FILE_CODE
});
assert.property(output, 'source');
assert.isString(output.source);
assert.property(output, 'ast');
assert.isObject(output.ast);
assertAst(output.ast);
assert.property(output, 'compiledSource');
assert.isString(output.compiledSource);
});
it('should output source despite syntax error', function() {
var output = {};
try {
supchik.compile('./test/sources/include-syntax-error.js', output, {
inputFormat: supchik.Format.FILE_CODE
});
} catch(e) {
}
assert.property(output, 'source');
assert.isString(output.source);
});
it('should use custom tech', function() {
var accessCount = 0;
var MyTechJs = {
parse: function(source, options) {
accessCount++;
return {
'type': 'Program',
'body': []
};
},
generate: function(ast, options) {
accessCount++;
return '';
},
generateSourceMap: function(ast, options) {
accessCount++;
return '';
},
generateWithSourceMap: function(ast, options) {
accessCount++;
return { code: '', map: '' };
}
};
supchik.compile('./test/sources/include.js', null, {
inputFormat: supchik.Format.FILE_CODE,
sourceMap: 'output.js.map',
techs: {
js: MyTechJs
}
});
assert.equal(accessCount, 2);
});
it('should share options with techs by 2 steps', function() {
var MyTechJs = {
parse: function(source, options) {
assert.propertyVal(options.shared, 'mySharedOption', true);
return {
'type': 'Program',
'body': []
};
},
generate: function(ast, options) {
assert.propertyVal(options.shared, 'mySharedOption', true);
return '';
},
generateSourceMap: function(ast, options) {
assert.propertyVal(options.shared, 'mySharedOption', true);
return '';
},
generateWithSourceMap: function(ast, options) {
assert.propertyVal(options.shared, 'mySharedOption', true);
return { code: '', map: '' };
}
};
supchik.compile('./test/sources/include.js', null, {
inputFormat: supchik.Format.FILE_CODE,
sourceMap: 'output.js.map',
techs: {
js: MyTechJs
},
mySharedOption: true
});
});
it('should share options with transforms', function() {
var accessCount = 0;
var MyTransform = {
transform: function(ast, options) {
assert.propertyVal(options.shared, 'mySharedOption', true);
accessCount++;
return ast;
}
};
supchik.compile('./test/sources/include.js', null, {
inputFormat: supchik.Format.FILE_CODE,
transforms: [ MyTransform ],
mySharedOption: true
});
assert.equal(accessCount, 1);
});
});