1 | #!/usr/bin/env node
|
2 |
|
3 | const program = require('commander')
|
4 | const _ = require('lodash')
|
5 | const fs = require('fs')
|
6 | const process = require('process')
|
7 |
|
8 | const linter = require('./lib/index')
|
9 | const { loadConfig } = require('./lib/config/config-file')
|
10 | const { validate } = require('./lib/config/config-validator')
|
11 | const applyFixes = require('./lib/apply-fixes')
|
12 | const ruleFixer = require('./lib/rule-fixer')
|
13 | const packageJson = require('./package.json')
|
14 |
|
15 | function 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 |
|
53 | function execMainAction() {
|
54 | if (program.init) {
|
55 | writeSampleConfigFile()
|
56 | }
|
57 |
|
58 | let formatterFn
|
59 |
|
60 | try {
|
61 |
|
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 |
|
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 |
|
109 | function 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 |
|
121 | function 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 |
|
139 | const 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 |
|
159 | const 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 |
|
173 | validate(config)
|
174 |
|
175 | return config
|
176 | })
|
177 |
|
178 | function processStr(input) {
|
179 | return linter.processStr(input, readConfig())
|
180 | }
|
181 |
|
182 | function processPath(path) {
|
183 | return linter.processPath(path, readConfig())
|
184 | }
|
185 |
|
186 | function printReports(reports, formatter) {
|
187 | console.log(formatter(reports))
|
188 | return reports
|
189 | }
|
190 |
|
191 | function 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 |
|
203 | function exitWithCode(reports) {
|
204 | const errorsCount = reports.reduce((acc, i) => acc + i.errorCount, 0)
|
205 |
|
206 | process.exit(errorsCount > 0 ? 1 : 0)
|
207 | }
|
208 |
|
209 | init()
|