cdocparser
Version:
Extract C style comments and extract context from source
590 lines (478 loc) • 21.1 kB
JavaScript
var fs = require('fs');
var assert = require("assert");
var docParser = require('../');
describe('CDocParser', function(){
var createExtractor = function(opts){
return new docParser.CommentExtractor( function (){ return { type : 'testCtx'}; }, opts );
};
var extractor = createExtractor();
var getCommentsFrom = function(file, opts){
if (opts){
return createExtractor(opts).extract(fs.readFileSync(__dirname + '/fixtures/'+file, 'utf-8'));
}
return extractor.extract(fs.readFileSync(__dirname + '/fixtures/'+file, 'utf-8'));
};
describe('Index', function(){
it('should index the offsets of the start of each line', function(){
var index = docParser.createIndex(fs.readFileSync(__dirname + '/fixtures/block.test.scss', 'utf-8'));
assert.equal(index[0], 0, 'First line starts at offset 0');
assert.equal(index[4], 1, 'Second line starts at offset 4');
});
});
describe('CommentExtractor', function(){
describe('#extract', function(){
describe('Block comments', function(){
it('should extract block comments', function(){
var comments = getCommentsFrom('block.test.scss');
assert.equal(comments.length, 2);
assert.equal(comments[0].lines.length, 3);
assert.equal(comments[0].context.type, 'testCtx');
assert.deepEqual(comments[0].lines, ['', 'Block comment test', '']);
assert.deepEqual(comments[0].commentRange, { start: 1, end: 5 });
assert.deepEqual(comments[1].lines, ['', 'Block comment test 2', '']);
assert.deepEqual(comments[1].commentRange, { start: 7, end: 11 });
});
it('should not split every "*"', function(){
var comments = getCommentsFrom('blockSplitCorrect.test.scss');
assert.deepEqual(comments[0].lines, [ '@param {*} $list - list to purge', 'Test', 'Test']);
});
it('should ignore block comments like /* comment */', function(){
var comments = getCommentsFrom('blockNested.test.scss');
assert.equal(comments.length, 2);
});
it('should normalize the indentation', function(){
var comments = getCommentsFrom('blockIndentation.test.scss');
assert.deepEqual(comments[0].lines, ['test', ' hello', ' world']);
});
it('should not extract block comments if disabled', function(){
var comments = getCommentsFrom('blockIndentation.test.scss', {blockComment : false});
assert.equal(comments.length, 0);
});
});
describe('Line comments', function(){
it('should extract line comments', function (){
var comments = getCommentsFrom('line.test.scss');
assert.equal(comments.length, 2);
assert.deepEqual(comments[0].lines, ['More', '', 'than', 'one', 'line']);
assert.deepEqual(comments[0].commentRange, { start: 1, end: 5 });
assert.deepEqual(comments[1].lines, ['More', 'than', 'one','comment']);
assert.deepEqual(comments[1].commentRange, { start: 7, end: 10 });
});
it('should ignore block comments in line comments', function(){
var comments = getCommentsFrom('lineIgnoreBlock.test.scss');
assert.equal(comments.length, 1);
assert.deepEqual(comments[0].lines, ['', '/** test*/', '']);
});
it('should normalize the indentation', function(){
var comments = getCommentsFrom('lineIndentation.test.scss');
assert.deepEqual(comments[0].lines, ['', 'test', ' hello', ' world', '']);
});
it('should extract line commments with leading spaces', function(){
var comments = getCommentsFrom('lineIndentionBefore.test.scss');
assert.deepEqual(comments[0].lines, [ 'Just a test', ' ' ]);
assert.deepEqual(comments[0].type, 'poster');
});
it('should not extract line comments if disabled', function(){
var comments = getCommentsFrom('lineIndentionBefore.test.scss', {lineComment : false});
assert.equal(comments.length, 0);
});
it('should not parse line poster inside strings', function () {
var comments = getCommentsFrom('linePosterStart.test.scss');
assert.deepEqual(comments, [{
lines: [ 'Only this poster comment!' ],
type: 'poster',
commentRange: { start: 1, end: 3 },
context: { type: 'testCtx' }
}]);
});
it('should preserve subsequent line comments on the same line', function(){
var comments = getCommentsFrom('linePreserveNested.test.scss');
assert.deepEqual(comments[0].lines, ['Preserves /// nested', '/// comments', '//// all /// over', 'the ////// place']);
});
});
describe('Mixed style comments', function(){
it('should extract comments', function (){
var comments = getCommentsFrom('mixed.test.scss');
assert.equal(comments.length, 3);
assert.deepEqual(comments[0].lines, ['', 'Block comment test', '']);
assert.deepEqual(comments[1].lines, ['', 'Line comment test', '']);
assert.deepEqual(comments[2].lines, ['Single line test']);
assert.deepEqual(comments[2].commentRange, { start: 14, end: 14 });
});
it('should either have line or block comments', function(){
var commentsBlock = getCommentsFrom('mixed.test.scss', {lineComment : false});
assert.equal(commentsBlock.length, 1);
assert.deepEqual(commentsBlock[0].lines, ['', 'Block comment test', '']);
var commentsLine = getCommentsFrom('mixed.test.scss', {blockComment : false});
assert.equal(commentsLine.length, 2);
assert.deepEqual(commentsLine[1].lines, ['Single line test']);
assert.deepEqual(commentsLine[1].commentRange, { start: 14, end: 14 });
});
});
describe('Block comment spanning a single line', function(){
it('should extract comments', function (){
var comments = getCommentsFrom('singleLineBlock.test.scss');
assert.equal(comments.length, 1);
assert.deepEqual(comments[0].lines, ['block comment on a single line']);
assert.deepEqual(comments[0].commentRange, { start: 1, end: 1 });
});
});
describe('Custom comment regex', function(){
it('should extract comments', function (){
var extractor = new docParser.CommentExtractor( function (){}, {
lineCommentStyle: '//',
blockCommentStyle: '/*'
});
var getCommentsFrom = function(file){
return extractor.extract(fs.readFileSync(__dirname + '/fixtures/'+file, 'utf-8'));
};
var comments = getCommentsFrom('custom-regex.test.scss');
assert.equal(comments.length, 2);
assert.deepEqual(comments[0].lines, ['Block comment test', '']);
assert.deepEqual(comments[0].commentRange, { start: 1, end: 5 });
assert.deepEqual(comments[1].lines, ['Single line test']);
assert.deepEqual(comments[1].commentRange, { start: 7, end: 7 });
});
});
describe('Error on both disabled', function(){
it('should throw an error if both styles are disabled', function (){
try{
var extractor = new docParser.CommentExtractor( function (){}, {
lineComment: false,
blockComment: false
});
} catch (e) {
assert.equal(e+'', 'Error: At least one comment style has to be enabled.');
}
});
});
});
});
describe('CommentParser', function(){
var parser;
// Test comments
var comments = [
{ lines : ['test', 'test', '@test'], commentRange : { start : 1, end :3 }, context : { type : 'testType1'} },
{ lines : ['test', 'test', ' @test'], commentRange : { start : 1, end :3 }, context : { type : 'testType1'} },
{ lines : ['test', '@ignore ingore this', 'test', '@ignore ingore this'], commentRange : { start : 1, end :3 }, context : { type : 'testType1'} },
{ lines : ['test', 'test', '@aliasTest'], commentRange : { start : 1, end :3 }, context : { type : 'testType2'} },
{ lines : ['test', 'test', '@test'], commentRange : { start : 1, end :3 }, context : { type : 'testType2'} },
{ lines : ['test', 'test', '@test'], commentRange : { start : 1, end :3 }, context : { type : 'testType3'} },
{ lines : ['test', 'test', '@flag'], commentRange : { start : 1, end :3 }, context : { type : 'testType3'} },
{ lines : ['@multiline\nThis is a\nmultiline\nannotation\n'], commentRange : { start : 1, end :3 }, context : { type : 'testType3'} }
];
// Test Annotations
var annotations = {
_ : {
alias : {
"aliasTest" : "test"
}
},
ignore : {
parse : function() {}
},
test : {
parse : function ( commentLine ){
return "Working";
},
default : function(){
return ["Default"];
}
},
flag : {
parse : function(){
return true;
}
},
poster : {
parse : function(commentLine){
return commentLine;
}
},
posterOverwrite : {
parse : function(commentLine){
return commentLine;
},
overwritePoster: true
},
multiline : {
parse : function(commentLine){
return commentLine;
}
},
allowedLimited : {
parse : function(){},
allowedOn : ['workingType']
}
};
beforeEach(function(){
parser = new docParser.CommentParser( annotations );
});
describe('#parse', function(){
it('should group comments by context type', function(){
var result = parser.parse ( comments );
assert.equal(result.length , 8);
});
it('should add default values', function(){
var result = parser.parse ( comments );
assert.equal(result[1].test[0] , 'Default' );
});
it('should join lines without annotation into description', function(){
var result = parser.parse ( comments );
assert.equal(result[0].description , 'test\ntest\n');
});
it('should resolve a alias to the real name', function(){
var result = parser.parse ( comments );
assert.equal(result.length , 8);
assert.equal(result[0].test[0] , 'Working' );
});
it('should convert an annotation that returns a boolean to a flag', function(){
var result = parser.parse ( comments );
assert.equal(result[6].flag , true );
});
it('should parse a multiline annotation', function(){
var result = parser.parse ( comments );
assert.equal(result[7].multiline[0] , "\nThis is a\nmultiline\nannotation\n");
});
it('should ignore annotations that aren\'t at the start of the line', function(){
var result = parser.parse ( comments );
assert.equal(result[1].test[0] , 'Default');
});
it('should ignore annotations that won\'t return anything', function(){
var result = parser.parse ( comments );
assert.deepEqual(result[2].ignore , []);
});
it('should emit an error if annotation was not found', function(done){
parser.on('warning', function(err){
assert.equal(err + '', 'Error: Parser for annotation `notFound` not found.');
done();
});
var result = parser.parse ( [{ lines : ['@notFound'], context : { type : 'testType1'}, commentRange : { start : 1, end : 3} }] );
});
it('should emit an error if annotation was not found and have ctx', function(done){
parser.on('warning', function(err){
assert.equal(err + '', 'Error: Parser for annotation `notFound` not found. Location: `file.scss:1:3`');
done();
});
var result = parser.parse ( [{ lines : ['@notFound'], context : { type : 'testType1'}, commentRange : { start : 1, end : 3} }], 'file.scss' );
});
it('should apply annotations in a block poster comment to each item', function () {
var comments = getCommentsFrom('blockPoster.test.scss');
var result = parser.parse ( comments );
assert.equal(result.length , 2);
assert.equal(result[0].flag , undefined);
assert.equal(result[1].flag , true);
});
it('should extend poster comments except for posterOverwrite `true`', function () {
var comments = getCommentsFrom('posterOverwrite.test.scss');
var result = parser.parse ( comments );
assert.equal(result.length , 2);
assert.deepEqual(result[1].poster , [ 'Poster', 'OnItem' ]);
assert.deepEqual(result[1].posterOverwrite , [ 'OnItem' ]);
});
it('should apply annotations in a line poster comment to each item', function () {
var comments = getCommentsFrom('linePoster.test.scss');
var result = parser.parse ( comments );
assert.equal(result.length , 2);
assert.equal(result[0].flag , undefined);
assert.equal(result[1].flag , true);
});
it('should emit an error if more than one poster comment was used', function(done){
var comments = extractor.extract('/**\n * Poster Comment\n **/ \n\n\n /**\n * Poster Comment\n **/');
parser.on('warning', function(err){
assert.equal(err + '', 'Error: You can\'t have more than one poster comment.');
done();
});
var result = parser.parse (comments);
});
it('should emit an error if more than one poster comment was used and include location', function(done){
var comments = extractor.extract('/**\n * Poster Comment\n **/ \n\n\n /**\n * Poster Comment\n **/');
parser.on('warning', function(err){
assert.equal(err + '', 'Error: You can\'t have more than one poster comment. Location: `file.scss:6:8`');
done();
});
var result = parser.parse (comments, 'file.scss');
});
it('should emit an warning if not allowed comment type', function(done){
parser.on('warning', function(err){
assert.equal(err + '', 'Error: Annotation `allowedLimited` is not allowed on comment from type `testType3`.');
done();
});
var result = parser.parse ([{
lines : ['@allowedLimited'],
context : { type : 'testType3'},
commentRange : { start : 1, end :3 }
}]);
});
it('should emit an warning if not allowed comment type and include location', function(done){
parser.on('warning', function(err){
assert.equal(err + '', 'Error: Annotation `allowedLimited` is not allowed on comment from type `testType3` in `file.js:1:3`.');
done();
});
var result = parser.parse ([{
lines : ['@allowedLimited'],
context : { type : 'testType3'},
commentRange : { start : 1, end :3 }
}], 'file.js');
});
it('should work on allowed context.type', function(){
var result = parser.parse ([{
lines : ['@allowedLimited'],
context : { type : 'workingType'},
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(result[0].allowedLimited , []);
});
describe('# multiple true/false', function(){
beforeEach(function(){
var annotations = {
_ : {
alias : { }
},
test : {
parse : function(line){
return line;
},
multiple : false
}
};
parser = new docParser.CommentParser(annotations);
});
it('should work for false', function(){
var rawTestResult = parser.parse ([{
lines : ['@test Hello'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.equal(rawTestResult[0].test, 'Hello');
});
it('should warn if used multiple times', function(done){
parser.on('warning', function(err){
assert.equal(err + '', 'Error: Annotation `test` is only allowed once per comment, second value will be ignored.');
done();
});
var rawTestResult = parser.parse ([{
lines : ['@test First', '@test Second'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.equal(rawTestResult[0].test, 'First');
});
});
describe('# Default and extended values', function(){
var annotations = {
_ : {
alias : {
otherName : 'test'
}
},
test : {
parse : function(line){
return line;
},
default : function(parsedComment){
return ['default'];
},
autofill : function(parsedComment){
parsedComment.test.push('extended');
}
}
};
it('should extend correctly', function(){
parser = new docParser.CommentParser(annotations);
var extendedResult = parser.parse ([{
lines : ['@test hello'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(extendedResult[0].test, ['hello', 'extended']);
var defaultResult = parser.parse ([{
lines : ['Just a description'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(defaultResult[0].test, ['default', 'extended']);
});
it('should respect the config', function(){
parser = new docParser.CommentParser(annotations, {
autofill : true
});
var extendedResult = parser.parse ([{
lines : ['@test hello'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(extendedResult[0].test, ['hello', 'extended']);
parser = new docParser.CommentParser(annotations, {
autofill : false
});
var defaultResult = parser.parse ([{
lines : ['Just a description'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(defaultResult[0].test, ['default']);
parser = new docParser.CommentParser(annotations, {
autofill : ['test']
});
var defaultTestResult = parser.parse ([{
lines : ['Just a description'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(defaultTestResult[0].test, ['default', 'extended']);
parser = new docParser.CommentParser(annotations, {
autofill : ['otherName']
});
var otherNameResult = parser.parse ([{
lines : ['Just a description'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}]);
assert.deepEqual(otherNameResult[0].test, ['default', 'extended']);
});
});
describe('# Pass in meta information about parsed object', function(){
it('should include meta information', function(done){
var annotations = {
_ : {
alias : { }
},
test : {
parse : function(line, info, id){
assert.equal(id, 'FileID');
assert.deepEqual(info, {"description":"","commentRange":{"start":1,"end":3},"context":{"type":"demo"}});
done();
},
multiple: false
}
};
parser = new docParser.CommentParser(annotations);
var extendedResult = parser.parse ([{
lines : ['@test hello'],
context : { type : 'demo' },
commentRange : { start : 1, end :3 }
}], 'FileID');
});
});
describe('File with CRLF', function(){
it('should extract comments', function (){
var comments = getCommentsFrom('crlf.test.scss');
assert.equal(comments.length, 2);
assert.deepEqual(comments[0].lines, [
'Block comment with CRLF.',
'With multiple lines.'
]);
assert.deepEqual(comments[0].commentRange, { start: 1, end: 4 });
assert.deepEqual(comments[1].lines, [
'',
'Comment with CRLF.',
'It have multiple lines in a single comment.',
''
]);
assert.deepEqual(comments[1].commentRange, { start: 6, end: 9 });
});
});
});
});
});