Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add rulesets #86

Merged
merged 9 commits into from
Jan 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module.exports = {
rules: {
'consistent-return': 'off',
'import/no-dynamic-require': 'off',
'import/no-extraneous-dependencies': ['error', { optionalDependencies: true }],
'global-require': 'off',
'no-bitwise': 'off',
'no-console': 'off',
Expand Down
4 changes: 4 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ language: node_js

node_js:
- "stable"

script:
- npm run lint
- npm test
12 changes: 12 additions & 0 deletions conf/rulesets/solhint-all.js
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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.

Loading