text: /**
  * @license Apache-2.0
  *
  * Copyright (c) 2018 The Stdlib Authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *    http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
  
  'use strict';
  
  // MODULES //
  
  var vm = require( 'vm' );
  var proc = require( 'process' );
  var join = require( 'path' ).join;
  var format = require( 'util' ).format;
  var dirname = require( 'path' ).dirname;
  var logger = require( 'debug' );
  var Buffer = require( '@stdlib/buffer/ctor' );
  var isNull = require( '@stdlib/assert/is-null' );
  var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
  var isNumber = require( '@stdlib/assert/is-number' );
  var contains = require( '@stdlib/assert/contains' );
  var replace = require( '@stdlib/string/replace' );
  var startsWith = require( '@stdlib/string/starts-with' );
  var objectKeys = require( '@stdlib/utils/keys' );
  var compareValues = require( '@stdlib/_tools/doctest/compare-values' );
  var createAnnotationValue = require( '@stdlib/_tools/doctest/create-annotation-value' );
  var SCOPE_DEFAULTS = require( './scope_defaults.json' );
  var windowShim = require( './window.js' );
  
  
  // VARIABLES //
  
  var debug = logger( 'doctest' );
  var RE_JSDOC = /\/\*\*[\s\S]+?\*\//g;
  var RE_ESLINT_INLINE = / ?\/\/ eslint-disable-(?:next-)?line[^\n]*\n/g;
  var RE_NEWLINE = /\r?\n/g;
  var RE_ANNOTATION = /(?:\n|^)(?:var|let|const)? ?([a-zA-Z0-9._]+) ?=?[^;]*;\n\/\/ ?(returns|([A-Za-z][A-Za-z_0-9]*)? ?=>|throws) {0,1}([\s\S]*?)(\n|$)/g;
  var RE_CONSOLE = /console\.(?:dir|error|log)/;
  var NODE_SHEBANG = /#!\/usr\/bin\/env node/;
  var rule;
  
  
  // FUNCTIONS //
  
  /**
  * Shim for `console.log` function that returns the logged data instead of printing it to `stdout`.
  *
  * @private
  * @param {*} data - data to be logged
  * @param {...*} args - substitution values
  * @returns {*} return value
  */
  function log( data ) {
  	if ( isString( data ) ) {
  		return format.apply( null, arguments );
  	}
  	return data;
  }
  
  /**
  * Counts the number of lines in the given string.
  *
  * @private
  * @param {string} str - input string
  * @returns {number} number of lines
  */
  function countLines( str ) {
  	return ( str.match( RE_NEWLINE ) || '' ).length;
  }
  
  /**
  * Searches for variable in scope matching the expected value.
  *
  * @private
  * @param {Object} scope - VM scope
  * @param {any} expected - expected value to search for
  * @returns {string} variable name or `?` if no match found
  */
  function findName( scope, expected ) {
  	var keys = objectKeys( scope );
  	var key;
  	var i;
  	for ( i = 0; i < keys.length; i++ ) {
  		key = keys[ i ];
  		if (
  			!contains( SCOPE_DEFAULTS, key ) &&
  			!compareValues( scope[ key ], expected )
  		) {
  			return key;
  		}
  	}
  	return '?';
  }
  
  
  // MAIN //
  
  /**
  * Rule for validating that return annotations match the actual output.
  *
  * @param {Object} context - ESLint context
  * @returns {Object} validators
  */
  function main( context ) {
  	var sourceCode;
  	var filename;
  	var source;
  	var scope;
  	var dir;
  
  	source = context.getSourceCode();
  	if ( NODE_SHEBANG.test( source.text ) ) {
  		// Do not lint executable Node.js script files:
  		return {};
  	}
  	sourceCode = replace( source.text, RE_JSDOC, '' );
  	sourceCode = replace( sourceCode, RE_ESLINT_INLINE, '\n' );
  	filename = context.getFilename();
  	dir = dirname( filename );
  	/**
  	* Requires modules after converting relative to absolute paths.
  	*
  	* @private
  	* @param {string} path - module path
  	* @returns {*} required module
  	*/
  	function customRequire( path ) {
  		if ( startsWith( path, './' ) ) {
  			return require( join( dir, path ) ); // eslint-disable-line stdlib/no-dynamic-require
  		}
  		return require( path ); // eslint-disable-line stdlib/no-dynamic-require
  	}
  	scope = {
  		'require': customRequire,
  		'exports': exports,
  		'module': module,
  		'process': proc,
  		'setTimeout': setTimeout,
  		'clearTimeout': clearTimeout,
  		'setInterval': setInterval,
  		'clearInterval': clearInterval,
  		'window': windowShim,
  		'Buffer': Buffer,
  		'__dirname': dir,
  		'__filename': filename,
  		'console': {
  			'dir': log,
  			'error': log,
  			'log': log,
  			'warn': log
  		}
  	};
  	vm.createContext( scope );
  
  	try {
  		// Run entire code in case it relies on hoisting of function declarations:
  		vm.runInContext( sourceCode, scope );
  	} catch ( err ) {
  		debug( 'Encountered an error: '+err.message );
  	}
  
  	/**
  	* Reports the error message.
  	*
  	* @private
  	* @param {Object} loc - error location info
  	* @param {string} msg - error message
  	* @param {string} replacement - fixed return annotation
  	*/
  	function report( loc, msg, replacement ) {
  		context.report({
  			'node': null,
  			'message': msg,
  			'loc': loc,
  			'fix': fix
  		});
  
  		/**
  		* Applies a fix to the offending code.
  		*
  		* @private
  		* @param {Object} fixer - object to apply a fix
  		* @returns {Object} fixing object
  		*/
  		function fix( fixer ) {
  			return fixer.replaceTextRange( loc.range, replacement );
  		}
  	}
  
  	/**
  	* Checks whether return annotations in the current program match the actual output.
  	*
  	* @private
  	*/
  	function validate() {
  		var replacement;
  		var expected;
  		var actual;
  		var code;
  		var last;
  		var line;
  		var opts;
  		var type;
  		var arr;
  		var idx;
  		var loc;
  		var msg;
  		var out;
  
  		last = 0;
  		opts = {
  			'includeDecimal': false
  		};
  		try {
  			arr = RE_ANNOTATION.exec( sourceCode );
  			while ( !isNull( arr ) ) {
  				// Run intermediary code:
  				code = sourceCode.substring( last, arr.index );
  				last = arr.index + arr[ 0 ].length;
  				if ( code ) {
  					vm.runInContext( code, scope );
  				}
  
  				// Calculate line of current code chunk:
  				idx = source.text.indexOf( arr[ 0 ] );
  				line = countLines( source.text.substring( 0, idx ) ) + 1;
  				loc = {
  					'start': {
  						'line': line,
  						'column': 0
  					},
  					'end': {
  						'line': line,
  						'column': arr[ 0 ].length
  					}
  				};
  
  				// Run code preceding return annotation:
  				try {
  					out = vm.runInContext( arr[ 0 ], scope );
  					if ( RE_CONSOLE.test( arr[ 1 ] ) ) {
  						actual = out;
  					} else {
  						actual = scope[ arr[ 1 ] ];
  					}
  					if ( arr[ 3 ] ) {
  						actual = vm.runInContext( arr[ 3 ], scope );
  					}
  					expected = arr[ 4 ];
  					msg = compareValues( actual, expected );
  					if ( msg ) {
  						opts.includeDecimal = isNumber( actual ) && contains( expected, '.' );
  						replacement = createAnnotationValue( actual, opts );
  						loc.range = [
  							// Position after `// returns`:
  							idx + arr[ 0 ].length -
  							arr[ 4 ].length -
  							arr[ 5 ].length,
  
  							// Position at the end of the return annotation:
  							idx + arr[ 0 ].length - arr[ 5 ].length
  						];
  						report( loc, msg, replacement );
  					}
  				} catch ( err ) {
  					type = '<'+err.name+'>';
  					if ( arr[ 2 ] !== 'throws' ) {
  						if ( arr[ 3 ] ) {
  							msg = 'Encountered an error: `'+err.message+'`.';
  						} else {
  							msg = 'Encountered an error while running code: `'+err.message+'`. ';
  							msg += 'Did you mean to include a `// throws '+type+'` annotation instead of `// '+arr[ 2 ]+' '+arr[ 4 ]+'`?';
  						}
  					}
  					else if ( arr[ 4 ] !== type ) {
  						msg = 'Code should have a `// throws '+type+'` annotation, but received: `// '+arr[ 2 ]+' '+arr[ 4 ]+'`';
  					}
  					if ( msg ) {
  						replacement = ( arr[ 3 ] ) ? findName( scope, arr[ 4 ] ) + ' => ' + arr[ 4 ] : 'throws '+type;
  						loc.range = [
  							// Position after `//`:
  							idx + arr[ 0 ].length - arr[ 2 ].length - 1 -
  							arr[ 4 ].length - arr[ 5 ].length,
  
  							// Position at the end of the annotation:
  							idx + arr[ 0 ].length - arr[ 5 ].length
  						];
  						report( loc, msg, replacement );
  					}
  				}
  				arr = RE_ANNOTATION.exec( sourceCode );
  			}
  		} catch ( err ) {
  			loc = {
  				'start': {
  					'line': 0,
  					'column': 0
  				},
  				'end': {
  					'line': countLines( source.text ),
  					'column': 0
  				}
  			};
  			report( loc, 'Encountered an error while running code: '+err.message );
  		} finally {
  			RE_ANNOTATION.lastIndex = 0;
  		}
  	}
  
  	return {
  		'Program': validate
  	};
  }
  
  
  // MAIN //
  
  rule = {
  	'meta': {
  		'docs': {
  			'description': 'ensure return annotations match the actual output'
  		},
  		'fixable': 'code',
  		'schema': []
  	},
  	'create': main
  };
  
  
  // EXPORTS //
  
  module.exports = rule;
  