Skip to content

Commit

Permalink
Merge e76c71e into 22b5ca5
Browse files Browse the repository at this point in the history
  • Loading branch information
mariano-aguero committed Jan 3, 2019
2 parents 22b5ca5 + e76c71e commit b066908
Show file tree
Hide file tree
Showing 70 changed files with 1,619 additions and 125 deletions.
12 changes: 12 additions & 0 deletions conf/rulesets/solhint-all.js
@@ -0,0 +1,12 @@
const { loadRules } = require('../../lib/load-rules')

const rulesConstants = loadRules()
const enabledRules = {}

rulesConstants.forEach(rule => {
if (!rule.meta.deprecated) {
enabledRules[rule.ruleId] = rule.meta.defaultSetup
}
})

module.exports = { rules: enabledRules }
12 changes: 12 additions & 0 deletions conf/rulesets/solhint-default.js
@@ -0,0 +1,12 @@
const { loadRules } = require('../../lib/load-rules')

const rulesConstants = loadRules()
const enabledRules = {}

rulesConstants.forEach(rule => {
if (!rule.meta.deprecated && rule.meta.isDefault) {
enabledRules[rule.ruleId] = rule.meta.defaultSetup
}
})

module.exports = { rules: enabledRules }
12 changes: 12 additions & 0 deletions conf/rulesets/solhint-recommended.js
@@ -0,0 +1,12 @@
const { loadRules } = require('../../lib/load-rules')

const rulesConstants = loadRules()
const enabledRules = {}

rulesConstants.forEach(rule => {
if (!rule.meta.deprecated && rule.meta.recommended) {
enabledRules[rule.ruleId] = rule.meta.defaultSetup
}
})

module.exports = { rules: enabledRules }
15 changes: 15 additions & 0 deletions lib/common/ajv.js
@@ -0,0 +1,15 @@
const Ajv = require('ajv')
const metaSchema = require('ajv/lib/refs/json-schema-draft-04.json')

const ajv = new Ajv({
meta: false,
validateSchema: false,
missingRefs: 'ignore',
verbose: true,
schemaId: 'auto'
})

ajv.addMetaSchema(metaSchema)
ajv._opts.defaultMeta = metaSchema.id

