'use strict';

// MODULES //

var parseJSDoc = require( 'doctrine' ).parse;
var copy = require( '@stdlib/utils/copy' );
var contains = require( '@stdlib/assert/contains' );
var isObject = require( '@stdlib/assert/is-object' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var findJSDoc = require( '@stdlib/_tools/eslint/utils/find-jsdoc' );
var DEFAULTS = require( './defaults.json' );


// VARIABLES //

var DOPTS = {
	'sloppy': true,
	'unwrap': true
};
var slice = Array.prototype.slice;
var rule;


// FUNCTIONS //

/**
* Rule for validating that a JSDoc tag is only used once in a JSDoc comment.
*
* @param {Object} context - ESLint context
* @returns {Object} validators
*/
function main( context ) {
	var options;
	var source;
	var opts;

	opts = copy( DEFAULTS );
	options = context.options[ 0 ];
	if ( hasOwnProp( options, 'exceptions' ) ) {
		opts.exceptions = slice.call( options.exceptions );
	}
	source = context.getSourceCode();

	return {
		'FunctionExpression:exit': validate,
		'FunctionDeclaration:exit': validate,
		'VariableDeclaration:exit': validate,
		'ExpressionStatement:exit': validate
	};

	/**
	* Checks whether JSDoc comments contain JSDoc tags more than once.
	*
	* @private
	* @param {ASTNode} node - AST node
	*/
	function validate( node ) {
		var cache;
		var jsdoc;
		var title;
		var tags;
		var ast;
		var i;

		jsdoc = findJSDoc( source, node );
		cache = {};
		if ( isObject( jsdoc ) ) {
			ast = parseJSDoc( jsdoc.value, DOPTS );
			tags = ast.tags;
			for ( i = 0; i < tags.length; i++ ) {
				title = tags[ i ].title;
				if ( !contains( opts.exceptions, title ) ) {
					if ( hasOwnProp( cache, title ) ) {
						report( title, jsdoc.loc );
					} else {
						cache[ title ] = true;
					}
				}
			}
		}
	}

	/**
	* Reports the error message.
	*
	* @private
	* @param {string} tag - name of duplicated tag
	* @param {Object} loc - lines of code (object with `start` and `end` properties)
	*/
	function report( tag, loc ) {
		var msg = '"@'+tag+'" should only be used once';
		context.report({
			'node': null,
			'message': msg,
			'loc': loc
		});
	}
}


// MAIN //

rule = {
	'meta': {
		'docs': {
			'description': 'enforce that a JSDoc tag is only used once in a JSDoc comment'
		},
		'schema': [
			{
				'type': 'object',
				'properties': {
					'exceptions': {
						'type': 'array'
					}
				},
				'additionalProperties': false
			}
		]
	},
	'create': main
};


// EXPORTS //

module.exports = rule;

'use strict';

// MODULES //

var parseJSDoc = require( 'doctrine' ).parse;
var copy = require( '@stdlib/utils/copy' );
var contains = require( '@stdlib/assert/contains' );
var isObject = require( '@stdlib/assert/is-object' );
var hasOwnProp = require( '@stdlib/assert/has-own-property' );
var findJSDoc = require( '@stdlib/_tools/eslint/utils/find-jsdoc' );
var DEFAULTS = require( './defaults.json' );


// VARIABLES //

var DOPTS = {
	'sloppy': true,
	'unwrap': true
};
var rule;


// FUNCTIONS //

/**
* Rule for validating that a JSDoc tag is only used once in a JSDoc comment.
*
* @param {Object} context - ESLint context
* @returns {Object} validators
*/
function main( context ) {
	var options;
	var source;
	var opts;

	opts = copy( DEFAULTS );
	options = context.options[ 0 ];
	if ( hasOwnProp( options, 'exceptions' ) ) {
		opts.exceptions = options.exceptions.slice();
	}
	source = context.getSourceCode();

	return {
		'FunctionExpression:exit': validate,
		'FunctionDeclaration:exit': validate,
		'VariableDeclaration:exit': validate,
		'ExpressionStatement:exit': validate
	};

	/**
	* Checks whether JSDoc comments contain JSDoc tags more than once.
	*
	* @private
	* @param {ASTNode} node - AST node
	*/
	function validate( node ) {
		var cache;
		var jsdoc;
		var title;
		var tags;
		var ast;
		var i;

		jsdoc = findJSDoc( source, node );
		cache = {};
		if ( isObject( jsdoc ) ) {
			ast = parseJSDoc( jsdoc.value, DOPTS );
			tags = ast.tags;
			for ( i = 0; i < tags.length; i++ ) {
				title = tags[ i ].title;
				if ( !contains( opts.exceptions, title ) ) {
					if ( hasOwnProp( cache, title ) ) {
						report( title, jsdoc.loc );
					} else {
						cache[ title ] = true;
					}
				}
			}
		}
	}

	/**
	* Reports the error message.
	*
	* @private
	* @param {string} tag - name of duplicated tag
	* @param {Object} loc - lines of code (object with `start` and `end` properties)
	*/
	function report( tag, loc ) {
		var msg = '"@'+tag+'" should only be used once';
		context.report({
			'node': null,
			'message': msg,
			'loc': loc
		});
	}
}


// MAIN //

rule = {
	'meta': {
		'docs': {
			'description': 'enforce that a JSDoc tag is only used once in a JSDoc comment'
		},
		'schema': [
			{
				'type': 'object',
				'properties': {
					'exceptions': {
						'type': 'array'
					}
				},
				'additionalProperties': false
			}
		]
	},
	'create': main
};


// EXPORTS //

module.exports = rule;

Call inherited slice method