1 | const fs = require('fs')
|
2 | const path = require('path')
|
3 | const glob = require('glob')
|
4 |
|
5 | const _ = require('lodash')
|
6 | const execa = require('execa')
|
7 | const copy = require('ncp').ncp
|
8 | const tmp = require('tmp')
|
9 |
|
10 | const FILE_REGEX = /\s*(\S*\.js):\d+:\d+/
|
11 | const FILE_REGEX_GLOBAL = /\s*(\S*\.js):\d+:\d+/g
|
12 |
|
13 | describe('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 | })
|