module.exports = ajv
8 changes: 4 additions & 4 deletions lib/common/errors.js
@@ -1,10 +1,10 @@
class FileNotExistsError extends Error {
constructor(data) {
const { message } = data
class ConfigMissingError extends Error {
constructor(configName) {
const message = `Failed to load config "${configName}" to extend from.`
super(message)
}
}

module.exports = {
FileNotExistsError
ConfigMissingError
}
45 changes: 30 additions & 15 deletions lib/common/utils.js
@@ -1,18 +1,33 @@
module.exports = {
getLocFromIndex(text, index) {
let line = 1
let column = 0
let i = 0
while (i < index) {
if (text[i] === '\n') {
line++
column = 0
} else {
column++
}
i++
}
const fs = require('fs')
const path = require('path')

return { line, column }
const getLocFromIndex = (text, index) => {
let line = 1
let column = 0
let i = 0
while (i < index) {
if (text[i] === '\n') {
line++
column = 0
} else {
column++
}
i++
}

return { line, column }
}

const walkSync = (dir, filelist = []) => {
fs.readdirSync(dir).forEach(file => {
filelist = fs.statSync(path.join(dir, file)).isDirectory()
? walkSync(path.join(dir, file), filelist)
: filelist.concat(path.join(dir, file))
})
return filelist
}

module.exports = {
getLocFromIndex,
walkSync
}
89 changes: 89 additions & 0 deletions lib/config/config-file.js
@@ -0,0 +1,89 @@
const path = require('path')
const fs = require('fs')
const _ = require('lodash')
const cosmiconfig = require('cosmiconfig')
const { ConfigMissingError } = require('../common/errors')
const packageJson = require('../../package.json')

const getSolhintCoreConfigPath = name => {
if (name === 'solhint:recommended') {
return path.resolve(__dirname, '../../conf/rulesets/solhint-recommended.js')
}

if (name === 'solhint:all') {
return path.resolve(__dirname, '../../conf/rulesets/solhint-all.js')
}

if (name === 'solhint:default') {
return path.resolve(__dirname, '../../conf/rulesets/solhint-default.js')
}

throw new ConfigMissingError(name)
}

const createEmptyConfig = () => ({
excludedFiles: {},
extends: {},
globals: {},
env: {},
rules: {},
parserOptions: {}
})

const loadConfig = () => {
// Use cosmiconfig to get the config from different sources
const appDirectory = fs.realpathSync(process.cwd())
const moduleName = packageJson.name
const cosmiconfigOptions = {
searchPlaces: [
'package.json',
`.${moduleName}.json`,
`.${moduleName}rc`,
`.${moduleName}rc.json`,
`.${moduleName}rc.yaml`,
`.${moduleName}rc.yml`,
`.${moduleName}rc.js`,
`${moduleName}.config.js`
]
}

const explorer = cosmiconfig(moduleName, cosmiconfigOptions)
const searchedFor = explorer.searchSync(appDirectory)
return searchedFor.config || createEmptyConfig()
}

const configGetter = path => {
let extensionPath = null

if (path.startsWith('solhint:')) {
extensionPath = getSolhintCoreConfigPath(path)
} else {
// Load packages with rules
extensionPath = `solhint-config-${path}`
}

const extensionConfig = require(extensionPath)

return extensionConfig
}

const applyExtends = (config, getter = configGetter) => {
if (!Array.isArray(config.extends)) {
config.extends = [config.extends]
}

return config.extends.reduceRight((previousValue, parentPath) => {
try {
const extensionConfig = getter(parentPath)
return _.merge({}, extensionConfig, previousValue)
} catch (e) {
throw new ConfigMissingError(parentPath)
}
}, config)
}

module.exports = {
applyExtends,
configGetter,
loadConfig
}
16 changes: 16 additions & 0 deletions lib/config/config-schema.js
@@ -0,0 +1,16 @@
const baseConfigProperties = {
rules: { type: 'object' },
excludedFiles: { type: 'array' },
extends: { type: 'array' },
globals: { type: 'object' },
env: { type: 'object' },
parserOptions: { type: 'object' }
}

const configSchema = {
type: 'object',
properties: baseConfigProperties,
additionalProperties: false
}

module.exports = configSchema
119 changes: 119 additions & 0 deletions lib/config/config-validator.js
@@ -0,0 +1,119 @@
const _ = require('lodash')
const ajv = require('../common/ajv')
const configSchema = require('./config-schema')
const { loadRule } = require('../load-rules')

let validateSchema

const validSeverityMap = ['error', 'warn']

const invalidSeverityMap = ['off']

const defaultSchemaValueForRules = Object.freeze({
oneOf: [{ type: 'string', enum: [...validSeverityMap, ...invalidSeverityMap] }, { const: false }]
})

const validateRules = rulesConfig => {
if (!rulesConfig) {
return
}

const errorsSchema = []
const errorsRules = []
const rulesConfigKeys = Object.keys(rulesConfig)

for (const ruleId of rulesConfigKeys) {
const ruleInstance = loadRule(ruleId)
const ruleValue = rulesConfig[ruleId]

if (ruleInstance === undefined) {
errorsRules.push(ruleId)
continue
}

// Inject default schema
if (ruleInstance.meta.schema.length) {
let i
for (i = 0; i < ruleInstance.meta.schema.length; i++) {
const schema = ruleInstance.meta.schema[i]
if (schema.type === 'array') {
ruleInstance.meta.schema[i] = _.cloneDeep(defaultSchemaValueForRules)
ruleInstance.meta.schema[i].oneOf.push(schema)
ruleInstance.meta.schema[i].oneOf[2].items.unshift(defaultSchemaValueForRules)
}
}
} else {
ruleInstance.meta.schema.push(defaultSchemaValueForRules)
}

// Validate rule schema
validateSchema = ajv.compile(ruleInstance.meta.schema[0])

if (!validateSchema(ruleValue)) {
errorsSchema.push({ ruleId, defaultSetup: ruleInstance.meta.defaultSetup })
}
}

if (errorsRules.length) {
throw new Error(errorsRules.map(error => `\tRule ${error} doesn't exist.\n`).join(''))
}

if (errorsSchema.length) {
throw new Error(
errorsSchema
.map(
(ruleId, defaultSetup) =>
`\tRule ${ruleId} have an invalid schema.\n\tThe default setup is: ${JSON.stringify(
defaultSetup
)}`
)
.join('')
)
}
}

const formatErrors = errors =>
errors
.map(error => {
if (error.keyword === 'additionalProperties') {
const formattedPropertyPath = error.dataPath.length
? `${error.dataPath.slice(1)}.${error.params.additionalProperty}`
: error.params.additionalProperty

return `Unexpected top-level property "${formattedPropertyPath}"`
}
if (error.keyword === 'type') {
const formattedField = error.dataPath.slice(1)
const formattedExpectedType = Array.isArray(error.schema)
? error.schema.join('/')
: error.schema
const formattedValue = JSON.stringify(error.data)

return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`
}

const field = error.dataPath[0] === '.' ? error.dataPath.slice(1) : error.dataPath

return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`
})
.map(message => `\t- ${message}.\n`)
.join('')

const validateConfigSchema = config => {
validateSchema = validateSchema || ajv.compile(configSchema)

if (!validateSchema(config)) {
throw new Error(`Solhint configuration is invalid:\n${formatErrors(validateSchema.errors)}`)
}
}

const validate = config => {
validateConfigSchema(config)
validateRules(config.rules)
}

module.exports = {
validate,
validSeverityMap,
defaultSchemaValueForRules
}
6 changes: 0 additions & 6 deletions lib/constants.js

This file was deleted.

0 comments on commit b066908

Please sign in to comment.