ahmadnassri/echint

View on GitHub
lib/index.js

Summary

Maintainability
B
4 hrs
Test Coverage
'use strict'

const debuglog = require('util').debuglog
const dotenv = require('dotenv')
const fs = require('fs')
const glob = require('glob')
const Lintspaces = require('lintspaces')
const minimatch = require('minimatch')
const path = require('path')
const pkg = require('pkg-config')

const debug = debuglog('echint')
const config = pkg('echint', { root: false })

const DEFAULT_PATTERN = '**/*'
const DEFAULT_IGNORE_PATTERNS = [
  'coverage/**',
  'node_modules/**',
  'bower_components/**',
  '**/*.{jpg,png,gif,ico}'
]

dotenv.config({ silent: true })
dotenv.config({ silent: true, path: '.env.' + process.env.NODE_ENV })

module.exports = function echint (files, options, cb) {
  // organize arguments
  if (typeof arguments[1] === 'function') {
    cb = arguments[1]
    options = {}
  }

  // organize arguments
  if (!Array.isArray(arguments[0]) && typeof arguments[0] === 'object') {
    options = arguments[0]
    files = undefined
  }

  // default values
  const defaults = {
    config: process.env.ECHINT_CONFIG || '.editorconfig',
    ignore: process.env.ECHINT_IGNORE ? [process.env.ECHINT_IGNORE] : DEFAULT_IGNORE_PATTERNS,
    pattern: process.env.ECHINT_PATTERN || DEFAULT_PATTERN,
    readPackage: process.env.ECHINT_READ_PACKAGE ? (process.env.ECHINT_READ_PACKAGE === 'true') : (options ? options.readPackage : true)
  }

  // initialize options
  const opts = Object.assign({}, defaults)

  // overwrite from package.json?
  if (opts.readPackage && config) {
    debug('package.json config found')

    Object.assign(opts, config)
  }

  // overwrite from local options again
  Object.assign(opts, options)

  if (opts.extends) {
    debug('extends found')

    const extendsDir = path.join(process.cwd(), 'node_modules', opts.extends.replace(/^(echint-config-)?/, 'echint-config-'))

    const extendsMain = pkg('main', { cwd: extendsDir, root: false })

    opts.config = path.join(extendsDir, extendsMain || '.editorconfig')
  }

  debug('starting with options: config=%s ignore=%j', opts.config, opts.ignore)

  // setup validator
  const lintspaces = new Lintspaces({
    editorconfig: opts.config,
    ignores: [
      'js-comments',
      'c-comments',
      'java-comments',
      'as-comments',
      'xml-comments',
      'html-comments',
      'python-comments',
      'ruby-comments',
      'applescript-comments'
    ]
  })

  // files to check
  if (!files || !files.length) {
    debug('no file list given, using match pattern instead: "%s"', opts.pattern)

    files = glob.sync(opts.pattern, {
      ignore: opts.ignore.concat(DEFAULT_IGNORE_PATTERNS),
      nodir: true
    })

    // since glob already ignored files for us, do NOT check later
    opts.ignore = []
  }

  // Run validation
  files.forEach(file => {
    if (opts.ignore.some(pattern => minimatch(file, pattern))) {
      debug('[%s] is ignored"', file)
      return
    }

    // confirm file exists
    try {
      if (!fs.statSync(file).isFile()) throw Error('is not a file')
    } catch (e) {
      return debug('✖ [%s] is not a file ', file)
    }

    // where the magic happens
    lintspaces.validate(file)

    // debugging
    if (Object.keys(lintspaces.getInvalidLines(file)).length) {
      debug('✖ [%s] is invalid', file)
    } else {
      debug('✓ [%s] is valid', file)
    }
  })

  // get full list of errors
  const errors = lintspaces.getInvalidFiles()

  // determine if test was a success
  const valid = Object.keys(errors).length === 0

  if (typeof cb === 'function') {
    cb(valid ? null : errors, valid)
  }

  return valid
}