Source: Sniff/SyntaxTree/ArgumentsSpacing.js

/*
* @package jscodesniffer
* @author sheiko
* @license MIT
* @copyright (c) Dmitry Sheiko http://www.dsheiko.com
* jscs standard:Jquery
* jshint unused:false
* Code style: http://docs.jquery.com/JQuery_Core_Style_Guidelines
*/
 /**
* A module representing a sniffer.
* @module lib/Sniff/SyntaxTree/ArgumentsSpacing
*/

// UMD boilerplate according to https://github.com/umdjs/umd
if ( typeof module === "object" && typeof define !== "function" ) {
	/**
	* Override AMD `define` function for RequireJS
	* @param {function( function, Object, Object )} factory
	*/
	var define = function ( factory ) {
		module.exports = factory( require, exports, module );
	};
}
/**
	* @param {function( string )} require
	*/
define(function( require ) {
"use strict";
	/**
	* @type {utilsSniff/Utils}
	*/
var utils = require( "../Utils" ),
		/**
		* @constant
		* @type {String}
		* @default
		*/
		NAME = "ArgumentsSpacing",
	/**
	* @constructor
	* @alias module:lib/Sniff/SyntaxTree/ArgumentsSpacing
	* @param {module:lib/SourceCode} sourceCode
	* @param {module:lib/Mediator} mediator
	* @param {module:lib/TokenIterator} tokenIterator
	*/
	Sniff = function( sourceCode, mediator, tokenIterator ) {
		/**
		* @type {Mixin}
		*/
		var mixin = utils.Mixin( sourceCode, mediator, NAME );
		/** @lends module:lib/Sniff/SyntaxTree/ArgumentsSpacing.prototype */
		return {
			/**
			* Check the contract
			* @access public
			* @param {Object} rule
			*/
			validateRule: function( rule ) {
				utils.validateRule( rule, "allowArgPrecedingWhitespaces", "number" );
				utils.validateRule( rule, "allowArgTrailingWhitespaces", "number" );
				utils.validateRule( rule, "exceptions", "object" );
				if ( !rule.exceptions ) {
					return false;
				}
				if ( rule.exceptions.singleArg ) {
					utils.validateRule( rule.exceptions.singleArg, "for", "array", true );
					this.validateRule( rule.exceptions.singleArg );
				}
				if ( rule.exceptions.firstArg ) {
					utils.validateRule( rule.exceptions.firstArg, "for", "array", true );
					this.validateRule( rule.exceptions.firstArg );
				}
				if ( rule.exceptions.lastArg ) {
					utils.validateRule( rule.exceptions.lastArg, "for", "array", true );
					this.validateRule( rule.exceptions.lastArg );
				}
				if ( rule.ifNesting ) {
					this.validateRule( rule.ifNesting );
				}
			},
			/**
				* Run the sniffer according a given rule if a given node type matches the case
				* @access public
				* @param {Object} rule
				* @param {Object} node
				*/
			run: function( rule, node ) {
				var expOpeningBrace, expClosingBrace;

				if ( node.type === "CallExpression" && node[ "arguments" ] && node.callee ) {

					// Ignore for nesting if the corresponding ruleset is disabled
					if ( rule.ifNesting === false && node.isNesting ) {
						return;
					}
					// Override rules when nesting and corresponding rules defined
					if ( rule.ifNesting && node.isNesting ) {
						rule = rule.ifNesting;
					}

					if ( !node[ "arguments" ].length ) {
						return;
					}

					// find expression opening brace token
					expOpeningBrace = this.getExpOpeningBrace( node );

					// find expression closing brace token
					expClosingBrace = this.getExpClosingBrace( node );


					this.sniffFirst( rule, node, expOpeningBrace );

					// Checking preceding for each argument
					node[ "arguments" ].forEach(function( el, i ){
						var tokenIt;
						// foo{ arg,< >arg }
						// also: foo{ arg,< >(((arg))) }
						// not the first and not the last
						if ( node[ "arguments" ][ i - 1 ] && node[ "arguments" ][ i + 1 ] ) {

							// Ignore cases: run( pNode || { type: null } ), run( ( a ? 1 : 2 ) )
							if ( [ "Identifier", "Literal" ].indexOf( el.type ) === -1 ) {
								return;
							}

							// find argument token in the interator
							tokenIt = tokenIterator
								.findByLeftPos( el.range[ 0 ] )
								.findGroupOpener( expOpeningBrace.range[ 1 ] );

							mixin.sniffExcerpt( tokenIt.get( -1 ), tokenIt.get( 0 ),
								rule.allowArgPrecedingWhitespaces, "ArgPrecedingWhitespaces", "<" );
							// No need to check trailing for non-closing args. They are followed by comma and
							// that will be checked by comma sniff
						}
					});

					this.sniffLast( rule, node, expOpeningBrace, expClosingBrace );

				}
			},
			/**
			* Find opening brace for a given node [<(>(((1)))]
			* @access protected
			* @param {Object} node
			* @returns {Object} token
			*/
			getExpOpeningBrace: function( node ){
				return tokenIterator
						.findByLeftPos( node[ "arguments" ][ 0 ].range[ 0 ] )
						.findGroupOpener( node.range[ 0 ] - 1 )
						.get( 0 );
			},
			/**
			* Find closing brace for a given node [(((1))<)>]
			* @access protected
			* @param {Object} node
			* @returns {Object} token
			*/
			getExpClosingBrace: function( node ){
				return tokenIterator
						.findByLeftPos( node[ "arguments" ][ node[ "arguments" ].length - 1 ].range[ 0 ] )
						.findGroupCloser( node.range[ 1 ] + 1 )
						.get( 0 );
			},

			/**
			* Check the first argument
			* @access protected
			* @param {Object} rule
			* @param {Object} node
			* @param {TokenIterator} expOpeningBrace
			*/
			sniffFirst: function( rule, node, expOpeningBrace ) {
				var arg, expected, tokenIt;

				if ( !node[ "arguments" ].length ) {
					return;
				}

				arg = node[ "arguments" ][ 0 ];

				// Ignore cases: run( pNode || { type: null } ), run( ( a ? 1 : 2 ) )
				if ( [ "Identifier", "Literal" ].indexOf( arg.type ) === -1 ) {
					return;
				}

				// Checking preceding space for the first argument
				// {< >param, param }

				expected = rule.allowArgPrecedingWhitespaces;

				if ( rule.exceptions && rule.exceptions.firstArg &&
					mixin.matchesFor( rule.exceptions.firstArg, arg.type ) ) {
					expected = rule.exceptions.firstArg.allowArgPrecedingWhitespaces;
				}

				if ( rule.exceptions && rule.exceptions.singleArg && node[ "arguments" ].length === 1 &&
					mixin.matchesFor( rule.exceptions.singleArg, arg.type ) ) {
					// Idiomatic: foo("bar");
					if ( arg.type !== "Literal" || typeof arg.value === "string" ) {
						expected = rule.exceptions.singleArg.allowArgPrecedingWhitespaces;
					}
				}

				// find argument token in the interator
				tokenIt = tokenIterator
					.findByLeftPos( arg.range[ 0 ] )
					.findGroupOpener( expOpeningBrace.range[ 1 ] );


				// between prev. token right pos and arg left pos
				mixin.sniffExcerpt( tokenIt.get( -1 ), tokenIt.get( 0 ),
					expected, "ArgPrecedingWhitespaces", "<" );
			},
			/**
			* Check the last argument
			* @access protected
			* @param {Object} rule
			* @param {Object} node
			* @param {TokenIterator} expOpeningBrace
			* @param {TokenIterator} expClosingBrace
			*/
			sniffLast: function( rule, node, expOpeningBrace, expClosingBrace ) {
				var arg, expected, tokenIt;
				if ( !node[ "arguments" ].length ) {
					return;
				}

				arg = node[ "arguments" ][ node[ "arguments" ].length - 1 ];

				// Ignore cases: run( pNode || { type: null } ), run( ( a ? 1 : 2 ) )
				if ( [ "Identifier", "Literal" ].indexOf( arg.type ) === -1 ) {
					return;
				}

				// Checking preceding for the last argument
				// foo( arg,< >arg )
				// also: foo( arg,< >(((arg))) )
				// For the first another sniff take care
				if ( node[ "arguments" ].length > 1 ) {

					// find argument token in the interator
					tokenIt = tokenIterator
						.findByLeftPos( arg.range[ 0 ] )
						.findGroupOpener( expOpeningBrace.range[ 1 ] );

					mixin.sniffExcerpt( tokenIt.get( -1 ), tokenIt.get( 0 ),
						rule.allowArgPrecedingWhitespaces, "ArgPrecedingWhitespaces", "<" );
				}

				// Checking trailing for the last argument
				// { arg, arg< >}
				expected = rule.allowArgTrailingWhitespaces;

				if ( rule.exceptions && rule.exceptions.lastArg &&
					mixin.matchesFor( rule.exceptions.lastArg, arg.type ) ) {
					expected = rule.exceptions.lastArg.allowArgTrailingWhitespaces;
				}

				if ( rule.exceptions && rule.exceptions.singleArg && node[ "arguments" ].length === 1 &&
						mixin.matchesFor( rule.exceptions.singleArg, arg.type ) ) {
					// Idiomatic: foo("bar");
					if ( arg.type !== "Literal" || typeof arg.value === "string" ) {
						expected = rule.exceptions.singleArg.allowArgTrailingWhitespaces;
					}
				}
				// find argument token in the interator
				tokenIt = tokenIterator
					.findByRightPos( arg.range[ 1 ] )
					.findGroupCloser( expClosingBrace.range[ 0 ] );

				// find next token: foo( arg, arg<>)
				// also: foo( arg, (((arg)))< >)
				mixin.sniffExcerpt( tokenIt.get( 0 ), tokenIt.get( 1 ), expected, "ArgTrailingWhitespaces", ">" );
			}

		};
	};
	return Sniff;
});