UNPKG

6.5 kBJavaScriptView Raw
1const fs = require('fs')
2const path = require('path')
3const glob = require('glob')
4
5const _ = require('lodash')
6const execa = require('execa')
7const copy = require('ncp').ncp
8const tmp = require('tmp')
9
10const FILE_REGEX = /\s*(\S*\.js):\d+:\d+/
11const FILE_REGEX_GLOBAL = /\s*(\S*\.js):\d+:\d+/g
12
13describe('bin/lint.js', () => {
14 let tmpDir, results
15
16 function getFullPath(dir) {
17 return dir === tmpDir.name ? dir : path.join(__dirname, dir)
18 }
19
20 function diffDirectories(dirA, dirB) {
21 dirA = getFullPath(dirA)
22 dirB = getFullPath(dirB)
23 const filesA = glob.sync(path.join(dirA, '**/*.js')).sort()
24 const filesB = glob.sync(path.join(dirB, '**/*.js')).sort()
25 expect(filesA).to.have.length(filesB.length)
26 filesA.forEach((fileA, index) => {
27 const contentA = fs.readFileSync(fileA, 'utf8')
28 const contentB = fs.readFileSync(filesB[index], 'utf8')
29 expect(contentA).to.equal(contentB)
30 })
31 }
32
33 function parseResult(result) {
34 const content = result.stdout
35 .split('\n')
36 .filter(s => s.trim())
37 .join('\t')
38 .trim()
39
40 if (!content.match(FILE_REGEX)) {
41 return {files: [], byFile: {}}
42 }
43
44 const files = content
45 .match(FILE_REGEX_GLOBAL)
46 .map(item => item.match(FILE_REGEX)[1])
47 const fileResults = files
48 .map((file, index) => {
49 const start = content.indexOf(file) + file.length
50 let end = content.indexOf(files[index + 1])
51 end = end === -1 ? undefined : end
52 const rules = content.slice(start, end)
53 return {file, rules}
54 })
55
56 return {files, byFile: _.keyBy(fileResults, 'file')}
57 }
58
59 function setup(dir, args, beforeLint, done) {
60 if (arguments.length === 3) {
61 done = beforeLint
62 beforeLint = _.noop
63 }
64
65 tmpDir = tmp.dirSync({unsafeCleanup: true})
66 copy(path.join(__dirname, dir), tmpDir.name, err => {
67 if (err) {
68 return done(err)
69 }
70
71 beforeLint()
72 execa(path.join(__dirname, '../bin/lint.js'), args, {cwd: tmpDir.name})
73 .catch(err => err)
74 .then(result => results = Object.assign(result, parseResult(result)))
75 .then(() => done())
76 .catch(done)
77 })
78 }
79
80 function teardown(done, copyBack) {
81 const finish = err => {
82 if (tmpDir) {
83 tmpDir.removeCallback()
84 }
85
86 tmpDir = null
87 done(err)
88 }
89
90 if (copyBack) {
91 copy(tmpDir.name, path.join(__dirname, copyBack), finish)
92 } else {
93 finish()
94 }
95 }
96
97 context('node', () => {
98 before(done => setup('fixtures/node', ['--fix'], done))
99 after(done => teardown(done, 'fixtures/node-actual'))
100
101 describe('source linting', () => {
102 it('should exit with error code', () => {
103 expect(results.code).to.equal(1)
104 })
105
106 it('should find errors in ./', () => {
107 expect(results.files).to.contain('toplevel.js')
108 })
109
110 it('should find errors in lib/', () => {
111 expect(results.files).to.contain('lib/file.js')
112 })
113
114 it('should find errors in src/', () => {
115 expect(results.files).to.contain('src/file.js')
116 })
117
118 it('should find errors in bin/', () => {
119 expect(results.files).to.contain('bin/file.js')
120 })
121
122 it('should use source config', () => {
123 expect(results.byFile).to.have.property('lib/file.js')
124 const violations = results.byFile['lib/file.js'].rules
125 expect(violations).to.contain('it is not defined')
126 expect(violations).to.contain('no-unused-expressions')
127 })
128 })
129
130 describe('test linting', () => {
131 it('should find errors in test/', () => {
132 expect(results.files).to.contain('test/file.test.js')
133 })
134
135 it('should use test config', () => {
136 expect(results.byFile).to.have.property('test/file.test.js')
137 const violations = results.byFile['test/file.test.js'].rules
138 expect(violations).to.not.contain('it is not defined')
139 expect(violations).to.not.contain('no-unused-expressions')
140 })
141 })
142
143 describe('--fix', () => {
144 it('should fix errors', () => {
145 diffDirectories(tmpDir.name, 'fixtures/node-expected')
146 })
147 })
148 })
149
150 describe('package.json overrides', () => {
151 function beforeLint() {
152 fs.writeFileSync(path.join(tmpDir.name, 'package.json'), JSON.stringify({
153 config: {
154 eslint: {
155 envs: ['browser'],
156 globals: ['__DEV__'],
157 rules: {'no-console': 'off'},
158 },
159 },
160 }, null, 2), 'utf-8')
161 }
162
163 before(done => setup('fixtures/node-overrides', [], beforeLint, done))
164 after(teardown)
165
166 it('should still lint', () => {
167 expect(results.byFile).to.have.property('file.js')
168 const violations = results.byFile['file.js'].rules
169 expect(violations).to.contain('Extra semicolon')
170 })
171
172 it('should respect overrides', () => {
173 expect(results.byFile).to.have.property('file.js')
174 const violations = results.byFile['file.js'].rules
175 expect(violations).to.not.contain('document is not defined')
176 expect(violations).to.not.contain('__DEV__ is not defined')
177 expect(violations).to.not.contain('Unexpected console')
178 })
179 })
180
181 context('react', () => {
182 before(done => setup('fixtures/react', ['-t', 'react', '--fix'], done))
183 after(done => teardown(done, 'fixtures/react-actual'))
184
185 describe('linting', () => {
186 it('should use browser env', () => {
187 expect(results.byFile).to.have.property('file.js')
188 const violations = results.byFile['file.js'].rules
189 expect(violations).to.not.contain('document is not defined')
190 expect(violations).to.not.contain('localStorage is not defined')
191 })
192
193 it('should find react-specific errors', () => {
194 expect(results.byFile).to.have.property('file.js')
195 const violations = results.byFile['file.js'].rules
196 expect(violations).to.contain('prop is never used')
197 expect(violations).to.contain('key must begin with handle')
198 expect(violations).to.contain('Link is not defined')
199 })
200
201 it('should use webpack resolution', () => {
202 expect(results.byFile).to.have.property('file.js')
203 const violations = results.byFile['file.js'].rules
204 expect(violations).to.not.contain('Unable to resolve path to module src/dep2')
205 })
206 })
207
208 describe('--fix', () => {
209 it('should fix errors', () => {
210 diffDirectories(tmpDir.name, 'fixtures/react-expected')
211 })
212 })
213 })
214})