UNPKG

5.46 kBJavaScriptView Raw
1#!/usr/bin/env node
2
3const program = require('commander')
4const _ = require('lodash')
5const fs = require('fs')
6const process = require('process')
7
8const linter = require('./lib/index')
9const { loadConfig } = require('./lib/config/config-file')
10const { validate } = require('./lib/config/config-validator')
11const applyFixes = require('./lib/apply-fixes')
12const ruleFixer = require('./lib/rule-fixer')
13const packageJson = require('./package.json')
14
15function init() {
16 const version = packageJson.version
17 program.version(version)
18
19 program
20 .usage('[options] <file> [...other_files]')
21 .option('-f, --formatter [name]', 'report formatter name (stylish, table, tap, unix)')
22 .option('-w, --max-warnings [maxWarningsNumber]', 'number of allowed warnings')
23 .option('-c, --config [file_name]', 'file to use as your .solhint.json')
24 .option('-q, --quiet', 'report errors only - default: false')
25 .option('--ignore-path [file_name]', 'file to use as your .solhintignore')
26 .option('--fix', 'automatically fix problems')
27 .option('--init', 'create configuration file for solhint')
28 .description('Linter for Solidity programming language')
29 .action(execMainAction)
30
31 program
32 .command('stdin')
33 .description('linting of source code data provided to STDIN')
34 .option('--filename [file_name]', 'name of file received using STDIN')
35 .action(processStdin)
36
37 program
38 .command('init-config', null, { noHelp: true })
39 .description('create configuration file for solhint')
40 .action(writeSampleConfigFile)
41
42 program.parse(process.argv)
43
44 if (program.init) {
45 writeSampleConfigFile()
46 }
47
48 if (program.args.length < 1) {
49 program.help()
50 }
51}
52
53function execMainAction() {
54 if (program.init) {
55 writeSampleConfigFile()
56 }
57
58 let formatterFn
59
60 try {
61 // to check if is a valid formatter before execute linter
62 formatterFn = getFormatter(program.formatter)
63 } catch (ex) {
64 console.error(ex.message)
65 process.exit(1)
66 }
67
68 const reportLists = program.args.filter(_.isString).map(processPath)
69 const reports = _.flatten(reportLists)
70 const warningsCount = reports.reduce((acc, i) => acc + i.warningCount, 0)
71 const warningsNumberExceeded = program.maxWarnings >= 0 && warningsCount > program.maxWarnings
72
73 if (program.fix) {
74 for (const report of reports) {
75 const inputSrc = fs.readFileSync(report.filePath).toString()
76
77 const fixes = _(report.reports)
78 .filter(x => x.fix)
79 .map(x => x.fix(ruleFixer))
80 .sort((a, b) => a.range[0] - b.range[0])
81 .value()
82
83 const { fixed, output } = applyFixes(fixes, inputSrc)
84 if (fixed) {
85 report.reports = report.reports.filter(x => !x.fix)
86 fs.writeFileSync(report.filePath, output)
87 }
88 }
89 }
90
91 if (program.quiet) {
92 // filter the list of reports, to set errors only.
93 reports[0].reports = reports[0].reports.filter(i => i.severity === 2)
94 }
95
96 if (printReports(reports, formatterFn)) {
97 if (program.maxWarnings && !reports[0].errorCount && warningsNumberExceeded) {
98 console.log(
99 'Solhint found more warnings than the maximum specified (maximum: %s)',
100 program.maxWarnings
101 )
102 process.exit(1)
103 }
104 }
105
106 exitWithCode(reports)
107}
108
109function processStdin(options) {
110 const STDIN_FILE = 0
111 const stdinBuffer = fs.readFileSync(STDIN_FILE)
112
113 const report = processStr(stdinBuffer.toString())
114 report.file = options.filename || 'stdin'
115 const formatterFn = getFormatter()
116 printReports([report], formatterFn)
117
118 exitWithCode([report])
119}
120
121function writeSampleConfigFile() {
122 const configPath = '.solhint.json'
123 const sampleConfig = `{
124 "extends": "solhint:default"
125}
126`
127
128 if (!fs.existsSync(configPath)) {
129 fs.writeFileSync(configPath, sampleConfig)
130
131 console.log('Configuration file created!')
132 } else {
133 console.log('Configuration file already exists')
134 }
135
136 process.exit(0)
137}
138
139const readIgnore = _.memoize(() => {
140 let ignoreFile = '.solhintignore'
141 try {
142 if (program.ignorePath) {
143 ignoreFile = program.ignorePath
144 }
145
146 return fs
147 .readFileSync(ignoreFile)
148 .toString()
149 .split('\n')
150 .map(i => i.trim())
151 } catch (e) {
152 if (program.ignorePath && e.code === 'ENOENT') {
153 console.error(`\nERROR: ${ignoreFile} is not a valid path.`)
154 }
155 return []
156 }
157})
158
159const readConfig = _.memoize(() => {
160 let config = {}
161
162 try {
163 config = loadConfig(program.config)
164 } catch (e) {
165 console.log(e.message)
166 process.exit(1)
167 }
168
169 const configExcludeFiles = _.flatten(config.excludedFiles)
170 config.excludedFiles = _.concat(configExcludeFiles, readIgnore())
171
172 // validate the configuration before continuing
173 validate(config)
174
175 return config
176})
177
178function processStr(input) {
179 return linter.processStr(input, readConfig())
180}
181
182function processPath(path) {
183 return linter.processPath(path, readConfig())
184}
185
186function printReports(reports, formatter) {
187 console.log(formatter(reports))
188 return reports
189}
190
191function getFormatter(formatter) {
192 const formatterName = formatter || 'stylish'
193 try {
194 return require(`eslint/lib/formatters/${formatterName}`)
195 } catch (ex) {
196 ex.message = `\nThere was a problem loading formatter option: ${program.formatter} \nError: ${
197 ex.message
198 }`
199 throw ex
200 }
201}
202
203function exitWithCode(reports) {
204 const errorsCount = reports.reduce((acc, i) => acc + i.errorCount, 0)
205
206 process.exit(errorsCount > 0 ? 1 : 0)
207}
208
209init()