css-coverage
Advanced tools
Comparing version 1.3.1 to 2.0.0
{ | ||
"name": "css-coverage", | ||
"version": "1.3.1", | ||
"version": "2.0.0", | ||
"scripts": { | ||
"pretest": "sass --source-map ./test/test.scss ./test/test.css", | ||
"test": "./bin/css-coverage.js --cover-declarations --html ./test/test.html --css ./test/test.css --lcov ./test/test.lcov", | ||
"test": "./bin/css-coverage.js --html ./test/test.html --css ./test/test.css --lcov ./test/test.lcov --ignore-declarations 'move-to,move-foobar'", | ||
"test-debug": "node --inspect-brk ./bin/css-coverage.js --cover-declarations --html ./test/test.html --css ./test/test.css --lcov ./test/test.lcov", | ||
@@ -8,0 +8,0 @@ "posttest": "standard --fix" |
@@ -22,2 +22,6 @@ const fs = require('fs') | ||
function parseTokenList (tokenString) { | ||
return tokenString.split(',').map(token => token.trim().toLowerCase()) | ||
} | ||
const STATUS_CODE = { | ||
@@ -36,3 +40,3 @@ ERROR: 111, | ||
.option('--ignore-source-map', 'disable loading the sourcemap if one is found') | ||
.option('--cover-declarations', 'try to cover CSS declarations as well as selectors (best-effort, difficult with sourcemaps)') | ||
.option('--ignore-declarations [move-to,content]', 'A comma-separated list of declarations to ignore', parseTokenList) | ||
.parse(process.argv) | ||
@@ -74,2 +78,4 @@ | ||
const cssRules = [] | ||
const cssDeclarations = {} // so it is serializable to the browser | ||
cssTree.walkRules(ast, (rule) => { | ||
@@ -80,2 +86,13 @@ if (rule.type === 'Atrule') { | ||
const converted = rule.prelude.children.map((selector) => { | ||
rule.block.children.each(declaration => { | ||
if (commander.ignoreDeclarations.indexOf(declaration.property.toLowerCase()) >= 0) { | ||
return // skip because it is ignored | ||
} | ||
// Append to a list of locations | ||
const key = cssTree.translate(declaration) | ||
let locs = cssDeclarations[key] | ||
locs = locs || [] | ||
locs.push(declaration.loc) | ||
cssDeclarations[key] = locs | ||
}) | ||
return cssTree.translate(selector) | ||
@@ -159,3 +176,3 @@ }) | ||
log.debug(`Calculating coverage`) | ||
const coverageOutput = await page.evaluate(cssRules => { | ||
const { matchedSelectors: coverageOutput, supportedDeclarations } = await page.evaluate((cssRules, cssDeclarations) => { | ||
// This is the meat of the code. It runs inside the browser | ||
@@ -173,3 +190,3 @@ console.log(`Starting evaluation`) | ||
const ret = [] | ||
const matchedSelectors = [] | ||
rules.forEach(function (selectors) { | ||
@@ -203,9 +220,19 @@ console.log(`Checking selector: "${JSON.stringify(selectors)}"`) | ||
ret.push([count, selectors]) | ||
matchedSelectors.push([count, selectors]) | ||
}) | ||
console.log(`Finished checking selectors`) | ||
return ret | ||
}, cssRules) | ||
console.log(`Checking if declarations are understandable by the browser`) | ||
const supportedDeclarations = [] | ||
for (const decl of cssDeclarations) { | ||
if (window.CSS.supports(decl)) { | ||
supportedDeclarations.push(decl) | ||
} else { | ||
console.warn(`Unsupported declaration ${decl}`) | ||
} | ||
} | ||
return { matchedSelectors, supportedDeclarations } | ||
}, cssRules, Object.keys(cssDeclarations)) | ||
log.debug('Closing browser') | ||
@@ -217,3 +244,3 @@ await browser.close() | ||
const lcovStr = await generateLcovStr(coverageOutput) | ||
const lcovStr = await generateLcovStr(coverageOutput, supportedDeclarations) | ||
if (commander.lcov) { | ||
@@ -234,3 +261,3 @@ fs.writeFileSync(commander.lcov, lcovStr) | ||
async function generateLcovStr (coverageOutput) { | ||
async function generateLcovStr (coverageOutput, supportedDeclarations) { | ||
// coverageOutput is of the form: | ||
@@ -259,7 +286,24 @@ // [[1, ['body']], [400, ['div.foo']]] | ||
function getStartInfo (origStart, origEnd) { | ||
const startInfo = sourceMapConsumer.originalPositionFor({ line: origStart.line, column: origStart.column - 1 }) | ||
// const endInfo = sourceMapConsumer.originalPositionFor({line: origEnd.line, column: origEnd.column - 2}) | ||
// When there is no match, startInfo.source is null | ||
if (!startInfo.source /* || startInfo.source !== endInfo.source */) { | ||
console.error('cssStart', JSON.stringify(origStart)) | ||
origEnd && console.error('cssEnd', JSON.stringify(origEnd)) | ||
// console.error('sourceStart', JSON.stringify(startInfo)); | ||
// console.error('sourceEnd', JSON.stringify(endInfo)); | ||
throw new Error('BUG: sourcemap might be invalid. Maybe try regenerating it?') | ||
} else { | ||
if (commander.verbose) { | ||
console.error('DEBUG: MATCHED this one', JSON.stringify(startInfo)) | ||
} | ||
} | ||
return startInfo | ||
} | ||
const files = {} // key is filename, value is [{startLine, endLine, count}] | ||
const ret = [] // each line in the lcov file. Joined at the end of the function | ||
const cssLines = CSS_STR.split('\n') | ||
function addCoverage (fileName, count, startLine, endLine) { | ||
@@ -292,51 +336,4 @@ // add it to the files | ||
if (commander.coverDeclarations) { | ||
// Loop over every character between origStart and origEnd to make sure they are covered | ||
// TODO: Do not duplicate-count lines just because this code runs character-by-character | ||
let parseColumn = origStart.column | ||
for (let parseLine = origStart.line; parseLine <= origEnd.line; parseLine++) { | ||
const curLineText = cssLines[parseLine - 1] | ||
for (let curColumn = parseColumn - 1; curColumn < curLineText.length; curColumn++) { | ||
const info = sourceMapConsumer.originalPositionFor({ line: parseLine, column: curColumn }) | ||
// stop processing when we hit origEnd | ||
if (parseLine === origEnd.line && curColumn >= origEnd.column) { | ||
break | ||
} | ||
if (/\s/.test(curLineText[curColumn])) { | ||
continue | ||
} | ||
// console.error('PHIL ', curLineText[curColumn], {line: parseLine, column: curColumn}, info); | ||
if (info.source) { | ||
addCoverage(info.source, count, info.line, info.line) | ||
} else { | ||
if (commander.verbose) { | ||
console.error('BUG: Could not look up source for this range:') | ||
console.error('origStart', origStart) | ||
console.error('origEnd', origEnd) | ||
console.error('currIndexes', { line: parseLine, column: curColumn }) | ||
} | ||
} | ||
} | ||
parseColumn = 1 | ||
} | ||
} else { | ||
// Just cover the selectors | ||
const startInfo = sourceMapConsumer.originalPositionFor({ line: origStart.line, column: origStart.column - 1 }) | ||
// const endInfo = sourceMapConsumer.originalPositionFor({line: origEnd.line, column: origEnd.column - 2}) | ||
// When there is no match, startInfo.source is null | ||
if (!startInfo.source /* || startInfo.source !== endInfo.source */) { | ||
console.error('cssStart', JSON.stringify(origStart)) | ||
console.error('cssEnd', JSON.stringify(origEnd)) | ||
// console.error('sourceStart', JSON.stringify(startInfo)); | ||
// console.error('sourceEnd', JSON.stringify(endInfo)); | ||
throw new Error('BUG: sourcemap might be invalid. Maybe try regenerating it?') | ||
} else { | ||
if (commander.verbose) { | ||
console.error('DEBUG: MATCHED this one', JSON.stringify(startInfo)) | ||
} | ||
} | ||
addCoverage(startInfo.source, count, startInfo.line, startInfo.line) | ||
} | ||
const startInfo = getStartInfo(origStart, origEnd) | ||
addCoverage(startInfo.source, count, startInfo.line, startInfo.line) | ||
} else { | ||
@@ -355,2 +352,11 @@ // No sourceMap available | ||
// Mark all the unsupported declarations | ||
const unsupportedDeclarations = Object.keys(cssDeclarations).filter(decl => supportedDeclarations.indexOf(decl) < 0) | ||
for (const decl of unsupportedDeclarations) { | ||
for (const loc of cssDeclarations[decl]) { | ||
const startInfo = getStartInfo(loc.start) | ||
addCoverage(startInfo.source, 0, startInfo.line, startInfo.line) | ||
} | ||
} | ||
for (const fileName in files) { | ||
@@ -357,0 +363,0 @@ let nonZero = 0 // For summary info |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
401
117374