text: var assert = require('assert');
  var fs = require('fs');
  var path = require('path');
  var glob = require('glob');
  var isAbsolutePath = require('path-is-absolute');
  var minimatch = require('minimatch');
  var resolve = require('resolve');
  var ConfigFile = require('./config-file');
  var Errors = require('./errors');
  var File = require('./pug-file');
  var utils = require('./utils');
  
  var Linter = function () {
    this._basePath = '.';
    this._excludedFileMasks = ['node_modules/**'];
    this._excludedFileMatchers = [];
    this._fileExtensions = ['.pug', '.jade'];
  };
  
  Linter.prototype = {
    checkDirectory: function (directoryPath) {
      var errors = [];
  
      if (this._isFileExcluded(directoryPath)) {
        return [];
      }
  
      fs.readdirSync(directoryPath).forEach(function (file) {
        var filePath = directoryPath + '/' + file;
        var stat = fs.statSync(filePath);
  
        if (stat.isDirectory()) {
          errors = errors.concat(this.checkDirectory(filePath));
        } else if (this._fileExtensions.indexOf(path.extname(filePath)) !== -1) {
          errors = errors.concat(this.checkFile(filePath));
        }
      }, this);
  
      return errors;
    },
  
    checkFile: function (filePath) {
      /* istanbul ignore if */
      if (this._isFileExcluded(filePath)) {
        return [];
      }
  
      return this.checkString(fs.readFileSync(filePath, 'utf8'), filePath);
    },
  
    checkPath: function (filePath) {
      filePath = filePath.replace(/\/$/, '');
  
      if (!fs.existsSync(filePath)) {
        throw new Error('Path ' + filePath + ' was not found');
      }
  
      var stat = fs.statSync(filePath);
  
      if (stat.isDirectory()) {
        return this.checkDirectory(filePath);
      }
  
      return this.checkFile(filePath);
    },
  
    checkString: function (source, filename) {
      filename = filename || 'input';
  
      var file = this._createFile(filename, source);
  
      return this._checkFile(file);
    },
  
    configure: function (options) {
      this._configuredRules = [];
      this._ruleMasks = [path.join(__dirname, 'rules/*.js')];
  
      if (options) {
        this._extendConfiguration(options);
  
        if (options.hasOwnProperty('preset')) {
          throw new Error('Presets have been deprecated. See: https://github.com/pugjs/pug-lint#preset-deprecated');
        }
  
        if (options.hasOwnProperty('excludeFiles')) {
          assert(Array.isArray(options.excludeFiles), '"excludeFiles" option requires array value');
  
          this._excludedFileMasks = options.excludeFiles;
        }
  
        if (options.hasOwnProperty('additionalRules')) {
          assert(Array.isArray(options.additionalRules), '"additionalRules" option requires array value');
  
          this._ruleMasks = this._ruleMasks.concat(options.additionalRules);
        }
  
        this._loadExcludedFiles();
  
        this._ruleMasks.forEach(function (mask) {
          glob.sync(mask).forEach(function (file) {
            var Rule = require(path.resolve(file));
            var rule = new Rule();
            var name = rule.name;
  
            if (options.hasOwnProperty(name) && options[name] !== null) {
              if (rule.contradictions) {
                rule.contradictions.forEach(function (contradiction) {
                  if (options.hasOwnProperty(contradiction) && options[contradiction] !== null) {
                    options[contradiction] = null;
                  }
                });
              }
  
              rule.configure(options[name]);
  
              this._configuredRules.push(rule);
            }
          }, this);
        }, this);
      }
    },
  
    getConfiguredRules: function () {
      return this._configuredRules;
    },
  
    _checkFile: function (file) {
      var errors = new Errors(file);
      var firstToken;
  
      file.getParseErrors().forEach(function (parseError) {
        errors.addParseError(parseError);
      });
  
      firstToken = file.getFirstToken();
  
      if (!firstToken || firstToken && firstToken.type === 'eos') {
        return errors.getErrors();
      }
  
      this.getConfiguredRules().forEach(function (rule) {
        errors.setCurrentRule(rule.name);
  
        rule.lint(file, errors);
      });
  
      return errors.getErrors();
    },
  
    _createFile: function (filename, source) {
      return new File(filename, source);
    },
  
    _extendConfiguration: function (options) {
      if (options.hasOwnProperty('extends')) {
        var configPath = this._resolveExtendsFile(options.extends);
        var configOptions = ConfigFile.loadFromFile(configPath);
  
        Object.keys(configOptions).forEach(function (key) {
          if (!options.hasOwnProperty(key)) {
            options[key] = configOptions[key];
          }
        });
      }
  
      return options;
    },
  
    _isFileExcluded: function (filePath) {
      filePath = path.resolve(filePath);
  
      return this._excludedFileMatchers.some(function (matcher) {
        return matcher.match(filePath);
      });
    },
  
    _loadExcludedFiles: function () {
      this._excludedFileMatchers = this._excludedFileMasks.map(function (fileMask) {
        return new minimatch.Minimatch(path.resolve(this._basePath, fileMask), {
          dot: true
        });
      }, this);
    },
  
    _resolveExtendsFile: function (filePath) {
      if (isAbsolutePath(filePath) || !/\w|@/.test(filePath.charAt(0))) {
        filePath = path.resolve(this._basePath, filePath);
  
        if (!fs.existsSync(filePath)) {
          throw new Error('Cannot find configuration file "' + filePath + '" to extend');
        }
      } else {
        var packageName = utils.normalizePackageName(filePath, 'pug-lint-config');
  
        try {
          filePath = resolve.sync(packageName);
        } catch (err) {
          throw new Error('Cannot find module "' + packageName + '" to extend');
        }
      }
  
      return filePath;
    }
  };
  
  module.exports = Linter;
  