Skip to content

Commit c878103

Browse files
committed
use puppeteer instead of phantomjs
1 parent 6c2a474 commit c878103

File tree

9 files changed

+1708
-565
lines changed

9 files changed

+1708
-565
lines changed

.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# valid values are: fatal, error, warn, info, debug, trace
2+
LOG_LEVEL=info
3+
4+
# valid values are: short, long, simple, json, bunyan
5+
LOG_FORMAT=short
6+
7+
# Set to "development" to open the "headless" browser and open Dev Tools
8+
# (so it pauses on `debugger` statements)
9+
NODE_ENV=production

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@
55

66
# HTML Coverage Report
77
/coverage/
8+
9+
.env

bin/css-coverage

Lines changed: 1 addition & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -2,267 +2,4 @@
22

33
process.bin = process.title = 'css-coverage';
44

5-
var fs = require('fs');
6-
var path = require('path');
7-
var childProcess = require('child_process');
8-
var phantomjs = require('phantomjs-prebuilt');
9-
var commander = require('commander');
10-
var SourceMapConsumer = require('source-map').SourceMapConsumer;
11-
var cssTree = require('css-tree');
12-
13-
14-
function parseFileName(filePath) {
15-
return path.resolve(process.cwd(), filePath);
16-
}
17-
18-
commander
19-
// .usage('[options]')
20-
.description('Generate coverage info for a CSS file against an HTML file. This supports loading sourcemaps by using the sourceMappingURL=FILENAME.map CSS comment')
21-
.option('--html [path/to/file.html]', 'path to a local HTML file', parseFileName) // TODO: Support multiple
22-
.option('--css [path/to/file.css]', 'path to a local CSS file', parseFileName)
23-
.option('--lcov [path/to/output.lcov]', 'the LCOV output file', parseFileName)
24-
.option('--verbose', 'verbose/debugging output')
25-
.option('--ignore-source-map', 'disable loading the sourcemap if one is found')
26-
.option('--cover-declarations', 'try to cover CSS declarations as well as selectors (best-effort, difficult with sourcemaps)')
27-
.parse(process.argv);
28-
29-
// Validate args
30-
if (!commander.html && !commander.css) {
31-
commander.help();
32-
}
33-
if (commander.html) {
34-
if (!fs.statSync(commander.html).isFile()) {
35-
console.error('ERROR: Invalid argument. HTML file not found at ' + commander.html);
36-
process.exit(1);
37-
}
38-
} else {
39-
console.error('ERROR: Missing argument. At least 1 HTML file must be specified');
40-
process.exit(1);
41-
}
42-
if (commander.css) {
43-
if (!fs.statSync(commander.css).isFile()) {
44-
console.error('ERROR: Invalid argument. CSS file not found at ' + commander.css);
45-
process.exit(1);
46-
}
47-
} else {
48-
console.error('ERROR: Missing argument. A CSS file must be specified');
49-
process.exit(1);
50-
}
51-
52-
var CSS_STR = fs.readFileSync(commander.css, 'utf8');
53-
var ast;
54-
try {
55-
ast = cssTree.parse(CSS_STR, { filename: commander.css, positions: true });
56-
} catch (e) {
57-
// CssSyntaxError
58-
console.error('CssSyntaxError: ' + e.message + ' @ ' + e.line + ':' + e.column);
59-
throw e;
60-
}
61-
62-
var cssForPhantom = [];
63-
cssTree.walkRules(ast, (rule) => {
64-
if ('Atrule' === rule.type) {
65-
// ignore
66-
} else if ('Rule' === rule.type) {
67-
var converted = rule.prelude.children.map((selector) => {
68-
return cssTree.translate(selector)
69-
})
70-
cssForPhantom.push(converted)
71-
} else {
72-
throw new Error('BUG: Forgot to handle this rule subtype: ' + rule.type)
73-
}
74-
})
75-
76-
// Check if there is a sourceMappingURL
77-
var sourceMapConsumer = null;
78-
if (!commander.ignoreSourceMap && /sourceMappingURL=([^\ ]*)/.exec(CSS_STR)) {
79-
var sourceMapPath = /sourceMappingURL=([^\ ]*)/.exec(CSS_STR)[1];
80-
sourceMapPath = path.resolve(path.dirname(commander.css), sourceMapPath);
81-
if (commander.verbose) {
82-
console.error('Using sourceMappingURL at ' + sourceMapPath);
83-
}
84-
var sourceMapStr = fs.readFileSync(sourceMapPath);
85-
var sourceMap = JSON.parse(sourceMapStr);
86-
sourceMapConsumer = new SourceMapConsumer(sourceMap);
87-
88-
// sourceMapConsumer.eachMapping(function (m) { console.log(m.generatedLine, m.source); });
89-
}
90-
91-
92-
var phantomCSSJSON = JSON.stringify(cssForPhantom);
93-
94-
var coverageOutput = [];
95-
var program = phantomjs.exec(path.resolve(__dirname, '../phantom-coverage.js'), require.resolve('sizzle'), commander.html, phantomCSSJSON);
96-
program.stderr.pipe(process.stderr);
97-
if (commander.verbose) {
98-
program.stdout.pipe(process.stderr);
99-
}
100-
// Collect the coverage info that is written by the phantom script.
101-
program.stdout.on('data', function(data) {
102-
data.toString().split('\n').forEach(function (entry) {
103-
if (entry.trim()) {
104-
try {
105-
var parsedJSON = JSON.parse(entry);
106-
coverageOutput.push(parsedJSON);
107-
} catch (e) {
108-
console.warn(entry);
109-
}
110-
}
111-
});
112-
});
113-
program.on('exit', function(code) {
114-
// if success, then write out the LCOV file
115-
if (code === 0) {
116-
117-
var lcovStr = generateLcovStr(coverageOutput);
118-
if (commander.lcov) {
119-
fs.writeFileSync(commander.lcov, lcovStr);
120-
} else {
121-
console.log(lcovStr);
122-
}
123-
}
124-
// do something on end
125-
process.exit(code);
126-
});
127-
128-
129-
function generateLcovStr(coverageOutput) {
130-
// coverageOutput is of the form:
131-
// [[1, ['body']], [400, ['div.foo']]]
132-
// where each entry is a pair of count, selectors
133-
var expected = cssForPhantom.length;
134-
var actual = coverageOutput.length;
135-
if (expected !== actual) {
136-
throw new Error('BUG: count lengths do not match. Expected: ' + expected + ' Actual: ' + actual);
137-
}
138-
139-
var files = {}; // key is filename, value is [{startLine, endLine, count}]
140-
var ret = []; // each line in the lcov file. Joined at the end of the function
141-
142-
var cssLines = CSS_STR.split('\n');
143-
144-
function addCoverage(fileName, count, startLine, endLine) {
145-
// add it to the files
146-
if (!files[fileName]) {
147-
files[fileName] = [];
148-
}
149-
files[fileName].push({startLine: startLine, endLine: endLine, count: count});
150-
}
151-
152-
var i = -1;
153-
cssTree.walkRules(ast, (rule, item, list) => {
154-
if ('Rule' !== rule.type) {
155-
return // Skip AtRules
156-
}
157-
158-
i += 1;
159-
160-
var count = coverageOutput[i][0];
161-
var fileName;
162-
var startLine;
163-
var endLine;
164-
// Look up the source map (if available)
165-
if (sourceMapConsumer) {
166-
// From https://github.com/mozilla/source-map#sourcemapconsumerprototypeoriginalpositionforgeneratedposition
167-
// Could have been {line: rule.position.start.line, column: rule.positoin.start.column}
168-
var origStart = rule.loc.start;
169-
var origEnd = rule.loc.end;
170-
171-
if (commander.coverDeclarations) {
172-
173-
// Loop over every character between origStart and origEnd to make sure they are covered
174-
// TODO: Do not duplicate-count lines just because this code runs character-by-character
175-
var parseColumn = origStart.column;
176-
for (var parseLine=origStart.line; parseLine <= origEnd.line; parseLine++) {
177-
var curLineText = cssLines[parseLine - 1];
178-
for (var curColumn=parseColumn-1; curColumn < curLineText.length; curColumn++) {
179-
var info = sourceMapConsumer.originalPositionFor({line: parseLine, column: curColumn});
180-
// stop processing when we hit origEnd
181-
if (parseLine === origEnd.line && curColumn >= origEnd.column) {
182-
break;
183-
}
184-
if (/\s/.test(curLineText[curColumn])) {
185-
continue;
186-
}
187-
// console.error('PHIL ', curLineText[curColumn], {line: parseLine, column: curColumn}, info);
188-
if (info.source) {
189-
addCoverage(info.source, count, info.line, info.line);
190-
} else {
191-
if (commander.verbose) {
192-
console.error('BUG: Could not look up source for this range:');
193-
console.error('origStart', origStart);
194-
console.error('origEnd', origEnd);
195-
console.error('currIndexes', {line: parseLine, column: curColumn});
196-
}
197-
}
198-
}
199-
parseColumn = 1;
200-
}
201-
202-
203-
} else {
204-
// Just cover the selectors
205-
var startInfo = sourceMapConsumer.originalPositionFor({line: origStart.line, column: origStart.column-1});
206-
var endInfo = sourceMapConsumer.originalPositionFor({line: origEnd.line, column: origEnd.column-2});
207-
208-
// When there is no match, startInfo.source is null
209-
if (!startInfo.source /*|| startInfo.source !== endInfo.source*/) {
210-
console.error('cssStart', JSON.stringify(origStart));
211-
console.error('cssEnd', JSON.stringify(origEnd));
212-
// console.error('sourceStart', JSON.stringify(startInfo));
213-
// console.error('sourceEnd', JSON.stringify(endInfo));
214-
throw new Error('BUG: sourcemap might be invalid. Maybe try regenerating it?');
215-
} else {
216-
if (commander.verbose) {
217-
console.error('DEBUG: MATCHED this one', JSON.stringify(startInfo));
218-
}
219-
}
220-
221-
addCoverage(startInfo.source, count, startInfo.line, startInfo.line);
222-
}
223-
224-
225-
} else {
226-
// No sourceMap available
227-
fileName = commander.css;
228-
startLine = rule.loc.start.line;
229-
if (commander.coverDeclarations) {
230-
endLine = rule.loc.end.line;
231-
} else {
232-
endLine = startLine; // Just do the selector (startLine)
233-
}
234-
addCoverage(fileName, count, startLine, endLine);
235-
}
236-
237-
});
238-
239-
for (var fileName in files) {
240-
241-
var nonZero = 0; // For summary info
242-
var allCounter = 0;
243-
var fileNamePrefix = sourceMapPath ? path.dirname(sourceMapPath) : '';
244-
ret.push('SF:' + path.resolve(fileNamePrefix, fileName));
245-
246-
files[fileName].forEach(function(entry) {
247-
var startLine = entry.startLine;
248-
var endLine = entry.endLine;
249-
var count = entry.count;
250-
251-
for (var line=startLine; line <= endLine; line++) {
252-
ret.push('DA:' + line + ',' + count);
253-
if (count > 0) {
254-
nonZero += 1;
255-
}
256-
allCounter += 1;
257-
}
258-
259-
});
260-
261-
// Include summary info for the file
262-
ret.push('LH:' + nonZero);
263-
ret.push('LF:' + allCounter);
264-
ret.push('end_of_record');
265-
}
266-
267-
return ret.join('\n');
268-
}
5+
require('../src/runCoverage')

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
{
22
"name": "css-coverage",
33
"version": "0.2.6",
4+
"scripts": {
5+
"test": "standard --fix"
6+
},
47
"dependencies": {
8+
"bunyan": "^1.8.12",
9+
"bunyan-format": "^0.2.1",
510
"commander": "2.16.0",
611
"css-tree": "1.0.0-alpha25",
12+
"dotenv": "^6.0.0",
713
"phantomjs-prebuilt": "2.1.16",
14+
"puppeteer": "^1.5.0",
815
"sizzle": "2.3.3",
9-
"source-map": "0.5.6"
16+
"source-map": "0.5.6",
17+
"standard": "^11.0.1"
1018
},
1119
"bin": {
1220
"css-coverage": "bin/css-coverage"

phantom-coverage.js

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)