-
Notifications
You must be signed in to change notification settings - Fork 165
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #86 from protofire/feature/add-rulesets
Add rulesets
- Loading branch information
Showing
73 changed files
with
1,628 additions
and
134 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,7 @@ language: node_js | |
|
||
node_js: | ||
- "stable" | ||
|
||
script: | ||
- npm run lint | ||
- npm test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.