/**
* @license Apache-2.0
*
* Copyright (c) 2021 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 basename = require( 'path' ).basename;
var dirname = require( 'path' ).dirname;
var join = require( 'path' ).join;
var statSync = require( 'fs' ).statSync; // eslint-disable-line no-sync
var endsWith = require( '@stdlib/string/ends-with' );
var isString = require( '@stdlib/assert/is-string' ).isPrimitive;
var trim = require( '@stdlib/string/trim' );
var readDir = require( '@stdlib/fs/read-dir' ).sync;
var contains = require( '@stdlib/assert/contains' );


// VARIABLES //

var rule;
var ORDER_COMMENT = 'When adding modules to the namespace, ensure that they are added in alphabetical order according to module name.';
var RE_CUSTOM_EXCLUDES = /The following modules are intentionally not exported: ?([^\n]+)\n/;
var EXCLUDE_LIST = [
	'node_modules',
	'benchmark',
	'data',
	'docs',
	'etc',
	'examples',
	'lib',
	'include',
	'scripts',
	'src',
	'test'
];


// FUNCTIONS //

/**
* Checks whether a directory content is a file.
*
* @private
* @param {string} content - directory content path
* @returns {boolean} boolean indicating whether the directory content is a file
*/
function isFile( content ) {
	var stat = statSync( content );
	return stat.isFile();
}

/**
* Rule for validating that a namespace `index.js` exports all packages in the respective namespace directory.
*
* @param {Object} context - ESLint context
* @returns {Object} validators
*/
function main( context ) {
	var requires;
	var packages;
	var filename;
	var excludes;
	var source;
	var match;
	var arr;
	var dir;
	var i;

	source = context.getSourceCode();
	if ( !contains( source.text, ORDER_COMMENT ) ) {
		return {};
	}
	match = RE_CUSTOM_EXCLUDES.exec( source.text );
	if ( match ) {
		excludes = EXCLUDE_LIST.slice();
		arr = match[ 1 ].split( ',' );
		for ( i = 0; i < arr.length; i++ ) {
			excludes.push( trim( arr[ i ] ) );
		}
	} else {
		excludes = EXCLUDE_LIST;
	}
	filename = context.getFilename();
	if ( !filename || !endsWith( filename, 'index.js' ) ) {
		return {};
	}
	dir = join( dirname( filename ), '..' );
	packages = packageDirectories( readDir( dir ) );
	requires = [];

	/**
	* Checks for existence of require paths in the current program and registers them.
	*
	* @private
	* @param {ASTNode} node - node to examine
	*/
	function validate( node ) {
		var requirePath;
		var pkg;
		if ( node.callee.name === 'require' ) {
			requirePath = node.arguments[ 0 ].value;
			if ( isString( requirePath ) ) {
				pkg = basename( requirePath );
				if ( !contains( requires, pkg ) ) {
					requires.push( pkg );
				}
			}
		}
	}

	/**
	* Callback invoked upon program exit.
	*
	* @private
	* @param {Object} node - AST node
	*/
	function finish( node ) {
		var errs;
		var i;

		errs = [];
		for ( i = 0; i < packages.length; i++ ) {
			if ( !contains( requires, packages[ i ] ) ) {
				errs.push( packages[ i ] );
			}
		}
		if ( errs.length ) {
			report( errs, node );
		}
		requires.length = 0; // reset the require cache
	}

	/**
	* Reports the error message.
	*
	* @private
	* @param {StringArray} errs - array of packages not exported from `index.js`
	* @param {ASTNode} node - AST node
	*/
	function report( errs, node ) {
		var pkg;
		var msg;
		var i;
		for ( i = 0; i < errs.length; i++ ) {
			pkg = errs[ i ];
			msg = '`' + pkg + '` should be exported from namespace `index.js`';
			context.report({
				'node': node,
				'message': msg
			});
		}
	}

	/**
	* Filters out any non-package directories from a list of directory contents.
	*
	* @private
	* @param {Array} contents - directory contents
	* @returns {Array} namespace package directories
	*/
	function packageDirectories( contents ) {
		var content;
		var out;
		var i;

		out = [];
		for ( i = 0; i < contents.length; i++ ) {
			content = contents[ i ];
			if (
				!isFile( join( dir, content ) ) &&
				!contains( excludes, content )
			) {
				out.push( content );
			}
		}
		return out;
	}

	return {
		'CallExpression': validate,
		'Program:exit': finish
	};
}


// MAIN //

rule = {
	'meta': {
		'docs': {
			'description': 'enforce that a namespace `index.js` exports all packages in the respective namespace directory'
		},
		'schema': []
	},
	'create': main
};


// EXPORTS //

module.exports = rule;

@@ -1569,20 +1569,25 @@
 nt is a 
-file
+directory
 .%0A*%0A* @p
@@ -1719,20 +1719,25 @@
 nt is a 
-file
+directory
 %0A*/%0Afunc
@@ -1743,20 +1743,25 @@
 ction is
-File
+Directory
 ( conten
@@ -1814,20 +1814,25 @@
  stat.is
-File
+Directory
 ();%0A%7D%0A%0A/
@@ -4403,15 +4403,19 @@
 %09%09%09%09
-!
 is
-File
+Directory
 ( jo

Check for directory instead of file