Skip to content

Commit bce6c51

Browse files
committed
feat: Write report to a file as JSON
Add the option "report" pointing to the file to write the report to.
1 parent ff8a595 commit bce6c51

File tree

6 files changed

+148
-16
lines changed

6 files changed

+148
-16
lines changed

Gruntfile.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ module.exports = function (grunt) {
5050
},
5151
incomplete: {
5252
options: {
53-
quiet: true
53+
quiet: true,
54+
report: 'test/work/incomplete.json'
5455
},
5556
src: 'test/work/incomplete.html'
5657
},

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ grunt.initConfig({
7070
options: {
7171
force: false,
7272
quiet: false,
73+
report: '',
7374
ignoreMissing: false,
7475
tidyOptions: {}
7576
},
@@ -95,6 +96,33 @@ Default: `false`
9596
Suppresses printing of errors and warnings about problems found in input
9697
files on the console. if set to `true`.
9798

99+
#### report
100+
Type: `String`
101+
Default: ''
102+
103+
Path to the file, where the report will be written. If specified, the file
104+
will contain an array of objects describing every problem found, for example:
105+
106+
```json
107+
[
108+
{
109+
"type": "warning",
110+
"firstColumn": 1,
111+
"lastColumn": 1,
112+
"lastLine": 1,
113+
"message": "missing <!DOCTYPE> declaration",
114+
"extract": "<html></html>",
115+
"hiliteStart": 0,
116+
"hiliteLength": 0,
117+
"file":"test/work/incomplete.html"
118+
}
119+
]
120+
```
121+
122+
The file will have the JSON format compatible with the JSON report produced by
123+
[grunt-html], which is backed up by gthe [Nu Html Checker (v.Nu)]. Marking the
124+
invalid code excerps is not supported.
125+
98126
#### ignoreMissing
99127
Type: `Boolean`
100128
Default: `false`
@@ -117,11 +145,12 @@ as a property value in the object.
117145
'tidy-html5': {
118146
accessibility: {
119147
options: {
148+
report: 'output/report.json',
120149
tidyOptions: {
121150
'accessibility-check': 2
122151
}
123152
},
124-
src: ['*.html']
153+
src: ['input/*.html']
125154
}
126155
}
127156
```
@@ -134,6 +163,7 @@ your code using Grunt.
134163

135164
## Release History
136165

166+
* 2018-03-06 v0.1.0 Write report to a file as JSON
137167
* 2018-02-26 v0.0.1 Initial release
138168

139169
## License
@@ -155,3 +185,6 @@ Licensed under the MIT license.
155185
[options]: https://github.com/gagern/node-libtidy/blob/master/API.md#TidyDoc.options
156186
[libtidy options]: https://github.com/gagern/node-libtidy/blob/master/README.md#options
157187
[all HTML Tidy options]: http://api.html-tidy.org/tidy/quickref_5.4.0.html
188+
[grunt-html]: https://github.com/jzaefferer/grunt-html
189+
[Nu Html Checker (v.Nu)]: https://validator.github.io/validator/
190+

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
},
2828
"main": "tasks/tidy-html5.js",
2929
"scripts": {
30-
"security": "test `node --version | cut -c 2` -ne 4 && nsp check || echo 'No vulnerabilities check possible on Node.js 4'",
30+
"prepare": "test `node --version | cut -c 2` -ne 4 && nsp check || echo 'No vulnerabilities check possible on Node.js 4'",
3131
"test": "grunt",
3232
"coverage": "grunt instrument && GRUNT_TIDY_HTML5_COVERAGE=1 grunt",
3333
"coveralls": "test `node --version | cut -c 2` -eq 8 && npm run coverage && grunt coveralls",
@@ -36,6 +36,7 @@
3636
},
3737
"dependencies": {
3838
"chalk": "^2.3.1",
39+
"mkdirp": "^0.5.1",
3940
"libtidy": "^0.3.7"
4041
},
4142
"devDependencies": {

tasks/tidy-html5.js

Lines changed: 99 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@
1111
const fs = require('fs')
1212
const chalk = require('chalk')
1313
const libtidy = require('libtidy')
14+
const mkdirp = require('mkdirp')
15+
const path = require('path')
1416

1517
module.exports = function (grunt) {
1618
grunt.registerMultiTask('tidy-html5', 'Checks and fixes HTML files using tidy-html5.', function () {
1719
const done = this.async()
1820
const options = this.options({
1921
force: false,
2022
quiet: false,
23+
report: '',
2124
ignoreMissing: false,
2225
tidyOptions: {}
2326
})
2427
const force = options.force
2528
const quiet = options.quiet
29+
const report = options.report
2630
const ignoreMissing = options.ignoreMissing
2731
const tidyOptions = options.tidyOptions
2832
const files = this.files
2933
const warn = force ? grunt.log.warn : grunt.fail.warn
34+
const reports = []
3035
let processed = 0
3136
let failed = 0
3237

@@ -48,22 +53,23 @@ module.exports = function (grunt) {
4853

4954
files.reduce(function (previous, file) {
5055
return previous.then(function () {
51-
return file.src.reduce(process, Promise.resolve())
56+
return file.src.reduce(processFile, Promise.resolve())
5257
})
5358
}, Promise.resolve())
5459
.then(function () {
5560
const ok = failed ? force ? grunt.log.warn : grunt.fail.warn
5661
: grunt.log.ok
5762
ok(processed + ' ' + grunt.util.pluralize(processed,
5863
'file/files') + ' processed, ' + failed + ' failed.')
64+
return writeReport()
5965
}, function (error) {
6066
grunt.verbose.error(error.stack)
6167
grunt.log.error(error)
6268
warn('Processing HTML files failed.')
6369
})
6470
.then(done)
6571

66-
function process (previous, source) {
72+
function processFile (previous, source) {
6773
return previous.then(function () {
6874
const document = libtidy.TidyDoc()
6975
document.options = tidyOptions
@@ -82,13 +88,101 @@ module.exports = function (grunt) {
8288
return document.parseBuffer(buffer)
8389
})
8490
.then(function (result) {
85-
const errors = result.errlog
86-
if (errors.length) {
91+
const messages = result.errlog
92+
if (messages.length) {
8793
if (!quiet) {
88-
grunt.log.write(errors)
94+
grunt.log.write(messages)
8995
}
9096
++failed
9197
}
98+
return addReport(source, messages)
99+
})
100+
})
101+
}
102+
103+
function writeReport () {
104+
function writeReport () {
105+
return writeFile(report, JSON.stringify(reports))
106+
}
107+
108+
if (report) {
109+
const directory = path.dirname(report)
110+
grunt.verbose.writeln('Writing report to "' + chalk.cyan(report) + '".')
111+
if (directory) {
112+
return ensureDirectory(directory).then(writeReport)
113+
}
114+
return writeReport()
115+
}
116+
}
117+
118+
function addReport (source, messages) {
119+
if (messages) {
120+
return readFile(source).then(function (content) {
121+
reportFile(source, content, messages)
122+
})
123+
}
124+
}
125+
126+
function reportFile (name, content, messages) {
127+
const contentLines = content.split(/\r?\n/)
128+
messages.split(/\r?\n/).forEach(function (line) {
129+
const message = parseMessage(line)
130+
const place = contentLines[message.lastLine - 1] || ''
131+
message.extract = place.substr(message.firstColumn - 1)
132+
message.hiliteLength = message.hiliteStart = 0
133+
message.file = name
134+
reports.push(message)
135+
})
136+
}
137+
138+
function parseMessage (message) {
139+
const parsed = /^line (\d+) column (\d+) - (\w+):/.exec(message)
140+
var column
141+
if (parsed) {
142+
column = parseInt(parsed[2])
143+
return parsed && {
144+
type: parsed[3].toLowerCase(),
145+
firstColumn: column,
146+
lastColumn: column,
147+
lastLine: parseInt(parsed[1]),
148+
message: message.substr(parsed[0].length + 1)
149+
}
150+
}
151+
return {}
152+
}
153+
154+
function ensureDirectory (name) {
155+
return new Promise(function (resolve, reject) {
156+
mkdirp(name, function (error) {
157+
if (error) {
158+
reject(error)
159+
} else {
160+
resolve()
161+
}
162+
})
163+
})
164+
}
165+
166+
function writeFile (name, content) {
167+
return new Promise(function (resolve, reject) {
168+
fs.writeFile(name, content, function (error) {
169+
if (error) {
170+
reject(error)
171+
} else {
172+
resolve()
173+
}
174+
})
175+
})
176+
}
177+
178+
function readFile (name) {
179+
return new Promise(function (resolve, reject) {
180+
fs.readFile(name, 'utf-8', function (error, content) {
181+
if (error) {
182+
reject(error)
183+
} else {
184+
resolve(content)
185+
}
92186
})
93187
})
94188
}

test/expected/incomplete.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[{"type":"warning","firstColumn":1,"lastColumn":1,"lastLine":1,"message":"missing <!DOCTYPE> declaration","extract":"<html></html>","hiliteStart":0,"hiliteLength":0,"file":"test/work/incomplete.html"},{"type":"warning","firstColumn":7,"lastColumn":7,"lastLine":1,"message":"discarding unexpected </html>","extract":"</html>","hiliteStart":0,"hiliteLength":0,"file":"test/work/incomplete.html"},{"type":"warning","firstColumn":14,"lastColumn":14,"lastLine":1,"message":"inserting missing 'title' element","extract":"","hiliteStart":0,"hiliteLength":0,"file":"test/work/incomplete.html"},{"extract":"","hiliteStart":0,"hiliteLength":0,"file":"test/work/incomplete.html"}]

test/tidy-html5_test.js

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,30 @@
22

33
const fs = require('fs')
44

5-
function readPage (path) {
6-
const content = fs.readFileSync('test/' + path + '.html', 'utf-8')
5+
function readFile (path) {
6+
const content = fs.readFileSync('test/' + path, 'utf-8')
77
return content.replace(/\r|\n/g, '')
88
}
99

10-
function readPages (name) {
10+
function readFiles (name) {
1111
return {
12-
expected: readPage('expected/' + name),
13-
work: readPage('work/' + name)
12+
expected: readFile('expected/' + name),
13+
work: readFile('work/' + name)
1414
}
1515
}
1616

1717
exports['tidy-html5'] = {
1818
incomplete: function (test) {
19-
const pages = readPages('incomplete')
20-
test.expect(1)
19+
const pages = readFiles('incomplete.html')
20+
const reports = readFiles('incomplete.json')
21+
test.expect(2)
2122
test.equal(pages.expected, pages.work, 'incomplete.html')
23+
test.equal(reports.expected, reports.work, 'incomplete.json')
2224
test.done()
2325
},
2426

2527
minimal: function (test) {
26-
const pages = readPages('minimal')
28+
const pages = readFiles('minimal.html')
2729
test.expect(1)
2830
test.equal(pages.expected, pages.work, 'minimal.html')
2931
test.done()

0 commit comments

Comments
 (0)